Skip to content

Commit 27c1685

Browse files
fix(agent): bound proxy error response parsing (#97351)
1 parent 2f851ec commit 27c1685

2 files changed

Lines changed: 57 additions & 4 deletions

File tree

src/agents/runtime/proxy.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,33 @@ function responseFromReaderText(text: string, releaseLock: () => void): Response
6060
} as Response;
6161
}
6262

63+
function streamingProxyErrorResponse(params: { chunkCount: number; chunkSize: number }): {
64+
response: Response;
65+
getReadCount: () => number;
66+
} {
67+
let reads = 0;
68+
const encoder = new TextEncoder();
69+
const stream = new ReadableStream<Uint8Array>({
70+
pull(controller) {
71+
if (reads >= params.chunkCount) {
72+
controller.close();
73+
return;
74+
}
75+
reads += 1;
76+
controller.enqueue(encoder.encode("a".repeat(params.chunkSize)));
77+
},
78+
});
79+
80+
return {
81+
response: new Response(stream, {
82+
status: 502,
83+
statusText: "Bad Gateway",
84+
headers: { "Content-Type": "application/json" },
85+
}),
86+
getReadCount: () => reads,
87+
};
88+
}
89+
6390
describe("streamProxy", () => {
6491
afterEach(() => {
6592
vi.unstubAllGlobals();
@@ -168,6 +195,30 @@ describe("streamProxy", () => {
168195
expect(releaseLock).toHaveBeenCalledTimes(1);
169196
});
170197

198+
it("stops reading oversized proxy error JSON responses", async () => {
199+
const streamed = streamingProxyErrorResponse({ chunkCount: 20, chunkSize: 1024 * 1024 });
200+
vi.stubGlobal(
201+
"fetch",
202+
vi.fn(async () => streamed.response),
203+
);
204+
205+
const stream = streamProxy(model, context, {
206+
authToken: "token",
207+
proxyUrl: "https://proxy.example",
208+
});
209+
const events = [];
210+
for await (const event of stream) {
211+
events.push(event);
212+
}
213+
214+
expect(events.at(-1)?.type).toBe("error");
215+
await expect(stream.result()).resolves.toMatchObject({
216+
stopReason: "error",
217+
errorMessage: "Proxy error: 502 Bad Gateway",
218+
});
219+
expect(streamed.getReadCount()).toBeLessThan(20);
220+
});
221+
171222
it("returns an error result when EOF arrives without a terminal event", async () => {
172223
vi.stubGlobal(
173224
"fetch",

src/agents/runtime/proxy.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from "../../llm/types.js";
1616
import { EventStream } from "../../llm/utils/event-stream.js";
1717
import { parseStreamingJson } from "../../llm/utils/json-parse.js";
18+
import { readProviderJsonResponse } from "../provider-http-errors.js";
1819

1920
type StreamingToolCall = ToolCall & { partialJson?: string };
2021

@@ -181,13 +182,14 @@ export function streamProxy(
181182
if (!response.ok) {
182183
let errorMessage = `Proxy error: ${response.status} ${response.statusText}`;
183184
try {
184-
const errorData = (await response.json()) as { error?: string };
185+
const errorData = await readProviderJsonResponse<{ error?: string }>(
186+
response,
187+
"Proxy error response",
188+
);
185189
if (errorData.error) {
186190
errorMessage = `Proxy error: ${errorData.error}`;
187191
}
188-
} catch {
189-
// Couldn't parse error response
190-
}
192+
} catch {}
191193
throw new Error(errorMessage);
192194
}
193195

0 commit comments

Comments
 (0)