From ef4cebb2636083dfce3beafad503a175a84c9c1a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 31 Jul 2026 00:50:04 +0800 Subject: [PATCH] fix(openai): bound GPT-Live delegation output --- extensions/openai/realtime-quicksilver-bridge.ts | 11 ++++++----- .../openai/realtime-quicksilver-gateway-bridge.ts | 3 ++- extensions/openai/realtime-quicksilver-session.ts | 3 ++- extensions/openai/realtime-quicksilver-wire.ts | 13 +++++++++++++ 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/extensions/openai/realtime-quicksilver-bridge.ts b/extensions/openai/realtime-quicksilver-bridge.ts index 95b012dbdb16..e0dd6583d4c0 100644 --- a/extensions/openai/realtime-quicksilver-bridge.ts +++ b/extensions/openai/realtime-quicksilver-bridge.ts @@ -22,6 +22,7 @@ import { type OpenAIQuicksilverSocketFactory, } from "./realtime-quicksilver-sideband.js"; import { + boundOpenAIQuicksilverDelegationResult, buildOpenAIQuicksilverSessionUpdate, buildOpenAIQuicksilverWebSocketUrl, chunkOpenAIQuicksilverAppendText, @@ -342,13 +343,13 @@ export class OpenAIQuicksilverVoiceBridge implements RealtimeVoiceBridge { options?: RealtimeVoiceToolResultOptions, ): void { const channel = options?.suppressResponse || options?.willContinue ? "commentary" : "speakable"; - const type = this.activeDelegations.has(callId) - ? "delegation.context.append" - : "session.context.append"; + const isDelegation = this.activeDelegations.has(callId); + const type = isDelegation ? "delegation.context.append" : "session.context.append"; + const text = toolResultText(result); this.sendContext( type, - type === "delegation.context.append" ? callId : undefined, - toolResultText(result), + isDelegation ? callId : undefined, + isDelegation ? boundOpenAIQuicksilverDelegationResult(text) : text, channel, ); if (!options?.willContinue) { diff --git a/extensions/openai/realtime-quicksilver-gateway-bridge.ts b/extensions/openai/realtime-quicksilver-gateway-bridge.ts index 5bf57e4d811b..73efd178c805 100644 --- a/extensions/openai/realtime-quicksilver-gateway-bridge.ts +++ b/extensions/openai/realtime-quicksilver-gateway-bridge.ts @@ -26,6 +26,7 @@ import { } from "./realtime-quicksilver-sideband.js"; import { boundOpenAIQuicksilverContextItems, + boundOpenAIQuicksilverDelegationResult, buildOpenAIQuicksilverSession, chunkOpenAIQuicksilverAppendText, createOpenAIQuicksilverCall, @@ -479,7 +480,7 @@ export class OpenAIQuicksilverGatewayBridge implements RealtimeVoiceBridge { if (params.signal.aborted) { return; } - text = result.text; + text = boundOpenAIQuicksilverDelegationResult(result.text); } catch (error) { // Host steering aborts the runner's registered chat signal, not this bridge-owned signal. // Abort-shaped rejection is therefore the runner boundary's cancellation marker. diff --git a/extensions/openai/realtime-quicksilver-session.ts b/extensions/openai/realtime-quicksilver-session.ts index 192241e94961..9798c1004aab 100644 --- a/extensions/openai/realtime-quicksilver-session.ts +++ b/extensions/openai/realtime-quicksilver-session.ts @@ -30,6 +30,7 @@ import { } from "./realtime-quicksilver-sideband.js"; import { boundOpenAIQuicksilverContextItems, + boundOpenAIQuicksilverDelegationResult, buildOpenAIQuicksilverSession, chunkOpenAIQuicksilverAppendText, createOpenAIQuicksilverCall, @@ -295,7 +296,7 @@ export function createOpenAIQuicksilverBrowserSessionBroker(params: { if (signal.aborted) { return; } - finalText = result.text; + finalText = boundOpenAIQuicksilverDelegationResult(result.text); } catch (error) { if (signal.aborted) { return; diff --git a/extensions/openai/realtime-quicksilver-wire.ts b/extensions/openai/realtime-quicksilver-wire.ts index 7511716faefa..31fb758c9389 100644 --- a/extensions/openai/realtime-quicksilver-wire.ts +++ b/extensions/openai/realtime-quicksilver-wire.ts @@ -5,10 +5,12 @@ import { readResponseTextLimited, resolveProviderRequestHeaders, } from "openclaw/plugin-sdk/provider-http"; +import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; import { z } from "zod"; import { isOpenAIGptLiveModel } from "./realtime-quicksilver.js"; const OPENAI_QUICKSILVER_APPEND_MAX_BYTES = 500; +const OPENAI_QUICKSILVER_DELEGATION_RESULT_MAX_CHARS = 1_800; const OPENAI_QUICKSILVER_CONTEXT_MAX_ENTRIES = 16; const OPENAI_QUICKSILVER_CONTEXT_MAX_ITEM_CHARS = 800; const OPENAI_QUICKSILVER_CONTEXT_MAX_UTF8_BYTES = 8_000; @@ -609,3 +611,14 @@ export function chunkOpenAIQuicksilverAppendText(text: string): string[] { } return chunks; } + +/** Bound completed delegation output while preserving under-limit text byte-for-byte. */ +export function boundOpenAIQuicksilverDelegationResult(text: string): string { + if (text.length <= OPENAI_QUICKSILVER_DELEGATION_RESULT_MAX_CHARS) { + return text; + } + return `${truncateUtf16Safe( + text, + OPENAI_QUICKSILVER_DELEGATION_RESULT_MAX_CHARS - 16, + ).trimEnd()} [truncated]`; +}