diff --git a/src/vision/describe.ts b/src/vision/describe.ts index 68744fd906..179fda4a5d 100644 --- a/src/vision/describe.ts +++ b/src/vision/describe.ts @@ -114,8 +114,9 @@ export async function describeImage( if (!parsed.text.trim() && parsed.error) return { text: "", error: parsed.error }; return { text: parsed.text }; } catch (e) { - recordOutcome?.(e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error"); + const callerAborted = abortSignal?.aborted && linkedSignal.signal.reason === abortSignal.reason; const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error"; + if (!callerAborted) recordOutcome?.(kind); console.warn(`[vision] sidecar ${kind} (${Date.now() - t0}ms)`); return { text: "", error: e instanceof Error ? e.message : String(e) }; } finally { diff --git a/src/web-search/executor.ts b/src/web-search/executor.ts index 290e1883b0..1cf76984fb 100644 --- a/src/web-search/executor.ts +++ b/src/web-search/executor.ts @@ -94,8 +94,9 @@ export async function runWebSearch( detachBodyGuard(); } } catch (e) { - recordOutcome?.(e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error"); + const callerAborted = abortSignal?.aborted && linkedSignal.signal.reason === abortSignal.reason; const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error"; + if (!callerAborted) recordOutcome?.(kind); console.warn(`[web-search] sidecar ${kind} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`); return { text: "", sources: [], error: e instanceof Error ? e.message : String(e) }; } finally { diff --git a/tests/sidecar-abort.test.ts b/tests/sidecar-abort.test.ts index 2aa9df394a..79749fe139 100644 --- a/tests/sidecar-abort.test.ts +++ b/tests/sidecar-abort.test.ts @@ -97,6 +97,7 @@ describe("sidecar abort propagation", () => { test("web-search sidecar fetch observes the WebSocket turn abort signal", async () => { const getSignal = installAbortAwareFetch(); const turn = new AbortController(); + const recorded: unknown[] = []; const outcome = runWebSearch( "current docs", { type: "web_search" }, @@ -104,6 +105,7 @@ describe("sidecar abort propagation", () => { new Headers({ authorization: "Bearer token" }), { model: "gpt-5.4-mini", reasoning: "low", timeoutMs: 30_000 }, turn.signal, + value => recorded.push(value), ); const signal = getSignal(); @@ -111,6 +113,7 @@ describe("sidecar abort propagation", () => { turn.abort("replacement turn"); expect(signal.aborted).toBe(true); expect((await outcome).error).toBe("aborted by turn"); + expect(recorded).toEqual([]); }); test("web-search sidecar records HTTP and connect outcomes", async () => { @@ -213,6 +216,7 @@ describe("sidecar abort propagation", () => { test("vision sidecar fetch observes the WebSocket turn abort signal", async () => { const getSignal = installAbortAwareFetch(); const turn = new AbortController(); + const recorded: unknown[] = []; const outcome = describeImage( "data:image/png;base64,iVBORw0KGgo=", "high", @@ -221,6 +225,7 @@ describe("sidecar abort propagation", () => { new Headers({ authorization: "Bearer token" }), { model: "gpt-5.4-mini", timeoutMs: 30_000 }, turn.signal, + value => recorded.push(value), ); const signal = getSignal(); @@ -228,6 +233,7 @@ describe("sidecar abort propagation", () => { turn.abort("replacement turn"); expect(signal.aborted).toBe(true); expect((await outcome).error).toBe("aborted by turn"); + expect(recorded).toEqual([]); }); test("vision sidecar records HTTP and connect outcomes", async () => {