mcpField
E
elevenlabs:sdk-migration

elevenlabs:sdk-migration

github.com/elevenlabs开发11899 次安装2 天前更新
代码仓库

关于 elevenlabs:sdk-migration

面向开发者的迁移指南,帮助将 ElevenLabs client、React 和 React Native 包升级到下一个主版本。涵盖破坏性 API 变更及可选的 Hook 重构。

主要功能

  1. 01分步迁移软件包
  2. 02配置 ConversationProvider
  3. 03更新 Conversation API
  4. 04使用细粒度会话 Hook
  5. 05注册 React 客户端工具

适用场景

  1. 01将应用升级到下一个 SDK 主版本
  2. 02替换已移除的导出并更新导入
  3. 03将 React 状态重构为细粒度 Hook

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elevenlabs/packages --skill elevenlabs:sdk-migration

安装后在 Claude Code 中输入/elevenlabs:sdk-migration确认已加载。

详细介绍

SDK 迁移指南(下一个主版本)

@elevenlabs/client、@elevenlabs/react 和 @elevenlabs/react-native 在下一个主版本中存在破坏性变更,本文将介绍如何迁移。

迁移顺序

请按顺序执行以下步骤,每一步都以前一步为基础。

  1. 安装新软件包。
  2. 修正导入——移除已删除的导出项(Input、Output、DeviceFormatConfig、DeviceInputConfig 和 ElevenLabsProvider),并替换为对应的新项。
  3. 使用 ConversationProvider 包装——每次调用 useConversation 现在都必须有一个上级 Provider。
  4. 更新 API 调用——适配已变更的签名(startSession 在 React 中现在是同步的、Conversation 不再是类等)。
  5. 通过细粒度 Hook 优化(可选)——将本地状态替换为 useConversationStatus、useConversationMode 等,以提升渲染性能。

安装

npm install @elevenlabs/client@next @elevenlabs/react@next @elevenlabs/react-native@next

@elevenlabs/client

替换 instanceof Conversation 检查

Conversation 现在是一个普通的命名空间对象,也是 TextConversation | VoiceConversation 的类型别名。这样可以启用摇树优化,并简化内部架构。instanceof Conversation 将无法编译,也无法对其进行子类化。

请使用鸭子类型来缩窄类型:

之前:

import { Conversation } from "@elevenlabs/client";

if (session instanceof Conversation) {
  /* ... */
}
class MyConversation extends Conversation {
  /* ... */
}

之后:

import { Conversation } from "@elevenlabs/client";

// startSession call is unchanged
const session: Conversation = await Conversation.startSession(options);

// Narrow using duck-typing instead of instanceof
if ("changeInputDevice" in session) {
  // session is VoiceConversation
}
使用会话方法替代 Input 和 Output

由于直接访问内部音频节点会与实现细节紧密耦合,因此不再导出 Input 和 Output 类。VoiceConversation 上的 input 和 output 字段现在是私有的。所有音频操作现在都是会话实例的方法。

之前:

import { Input, Output } from "@elevenlabs/client";

const input: Input = conversation.input;
input.analyser.getByteFrequencyData(data);
input.setMuted(true);

const output: Output = conversation.output;
output.gain.gain.value = 0.5;
output.analyser.getByteFrequencyData(data);

const newInput: Input = await conversation.changeInputDevice(config);

之后:

conversation.getInputByteFrequencyData(); // replaces input.analyser.getByteFrequencyData
conversation.setMicMuted(true); // replaces input.setMuted
conversation.setVolume({ volume: 0.5 }); // replaces output.gain.gain.value
conversation.getOutputByteFrequencyData(); // replaces output.analyser.getByteFrequencyData

await conversation.changeInputDevice(config); // return value dropped
await conversation.changeOutputDevice(config); // return value dropped
移除对 wakeLock 的直接访问

现在,wakeLock 字段是私有的,因为唤醒锁的生命周期由系统自动管理,以防止意外干扰。如需停用,请使用 useWakeLock: false:

const conversation = await Conversation.startSession({
  // ...
  useWakeLock: false,
});

@elevenlabs/react

使用 ConversationProvider 包装 useConversation

useConversation 现在必须有一个上级 ConversationProvider。Provider 会保存共享的会话状态,多个 Hook 可以分别订阅该状态,这也是新细粒度 Hook 系统提升渲染性能的基础。

该 Hook 接受与之前相同的选项,返回的结构也相同,此外还新增了 isMuted、setMuted、isListening、mode 和 message 字段。

迁移包含多个使用 useConversation 的组件的代码库时,请询问用户希望采用哪种方案:

  1. 使用单个共享的 ConversationProvider,在组件树较高的位置包裹所有会话组件(所有组件共享一个会话),还是
  2. 为每个组件单独添加 ConversationProvider 包装器(每个组件管理各自独立的会话)。

