diff --git a/changelog.d/fixes/8990-preserve-tools-response-completed.md b/changelog.d/fixes/8990-preserve-tools-response-completed.md new file mode 100644 index 0000000000..50ce426422 --- /dev/null +++ b/changelog.d/fixes/8990-preserve-tools-response-completed.md @@ -0,0 +1 @@ +- **fix(sse):** `stripResponsesLifecycleEcho` no longer strips `tools` from the `response.completed` snapshot — that terminal event is what Codex CLI rebuilds its tool list from, so stripping it left the client with zero tools. `tools` is still stripped from `response.created`/`response.in_progress`, and `instructions` (the >100KB size lever) is still stripped from all three ([#8990](https://github.com/diegosouzapw/OmniRoute/pull/8990)) diff --git a/open-sse/utils/responsesStreamHelpers.ts b/open-sse/utils/responsesStreamHelpers.ts index 85eff8ccd4..ce40999eb8 100644 --- a/open-sse/utils/responsesStreamHelpers.ts +++ b/open-sse/utils/responsesStreamHelpers.ts @@ -201,7 +201,10 @@ export function stripResponsesLifecycleEcho(parsed: unknown): boolean { delete r.instructions; changed = true; } - if ("tools" in r) { + // Preserve tools on the terminal snapshot: response.completed is what + // Codex CLI rebuilds its tool list from (#8990). Same special-case as + // backfillResponsesCompletedOutput. Still stripped on created/in_progress. + if (obj.type !== "response.completed" && "tools" in r) { delete r.tools; changed = true; } diff --git a/tests/unit/stream-strip-responses-lifecycle-echo.test.ts b/tests/unit/stream-strip-responses-lifecycle-echo.test.ts index 3fb778f21f..e236b1c2cd 100644 --- a/tests/unit/stream-strip-responses-lifecycle-echo.test.ts +++ b/tests/unit/stream-strip-responses-lifecycle-echo.test.ts @@ -39,7 +39,10 @@ describe("stripResponsesLifecycleEcho", () => { assert.deepEqual(event.response, {}); }); - it("strips fields from response.completed (preserving usage)", () => { + it("strips instructions but PRESERVES tools on response.completed (#8990)", () => { + // response.completed is the terminal snapshot Codex CLI rebuilds its tool + // list from — stripping tools here left the client with zero tools. Same + // special-case precedent as backfillResponsesCompletedOutput. const event = { type: "response.completed", response: { @@ -54,8 +57,10 @@ describe("stripResponsesLifecycleEcho", () => { const changed = stripResponsesLifecycleEcho(event); assert.equal(changed, true); + // instructions is still stripped (the >100KB size lever, not reported broken). assert.equal("instructions" in event.response, false); - assert.equal("tools" in event.response, false); + // tools MUST survive on the terminal snapshot. + assert.deepEqual(event.response.tools, [{ name: "bash" }]); // Usage must survive — downstream tracking depends on it. assert.deepEqual(event.response.usage, { input_tokens: 100,