feat: show client tools in debug preview

This commit is contained in:
Xin Wang
2026-08-03 14:00:33 +08:00
parent ef434c80b2
commit dd40bfb90a
4 changed files with 242 additions and 28 deletions

View File

@@ -71,6 +71,23 @@ export type SessionUpdateResult = {
export type ClientToolHandler = (
argumentsValue: Record<string, unknown>,
) => unknown | Promise<unknown>;
export type ClientToolDefinition = {
name: string;
label: string;
description: string;
parameters: readonly ClientToolParameter[];
};
export type ClientToolParameter = {
name: string;
type: string;
required: boolean;
description: string;
};
type RegisteredClientTool = {
definition: ClientToolDefinition;
handler: ClientToolHandler;
};
type PendingUserInput = {
resolve: (result: UserInputResult) => void;
@@ -276,6 +293,7 @@ export function useVoicePreview(
const [audioOutputs, setAudioOutputs] = useState<MediaDeviceInfo[]>([]);
const [selectedDeviceId, setSelectedDeviceId] = useState("");
const [selectedOutputDeviceId, setSelectedOutputDeviceId] = useState("");
const [clientTools, setClientTools] = useState<ClientToolDefinition[]>([]);
const transportRef = useRef<AppSmallWebRTCTransport | null>(null);
const cleanupPromiseRef = useRef<Promise<void>>(Promise.resolve());
@@ -290,7 +308,9 @@ export function useVoicePreview(
const pendingSessionUpdatesRef = useRef(
new Map<string, PendingSessionUpdate>(),
);
const clientToolHandlersRef = useRef(new Map<string, ClientToolHandler>());
const clientToolHandlersRef = useRef(
new Map<string, RegisteredClientTool>(),
);
const endedByServerRef = useRef(false);
const selectedDeviceIdRef = useRef("");
const selectedOutputDeviceIdRef = useRef("");
@@ -396,10 +416,10 @@ export function useVoicePreview(
const waitForResponse = msg.wait_for_response !== false;
if (!toolCallId || !functionName) return;
const handler = clientToolHandlersRef.current.get(functionName);
const registeredTool = clientToolHandlersRef.current.get(functionName);
const transport = transportRef.current;
if (!transport || transport.state !== "connected") return;
if (!handler) {
if (!registeredTool) {
if (waitForResponse) {
transport.sendAppMessage({
type: "client-tool-result",
@@ -418,7 +438,7 @@ export function useVoicePreview(
!Array.isArray(msg.arguments)
? (msg.arguments as Record<string, unknown>)
: {};
const data = await handler(argumentsValue);
const data = await registeredTool.handler(argumentsValue);
if (transportRef.current !== transport) return;
if (!waitForResponse) return;
transport.sendAppMessage({
@@ -889,11 +909,27 @@ export function useVoicePreview(
);
const registerClientTool = useCallback(
(functionName: string, handler: ClientToolHandler): (() => void) => {
clientToolHandlersRef.current.set(functionName, handler);
(
definition: ClientToolDefinition,
handler: ClientToolHandler,
): (() => void) => {
const registeredTool = { definition, handler };
clientToolHandlersRef.current.set(definition.name, registeredTool);
setClientTools(
[...clientToolHandlersRef.current.values()].map(
(item) => item.definition,
),
);
return () => {
if (clientToolHandlersRef.current.get(functionName) === handler) {
clientToolHandlersRef.current.delete(functionName);
if (
clientToolHandlersRef.current.get(definition.name) === registeredTool
) {
clientToolHandlersRef.current.delete(definition.name);
setClientTools(
[...clientToolHandlersRef.current.values()].map(
(item) => item.definition,
),
);
}
};
},
@@ -911,6 +947,7 @@ export function useVoicePreview(
remoteStream,
messages,
sessionVariables,
clientTools,
callEnded,
networkQuality,
audioInputs,