此选择会影响会话共享、状态隔离和组件架构——不要自行假设,应先询问再继续。

之前:

import { useConversation } from "@elevenlabs/react";

function App() {
  const { status, isSpeaking, startSession, endSession } = useConversation({
    agentId: "your-agent-id",
    onMessage: message => console.log(message),
    onError: error => console.error(error),
  });

  return (
    <div>
      <p>Status: {status}</p>
      <p>{isSpeaking ? "Agent is speaking" : "Agent is listening"}</p>
      <button onClick={() => startSession()}>Start</button>
      <button onClick={() => endSession()}>Stop</button>
    </div>
  );
}

之后:

import { ConversationProvider, useConversation } from "@elevenlabs/react";

function App() {
  return (
    <ConversationProvider>
      <Conversation />
    </ConversationProvider>
  );
}

function Conversation() {
  const { status, isSpeaking, startSession, endSession } = useConversation({
    agentId: "your-agent-id",
    onMessage: message => console.log(message),
    onError: error => console.error(error),
  });

  return (
    <div>
      <p>Status: {status}</p>
      <p>{isSpeaking ? "Agent is speaking" : "Agent is listening"}</p>
      <button onClick={() => startSession()}>Start</button>
      <button onClick={() => endSession()}>Stop</button>
    </div>
  );
}
从 startSession 中移除 await

使用 useConversation(React)时,startSession 现在是同步的,并返回 void。会话生命周期由 Provider 管理,错误通过回调传递,而非通过抛出 Promise 错误传递。

迁移时:

  • 从 startSession() 调用中移除 await。
  • 如果 startSession() 是函数中唯一需要等待的调用,则从该函数中移除 async 关键字。
  • 如果原本使用 try/catch 包围 startSession() 来处理连接错误,请将错误处理移至 onError 回调或 useConversationStatus Hook——同步调用不再因会话失败而抛出错误。

之前:

const startConversation = async () => {
  try {
    await conversation.startSession({ agentId: "..." });
  } catch (error) {
    console.error("Failed to start:", error);
    setStatus("disconnected");
  }
};

之后:

// onError handles failures instead of try/catch
const conversation = useConversation({
  onError: (error) => {
    console.error("Failed to start:", error);
    setStatus("disconnected");
  },
});

const startConversation = () => {
  conversation.startSession({ agentId: "..." });
};
更新已移除的类型导入
  • 将 DeviceFormatConfig 替换为从 @elevenlabs/client 导入的 FormatConfig。
  • 将 DeviceInputConfig 替换为从 @elevenlabs/client 导入的 InputDeviceConfig。
重新导出变更

@elevenlabs/react 现在通过 export * 重新导出 @elevenlabs/client 的全部内容,取代之前的选择性重新导出。

采用细粒度对话钩子(可选)

新的钩子会订阅对话状态的独立部分,因此只读取模式的组件不会因状态变化而重新渲染。这是 ConversationProvider 架构的主要优势——共享状态支持细粒度订阅。

初次迁移完成并通过编译后,请检查组件是否维护了本地连接状态(例如,用于 agentState、isMuted、isSpeaking 的 useState),而这些状态与钩子提供的状态重复。如果是,请询问用户是否想要用细粒度钩子替换本地状态:

  • useConversationStatus() 替换通过 onStatusChange 回调管理的本地 agentState / status 状态。
  • useConversationInput() 替换通过手动切换管理的本地 isMuted 状态。
  • useConversationMode() 替换本地 isSpeaking / isListening 状态。
  • useConversationControls() 提供稳定的操作引用(startSession、endSession、sendUserMessage、getInputVolume、getOutputVolume 等),永远不会导致重新渲染。

重构为细粒度钩子时:

  • 如果事件回调(onConnect、onDisconnect、onError、onMessage)不需要访问组件本地状态,请将它们移至 ConversationProvider 属性中。
  • 对于引用组件本地状态的回调(例如,更新本地 messages 数组),继续使用 useConversation;但读取状态/模式/输入状态时,请使用细粒度钩子。
  • 从 startSession 选项中移除 onStatusChange——现在可通过 useConversationStatus() 响应式地获取状态。
钩子返回值
useConversationControls()稳定的操作方法:startSession、endSession、sendUserMessage、setVolume、changeInputDevice、changeOutputDevice 等。永远不会导致重新渲染。
useConversationStatus()status("disconnected" / "connecting" / "connected" / "error")和可选的 message。
useConversationInput()isMuted 状态和 setMuted 操作。
useConversationMode()mode("speaking" / "listening")、isSpeaking、isListening。
useConversationFeedback()canSendFeedback 状态和 sendFeedback(like: boolean) 操作。
useRawConversation()原始 Conversation 实例或 null(应急出口)。

