






























@@ -46,9 +46,58 @@ function findSseEventBoundary(buffer: string): { index: number; length: number }
4646return best;
4747}
484849-function sanitizeOpenAISdkSseResponse(response: Response): Response {
49+function sanitizeOpenAISdkSseResponse(
50+response: Response,
51+options?: { synthesizeJsonAsSse?: boolean },
52+): Response {
5053const contentType = response.headers.get("content-type") ?? "";
51-if (!response.ok || !response.body || !/\btext\/event-stream\b/i.test(contentType)) {
54+if (!response.ok || !response.body) {
55+return response;
56+}
57+if (
58+options?.synthesizeJsonAsSse === true &&
59+(/\bapplication\/json\b/i.test(contentType) || /\+json\b/i.test(contentType))
60+) {
61+const source = response.body;
62+const decoder = new TextDecoder();
63+const encoder = new TextEncoder();
64+let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
65+let buffer = "";
66+const sseBody = new ReadableStream<Uint8Array>({
67+start() {
68+reader = source.getReader();
69+},
70+async pull(controller) {
71+try {
72+const chunk = await reader?.read();
73+if (!chunk || chunk.done) {
74+buffer += decoder.decode();
75+const data = buffer.trim();
76+if (data) {
77+controller.enqueue(encoder.encode(`data: ${data}\n\n`));
78+}
79+controller.enqueue(encoder.encode("data: [DONE]\n\n"));
80+controller.close();
81+return;
82+}
83+buffer += decoder.decode(chunk.value, { stream: true });
84+} catch (error) {
85+controller.error(error);
86+}
87+},
88+async cancel(reason) {
89+await reader?.cancel(reason);
90+},
91+});
92+const headers = new Headers(response.headers);
93+headers.set("content-type", "text/event-stream; charset=utf-8");
94+return new Response(sseBody, {
95+status: response.status,
96+statusText: response.statusText,
97+ headers,
98+});
99+}
100+if (!/\btext\/event-stream\b/i.test(contentType)) {
52101return response;
53102}
54103@@ -115,6 +164,39 @@ function sanitizeOpenAISdkSseResponse(response: Response): Response {
115164});
116165}
117166167+async function requestBodyHasStreamTrue(
168+request: Request | undefined,
169+init: RequestInit | undefined,
170+): Promise<boolean> {
171+const method = request?.method ?? init?.method;
172+if (method && method.toUpperCase() !== "POST") {
173+return false;
174+}
175+const headers = request?.headers ?? new Headers(init?.headers);
176+const contentType = headers.get("content-type") ?? "";
177+if (contentType && !/\bapplication\/json\b/i.test(contentType)) {
178+return false;
179+}
180+181+let text: string | undefined;
182+if (request) {
183+text = await request
184+.clone()
185+.text()
186+.catch(() => undefined);
187+} else if (typeof init?.body === "string") {
188+text = init.body;
189+}
190+if (!text) {
191+return false;
192+}
193+try {
194+return (JSON.parse(text) as { stream?: unknown }).stream === true;
195+} catch {
196+return false;
197+}
198+}
199+118200function parseRetryAfterSeconds(headers: Headers): number | undefined {
119201const retryAfterMs = headers.get("retry-after-ms");
120202if (retryAfterMs) {
@@ -344,6 +426,7 @@ export function buildGuardedModelFetch(model: Model<Api>, timeoutMs?: number): t
344426signal: request.signal,
345427 ...(request.body ? ({ duplex: "half" } as const) : {}),
346428} satisfies RequestInit & { duplex?: "half" });
429+const synthesizeJsonAsSse = await requestBodyHasStreamTrue(request, requestInit ?? init);
347430const guardedFetchOptions = {
348431 url,
349432init: requestInit ?? init,
@@ -377,6 +460,6 @@ export function buildGuardedModelFetch(model: Model<Api>, timeoutMs?: number): t
377460});
378461}
379462response = buildManagedResponse(response, result.release, result.refreshTimeout);
380-return sanitizeOpenAISdkSseResponse(response);
463+return sanitizeOpenAISdkSseResponse(response, { synthesizeJsonAsSse });
381464};
382465}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。