diff --git a/extensions/openai/realtime-quicksilver-wire.test.ts b/extensions/openai/realtime-quicksilver-wire.test.ts index 1b6c03830d79..170d6637308f 100644 --- a/extensions/openai/realtime-quicksilver-wire.test.ts +++ b/extensions/openai/realtime-quicksilver-wire.test.ts @@ -315,4 +315,37 @@ describe("GPT-Live call creation", () => { message: "GPT-Live call creation returned an empty SDP answer", }); }); + + it.each([ + { + label: "GPT-Live", + auth: { type: "oauth" as const, token: "oauth-token", accountId: "acct-1" }, + model: "gpt-live-1-codex", + location: "/v1/live/rtc_oversized_answer", + }, + { + label: "OpenAI Realtime", + auth: { type: "api-key" as const, token: "platform-key" }, + model: "gpt-realtime-2.1", + location: undefined, + }, + ])("rejects an oversized streaming $label SDP answer", async (testCase) => { + const fetchImpl = vi.fn( + async () => + new Response(`v=answer\r\n${"x".repeat(256 * 1024)}`, { + status: 201, + headers: testCase.location ? { Location: testCase.location } : undefined, + }), + ); + + await expect( + createOpenAIQuicksilverCall({ + auth: testCase.auth, + requestIds: createRequestIds(`oversized-answer-${testCase.label}`), + sdp: "v=offer\r\n", + session: buildOpenAIQuicksilverSession({ model: testCase.model }), + fetchImpl: fetchImpl as unknown as typeof fetch, + }), + ).rejects.toThrow(`${testCase.label} SDP answer: text response exceeds 262144 bytes`); + }); }); diff --git a/extensions/openai/realtime-quicksilver-wire.ts b/extensions/openai/realtime-quicksilver-wire.ts index b8d31136e113..7511716faefa 100644 --- a/extensions/openai/realtime-quicksilver-wire.ts +++ b/extensions/openai/realtime-quicksilver-wire.ts @@ -1,6 +1,7 @@ // GPT-Live frameless session, call-creation, and sideband event wire contracts. import { randomBytes } from "node:crypto"; import { + readProviderTextResponse, readResponseTextLimited, resolveProviderRequestHeaders, } from "openclaw/plugin-sdk/provider-http"; @@ -15,6 +16,7 @@ const OPENAI_QUICKSILVER_CALL_URL = "https://api.openai.com/v1/live"; const OPENAI_REALTIME_CALL_URL = "https://api.openai.com/v1/realtime/calls"; const OPENAI_REALTIME_ERROR_BODY_MAX_BYTES = 16 * 1024; const OPENAI_REALTIME_ERROR_DETAIL_MAX_CHARS = 500; +const OPENAI_REALTIME_SDP_ANSWER_MAX_BYTES = 256 * 1024; const OPENAI_GPT_LIVE_WAITLIST_URL = "https://openai.com/form/gpt-live-1-in-the-api/"; const OPENAI_QUICKSILVER_VOICES = [ @@ -428,7 +430,11 @@ export async function createOpenAIQuicksilverCall(params: { response.status, ); } - const answerSdp = await response.text(); + const answerSdp = await readProviderTextResponse( + response, + `${isGptLive ? "GPT-Live" : "OpenAI Realtime"} SDP answer`, + { maxBytes: OPENAI_REALTIME_SDP_ANSWER_MAX_BYTES }, + ); if (!answerSdp.trim()) { throw new OpenAIQuicksilverCallError( `${isGptLive ? "GPT-Live" : "OpenAI Realtime"} call creation returned an empty SDP answer`,