关于 elevenlabs:sdk-migration
面向开发者的迁移指南,帮助将 ElevenLabs client、React 和 React Native 包升级到下一个主版本。涵盖破坏性 API 变更及可选的 Hook 重构。
主要功能
- 01分步迁移软件包
- 02配置 ConversationProvider
- 03更新 Conversation API
- 04使用细粒度会话 Hook
- 05注册 React 客户端工具
适用场景
- 01将应用升级到下一个 SDK 主版本
- 02替换已移除的导出并更新导入
- 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 在下一个主版本中存在破坏性变更,本文将介绍如何迁移。
迁移顺序
请按顺序执行以下步骤,每一步都以前一步为基础。
- 安装新软件包。
- 修正导入——移除已删除的导出项(
Input、Output、DeviceFormatConfig、DeviceInputConfig和ElevenLabsProvider),并替换为对应的新项。 - 使用
ConversationProvider包装——每次调用useConversation现在都必须有一个上级 Provider。 - 更新 API 调用——适配已变更的签名(
startSession在 React 中现在是同步的、Conversation不再是类等)。 - 通过细粒度 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 的组件的代码库时,请询问用户希望采用哪种方案:
- 使用单个共享的
ConversationProvider,在组件树较高的位置包裹所有会话组件(所有组件共享一个会话),还是 - 为每个组件单独添加
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回调或useConversationStatusHook——同步调用不再因会话失败而抛出错误。
之前:
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、message | useConversationStatus() |
isSpeaking、isListening、mode | useConversationMode() |
canSendFeedback、sendFeedback | useConversationFeedback() |
isMuted、setMuted | useConversationInput() |
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" })} />;
}