

























@@ -2,6 +2,8 @@ import { bytesToBase64, floatToPcm16 } from "./realtime-talk-audio.ts";
22import { RealtimeTalkPcmOutputQueue } from "./realtime-talk-pcm-output.ts";
33import {
44REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME,
5+REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME,
6+submitRealtimeTalkAgentControl,
57submitRealtimeTalkConsult,
68type RealtimeTalkGatewayRelaySessionResult,
79type RealtimeTalkEvent,
@@ -30,6 +32,7 @@ type GatewayRelayEvent = {
3032args?: unknown;
3133forced?: boolean;
3234}
35+| { type?: "toolResult"; callId?: string }
3336| { type?: "error"; message?: string }
3437| { type?: "close"; reason?: string }
3538);
@@ -47,7 +50,8 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
4750private unsubscribe: (() => void) | null = null;
4851private closed = false;
4952private readonly outputQueue = new RealtimeTalkPcmOutputQueue();
50-private readonly consultAbortControllers = new Set<AbortController>();
53+private readonly consultAbortControllers = new Map<string, AbortController>();
54+private readonly completedToolCalls = new Set<string>();
5155private cancelRequestedForPlayback = false;
5256private speechFramesDuringPlayback = 0;
5357private lastRelayError: string | undefined;
@@ -187,6 +191,11 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
187191case "toolCall":
188192void this.handleToolCall(event);
189193return;
194+case "toolResult":
195+if (this.isFinalToolResult(event)) {
196+this.completeToolCall(event.callId);
197+}
198+return;
190199case "error":
191200this.lastRelayError = event.message ?? "Realtime relay failed";
192201this.ctx.callbacks.onStatus?.("error", this.lastRelayError);
@@ -237,12 +246,22 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
237246if (!callId || !name) {
238247return;
239248}
249+if (name === REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME) {
250+await submitRealtimeTalkAgentControl({
251+ctx: this.ctx,
252+ callId,
253+args: event.args ?? {},
254+sessionId: this.session.relaySessionId,
255+submit: (toolCallId, result) => this.submitToolResult(toolCallId, result),
256+});
257+return;
258+}
240259if (name !== REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME) {
241260this.submitToolResult(callId, { error: `Tool "${name}" not available in browser Talk` });
242261return;
243262}
244263const abortController = new AbortController();
245-this.consultAbortControllers.add(abortController);
264+this.consultAbortControllers.set(callId, abortController);
246265try {
247266if (event.forced) {
248267this.submitToolResult(
@@ -265,7 +284,7 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
265284submit: (toolCallId, result) => this.submitToolResult(toolCallId, result),
266285});
267286} finally {
268-this.consultAbortControllers.delete(abortController);
287+this.consultAbortControllers.delete(callId);
269288}
270289}
271290@@ -274,6 +293,9 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
274293result: unknown,
275294options?: { suppressResponse?: boolean; willContinue?: boolean },
276295): void {
296+if (this.completedToolCalls.has(callId)) {
297+return;
298+}
277299void this.ctx.client.request("talk.session.submitToolResult", {
278300sessionId: this.session.relaySessionId,
279301 callId,
@@ -282,6 +304,27 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
282304});
283305}
284306307+private completeToolCall(callIdRaw: string | undefined): void {
308+const callId = callIdRaw?.trim();
309+if (!callId) {
310+return;
311+}
312+this.completedToolCalls.add(callId);
313+this.consultAbortControllers.get(callId)?.abort();
314+this.consultAbortControllers.delete(callId);
315+}
316+317+private isFinalToolResult(event: GatewayRelayEvent): boolean {
318+const talkEvent = event.talkEvent;
319+if (talkEvent?.type === "tool.progress") {
320+return false;
321+}
322+if (talkEvent?.type === "tool.result" && talkEvent.final === false) {
323+return false;
324+}
325+return true;
326+}
327+285328private cancelOutputForBargeIn(): void {
286329if (!this.outputQueue.isPlaying || this.cancelRequestedForPlayback) {
287330return;
@@ -295,7 +338,7 @@ export class GatewayRelayRealtimeTalkTransport implements RealtimeTalkTransport
295338}
296339297340private abortConsults(): void {
298-for (const controller of this.consultAbortControllers) {
341+for (const controller of this.consultAbortControllers.values()) {
299342controller.abort();
300343}
301344this.consultAbortControllers.clear();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。