Merge pull request #9213 from diegosouzapw/fix/responses-usage-short-circuit

fix(responses): avoid Codex usage normalization short-circuit
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-02 10:06:12 -03:00
committed by GitHub
2 changed files with 69 additions and 2 deletions

View File

@@ -1582,12 +1582,13 @@ export function createSSEStream(options: StreamOptions = {}) {
parsed,
passthroughResponsesOutputItems
);
const usageNormalized = normalizeUsage(parsed);
if (
stripped ||
backfilled ||
textualToolCallBackfilled ||
responsesIdsNormalized ||
normalizeUsage(parsed)
usageNormalized
) {
output = `data: ${JSON.stringify(parsed)}\n\n`;
injectedUsage = true;
@@ -2282,7 +2283,9 @@ export function createSSEStream(options: StreamOptions = {}) {
const isResponses = flushedType.startsWith("response.");
const isClaude = isClaudeEventPayload(flushedParsed);
if (isResponses) {
if (normalizeResponsesSseIds(flushedParsed) || normalizeUsage(flushedParsed)) {
const idsNormalized = normalizeResponsesSseIds(flushedParsed);
const usageNormalized = normalizeUsage(flushedParsed);
if (idsNormalized || usageNormalized) {
output = `data: ${JSON.stringify(flushedParsed)}\n\n`;
}
} else if (!isClaude) {

View File

@@ -228,6 +228,42 @@ test("response.completed always includes total_tokens for strict Codex clients",
});
});
test("response.completed normalizes usage when lifecycle echoes are stripped", async () => {
const output = await readTransformed(
[
sse({
type: "response.completed",
response: {
id: "resp_agentrouter_live_shape",
status: "completed",
instructions: "echoed upstream instructions",
tools: [{ type: "function", name: "echoed_tool" }],
output: [],
usage: {
prompt_tokens: 91,
completion_tokens: 0,
input_tokens: 91,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
},
}),
],
PASSTHROUGH_RESPONSES_OPTIONS
);
const completedLine = output
.split(/\r?\n/)
.find((line) => line.startsWith("data:") && line.includes('"response.completed"'));
assert.ok(completedLine, "the terminal Responses event must be forwarded");
const completed = JSON.parse(completedLine.slice(5).trim());
assert.equal("instructions" in completed.response, false);
assert.equal("tools" in completed.response, false);
assert.equal(completed.response.usage.total_tokens, 91);
});
test("response.completed synthesizes zero usage when upstream omits usage", async () => {
const completed = {
type: "response.completed",
@@ -249,6 +285,34 @@ test("response.completed synthesizes zero usage when upstream omits usage", asyn
});
});
test("buffered response.completed normalizes IDs and usage independently", async () => {
const completed = {
type: "response.completed",
response: {
id: 12345,
status: "completed",
output: [],
usage: { input_tokens: 12, output_tokens: 3 },
},
};
const output = await readTransformed(
[`data: ${JSON.stringify(completed)}`],
PASSTHROUGH_RESPONSES_OPTIONS
);
const completedLine = output
.split(/\r?\n/)
.find((line) => line.startsWith("data:") && line.includes('"response.completed"'));
assert.ok(completedLine, "the buffered terminal Responses event must be forwarded");
const forwarded = JSON.parse(completedLine.slice(5).trim());
assert.equal(forwarded.response.id, "12345");
assert.deepEqual(forwarded.response.usage, {
input_tokens: 12,
output_tokens: 3,
total_tokens: 15,
});
});
test("Claude to Responses translation includes canonical Codex usage", async () => {
const output = await readTransformed(
[