





















@@ -6,6 +6,7 @@ import { GoogleGenAI, Modality } from "@google/genai";
66import { chromium, type Browser } from "playwright";
77import { createServer } from "vite";
88import { buildOpenAIRealtimeVoiceProvider } from "../../extensions/openai/realtime-voice-provider.ts";
9+import { readBoundedResponseText } from "../lib/bounded-response.ts";
910import {
1011parseStrictIntegerOption,
1112previewForDevToolLog,
@@ -50,95 +51,16 @@ function shortError(error: unknown): string {
5051return previewForDevToolLog(error instanceof Error ? error.message : String(error), 800);
5152}
525353-function responseBodyTooLargeError(label: string, maxBytes: number): Error {
54-return new Error(`${label} response body exceeded ${maxBytes} bytes`);
55-}
56-5754async function readBoundedText(
5855response: Response,
5956label: string,
6057maxBytes = OPENAI_HTTP_RESPONSE_MAX_BYTES,
6158signal?: AbortSignal,
6259): Promise<string> {
63-const contentLength = Number(response.headers.get("content-length") ?? "");
64-if (Number.isSafeInteger(contentLength) && contentLength > maxBytes) {
65-await response.body?.cancel().catch(() => undefined);
66-throw responseBodyTooLargeError(label, maxBytes);
67-}
68-69-if (!response.body) {
70-return "";
71-}
72-73-const reader = response.body.getReader();
74-const decoder = new TextDecoder();
75-const chunks: string[] = [];
76-let totalBytes = 0;
77-let canceled = false;
78-79-try {
80-for (;;) {
81-const { done, value } = await readResponseChunk(reader, label, signal, () => {
82-canceled = true;
83-});
84-if (done) {
85-const tail = decoder.decode();
86-if (tail) {
87-chunks.push(tail);
88-}
89-break;
90-}
91-92-totalBytes += value.byteLength;
93-if (totalBytes > maxBytes) {
94-canceled = true;
95-await reader.cancel().catch(() => undefined);
96-throw responseBodyTooLargeError(label, maxBytes);
97-}
98-chunks.push(decoder.decode(value, { stream: true }));
99-}
100-} finally {
101-if (!canceled) {
102-reader.releaseLock();
103-}
104-}
105-106-return chunks.join("");
107-}
108-109-async function readResponseChunk(
110-reader: ReadableStreamDefaultReader<Uint8Array>,
111-label: string,
112-signal: AbortSignal | undefined,
113-markCanceled: () => void,
114-): Promise<ReadableStreamReadResult<Uint8Array>> {
115-if (!signal) {
116-return await reader.read();
117-}
118-if (signal.aborted) {
119-markCanceled();
120-await reader.cancel().catch(() => undefined);
121-throw signal.reason instanceof Error ? signal.reason : new Error(`${label} request aborted`);
122-}
123-124-let removeAbortListener: (() => void) | undefined;
125-const abortPromise = new Promise<ReadableStreamReadResult<Uint8Array>>((_resolve, reject) => {
126-const onAbort = () => {
127-markCanceled();
128-void reader.cancel().catch(() => undefined);
129-reject(
130-signal.reason instanceof Error ? signal.reason : new Error(`${label} request aborted`),
131-);
132-};
133-signal.addEventListener("abort", onAbort, { once: true });
134-removeAbortListener = () => signal.removeEventListener("abort", onAbort);
60+return await readBoundedResponseText(response, label, maxBytes, {
61+createTooLargeError: (message) => new Error(message),
62+ signal,
13563});
136-137-try {
138-return await Promise.race([reader.read(), abortPromise]);
139-} finally {
140-removeAbortListener?.();
141-}
14264}
1436514466async function readBoundedJsonResponse(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。