所有钩子都需要一个 ConversationProvider 祖先组件。

useConversation 的映射关系:

useConversation 返回值细粒度钩子
status、messageuseConversationStatus()
isSpeaking、isListening、modeuseConversationMode()
canSendFeedback、sendFeedbackuseConversationFeedback()
isMuted、setMuteduseConversationInput()
startSession、endSession、setVolume 等useConversationControls()

示例 — 每个组件仅在其特定状态发生变化时重新渲染:

import {
  ConversationProvider,
  useConversationStatus,
  useConversationMode,
  useConversationControls,
  useConversationInput,
  useConversationFeedback,
} from "@elevenlabs/react";

function App() {
  return (
    <ConversationProvider agentId="your-agent-id">
      <StatusBadge />
      <Controls />
      <MuteButton />
      <FeedbackButtons />
      <ModeIndicator />
    </ConversationProvider>
  );
}

/** Only re-renders when status changes. */
function StatusBadge() {
  const { status } = useConversationStatus();
  return <span className={`badge badge-${status}`}>{status}</span>;
}

/** Never re-renders — controls are stable references. */
function Controls() {
  const { startSession, endSession } = useConversationControls();
  return (
    <div>
      <button onClick={() => startSession()}>Start</button>
      <button onClick={() => endSession()}>Stop</button>
    </div>
  );
}

/** Only re-renders when mute state changes. */
function MuteButton() {
  const { isMuted, setMuted } = useConversationInput();
  return (
    <button onClick={() => setMuted(!isMuted)}>
      {isMuted ? "Unmute" : "Mute"}
    </button>
  );
}

/** Only re-renders when feedback availability changes. */
function FeedbackButtons() {
  const { canSendFeedback, sendFeedback } = useConversationFeedback();
  if (!canSendFeedback) return null;
  return (
    <div>
      <button onClick={() => sendFeedback(true)}>👍</button>
      <button onClick={() => sendFeedback(false)}>👎</button>
    </div>
  );
}

/** Only re-renders when mode changes. */
function ModeIndicator() {
  const { isSpeaking, isListening } = useConversationMode();
  return (
    <p>
      {isSpeaking ? "Agent is speaking..." : isListening ? "Listening..." : ""}
    </p>
  );
}
使用 useConversationClientTool 注册客户端工具

用于从 React 组件中动态注册客户端工具的新 hook。会话开始后添加或移除的工具会立即可见。重复的工具名称会引发错误。

import { useConversationClientTool } from "@elevenlabs/react";

// Untyped — parameters are Record<string, unknown>
useConversationClientTool("get_weather", params => {
  const city = params["city"];
  return `Weather in ${city} is sunny.`;
});

// Type-safe — tool names are constrained, params and return types are inferred
type Tools = {
  get_weather: (params: { city: string }) => string;
  set_volume: (params: { level: number }) => void;
};

useConversationClientTool<Tools>("get_weather", params => {
  return `Weather in ${params.city} is sunny.`;
});

@elevenlabs/react-native

将 ElevenLabsProvider 替换为 ConversationProvider

基于 LiveKit 的自定义实现(ElevenLabsProvider、useConversation)已被完全移除。现在,该软件包会从 @elevenlabs/react 重新导出,使 Web 和移动端的 API 统一为 ConversationProvider 和相同的细粒度 hook。

在 React Native 上,导入该软件包时会执行副作用:填充 WebRTC 全局对象、配置原生 AudioSession,以及注册特定于平台的语音会话策略。在 Web 上,该软件包会重新导出内容而不产生副作用。

之前:

import { ElevenLabsProvider, useConversation } from "@elevenlabs/react-native";

function App() {
  return (
    <ElevenLabsProvider>
      <Conversation />
    </ElevenLabsProvider>
  );
}

function Conversation() {
  const conversation = useConversation({
    onConnect: ({ conversationId }) => console.log("Connected", conversationId),
    onError: message => console.error(message),
  });

  return (
    <Button
      onPress={() => conversation.startSession({ agentId: "your-agent-id" })}
    />
  );
}

之后:

import {
  ConversationProvider,
  useConversationControls,
  useConversationStatus,
} from "@elevenlabs/react-native";

function App() {
  return (
    <ConversationProvider
      onConnect={({ conversationId }) =>
        console.log("Connected", conversationId)
      }
      onError={message => console.error(message)}
    >
      <Conversation />
    </ConversationProvider>
  );
}

function Conversation() {
  const { startSession } = useConversationControls();
  const { status } = useConversationStatus();

  return <Button onPress={() => startSession({ agentId: "your-agent-id" })} />;
}

评论

支持 Markdown