The declared return type for a client tool does not admit the object results that
BaseConversation already coerces at runtime, so TypeScript callers cannot express
something the SDK supports on purpose.
What the type says
packages/client/src/BaseConversation.ts on main:
export type ClientToolsConfig = {
clientTools: Record<
string,
(
parameters: any
) => Promise<string | number | void> | string | number | void
>;
};
What the runtime does
Same file, in the client_tool_call handler:
const result =
(await ...) ?? "Client tool execution successful."; // default client-tool call response
// The API expects result to be a string, so we need to convert it if it's not already a string
const formattedResult =
typeof result === "object" ? JSON.stringify(result) : String(result);
An object result is serialised and sent. The comment states the intent, and #41
("Always cast tool result to string", March 2025) added the coercion for exactly this
reason: "The API expects the result of a client tool to always be a string, this
enforces that on the SDK level."
So returning an object is supported behaviour that the published type rejects.
Why it is more than cosmetic
The type is also load-bearing for a second signature. useConversation in
packages/react types startSession as:
startSession: T extends SessionConfig
? (options?: HookOptions) => Promise<string>
: (options: SessionConfig & HookOptions) => Promise<string>;
A handler map that does not satisfy ClientToolsConfig prevents the hook argument
from satisfying Partial<Options>, so T extends SessionConfig cannot hold and every
startSession({ overrides }) call resolves to the second branch. That branch requires
a conversationToken, which is not applicable to an agentId session. The reported
error therefore names conversationToken and gives no indication that the cause is the
client tool return type.
Concretely, this is what a caller sees:
Argument of type '{ overrides: { conversation: { textOnly: boolean } } }' is not
assignable to parameter of type 'SessionConfig & HookOptions'.
Property 'conversationToken' is missing in type ...
Suggested change
Widen the return type to include what the runtime handles:
(parameters: any) => Promise<unknown> | unknown
A narrower version that keeps the intent visible would also work:
(parameters: any) =>
| Promise<string | number | object | void>
| string | number | object | void
Either makes the type agree with formattedResult, and neither changes behaviour.
Workaround, for anyone finding this first
Serialise on the caller's side before handing the map to useConversation. Two details
matter if you do:
- Objects only: strings and numbers are already accepted, so converting them adds
nothing.
- Leave nullish results alone: a handler returning nothing must stay nullish so the
?? "Client tool execution successful." default can apply. Stringifying it produces
"undefined" and makes that default unreachable.
Versions checked
@elevenlabs/client 1.21.0 and main at time of writing: type and runtime both as
quoted above.
@elevenlabs/client 0.13.1: same type, same coercion, but no
?? "Client tool execution successful." default. A void handler produced the string
"undefined" there.
- Discussions are not enabled on the repository, so there is no discussion thread to
reference.
The declared return type for a client tool does not admit the object results that
BaseConversationalready coerces at runtime, so TypeScript callers cannot expresssomething the SDK supports on purpose.
What the type says
packages/client/src/BaseConversation.tsonmain:What the runtime does
Same file, in the
client_tool_callhandler:An object result is serialised and sent. The comment states the intent, and #41
("Always cast tool result to string", March 2025) added the coercion for exactly this
reason: "The API expects the result of a client tool to always be a string, this
enforces that on the SDK level."
So returning an object is supported behaviour that the published type rejects.
Why it is more than cosmetic
The type is also load-bearing for a second signature.
useConversationinpackages/reacttypesstartSessionas:A handler map that does not satisfy
ClientToolsConfigprevents the hook argumentfrom satisfying
Partial<Options>, soT extends SessionConfigcannot hold and everystartSession({ overrides })call resolves to the second branch. That branch requiresa
conversationToken, which is not applicable to anagentIdsession. The reportederror therefore names
conversationTokenand gives no indication that the cause is theclient tool return type.
Concretely, this is what a caller sees:
Suggested change
Widen the return type to include what the runtime handles:
A narrower version that keeps the intent visible would also work:
Either makes the type agree with
formattedResult, and neither changes behaviour.Workaround, for anyone finding this first
Serialise on the caller's side before handing the map to
useConversation. Two detailsmatter if you do:
nothing.
?? "Client tool execution successful."default can apply. Stringifying it produces"undefined" and makes that default unreachable.
Versions checked
@elevenlabs/client1.21.0 andmainat time of writing: type and runtime both asquoted above.
@elevenlabs/client0.13.1: same type, same coercion, but no?? "Client tool execution successful."default. A void handler produced the string"undefined" there.
reference.