Skip to content

Commit 78ca706

Browse files
committed
fix: abort web-search loop provider fetches
1 parent 41f4378 commit 78ca706

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/web-search/loop.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
130130
const request = adapter.buildRequest(iterParsed, { headers: incomingHeaders });
131131
let resp: Response;
132132
try {
133-
resp = await fetch(request.url, { method: request.method, headers: request.headers, body: request.body });
133+
resp = await fetch(request.url, {
134+
method: request.method,
135+
headers: request.headers,
136+
body: request.body,
137+
signal: abortSignal,
138+
});
134139
} catch (e) {
135140
return jsonError(502, `Provider unreachable: ${e instanceof Error ? e.message : String(e)}`);
136141
}

tests/sidecar-abort.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { afterEach, describe, expect, test } from "bun:test";
22
import { runWebSearch } from "../src/web-search/executor";
3+
import { runWithWebSearch } from "../src/web-search/loop";
34
import { describeImage } from "../src/vision/describe";
5+
import { parseRequest } from "../src/responses/parser";
6+
import type { ProviderAdapter } from "../src/adapters/base";
47
import type { OcxProviderConfig } from "../src/types";
58

69
const originalFetch = globalThis.fetch;
@@ -30,6 +33,39 @@ function installAbortAwareFetch(): () => AbortSignal {
3033
}
3134

3235
describe("sidecar abort propagation", () => {
36+
test("web-search loop routed-provider fetch observes the WebSocket turn abort signal", async () => {
37+
const getSignal = installAbortAwareFetch();
38+
const turn = new AbortController();
39+
const adapter: ProviderAdapter = {
40+
name: "mock",
41+
buildRequest: () => ({ url: "https://routed.test/v1/chat/completions", method: "POST", headers: {}, body: "{}" }),
42+
async *parseStream() { /* unused */ },
43+
async parseResponse() { return []; },
44+
};
45+
const response = runWithWebSearch({
46+
parsed: parseRequest({
47+
model: "routed/model",
48+
input: "Search for current docs",
49+
stream: true,
50+
tools: [{ type: "web_search" }],
51+
}),
52+
adapter,
53+
forwardProvider,
54+
hostedTool: { type: "web_search" },
55+
incomingHeaders: new Headers({ authorization: "Bearer token" }),
56+
settings: { model: "gpt-5.4-mini", reasoning: "low", timeoutMs: 30_000 },
57+
maxSearches: 1,
58+
abortSignal: turn.signal,
59+
});
60+
61+
const signal = getSignal();
62+
expect(signal).toBe(turn.signal);
63+
expect(signal.aborted).toBe(false);
64+
turn.abort("replacement turn");
65+
expect(signal.aborted).toBe(true);
66+
expect((await response).status).toBe(502);
67+
});
68+
3369
test("web-search sidecar fetch observes the WebSocket turn abort signal", async () => {
3470
const getSignal = installAbortAwareFetch();
3571
const turn = new AbortController();

0 commit comments

Comments
 (0)