Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/server/responses/codex-ws-wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ export type CodexWsStageRecord = Omit<CodexWsFailureStage, "requestBytes"> & {
const codexWsStageByResponse = new WeakMap<Response, CodexWsStageRecord>();

export function markCodexWsStage(response: Response, record: CodexWsStageRecord): void {
const current = codexWsStageByResponse.get(response);
if (current) {
Object.assign(current, record);
return;
}
codexWsStageByResponse.set(response, record);
}

Expand Down
2 changes: 1 addition & 1 deletion structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ caller's abort signal, so a `connectTimeoutMs` shorter than 90 seconds cancels
an already-sent create before the prelude timer fires.
These are transport-fidelity guarantees, not a provider-billing guarantee.

Every exchange also leaves a content-free stage record (`CodexWsStageRecord`, #4191): create-frame bytes (measured on failure only — the committed-success record keeps it null so the happy path never byte-counts a megabyte replay frame), send completion, numeric close code, elapsed and first-frame durations, frame counters, liveness ping/pong counts, pool reuse, and the OCX/Bun versions. The exchange pins the record on the resolved Response (`markCodexWsStage`, the same marker seam as `markCodexWsResponse`); `handleResponses` adopts it onto the serving attempt, and usage.jsonl persists it per attempt behind a drop-guard normalizer, so hand-edited rows cannot inject strings into the DTO. The record never carries conversation text, headers, close-reason text, or account identifiers, and it is not a fallback-eligibility signal: the no-replay-after-send contract stands regardless of what it says.
Every exchange also leaves a content-free stage record (`CodexWsStageRecord`, #4191): create-frame bytes (measured on failure only — the committed-success record keeps it null so the happy path never byte-counts a megabyte replay frame), send completion, numeric close code, elapsed and first-frame durations, frame counters, liveness ping/pong counts, pool reuse, and the OCX/Bun versions. The exchange pins the record on the resolved Response (`markCodexWsStage`, the same marker seam as `markCodexWsResponse`); later stream settlements finalize that same object in place so the reference `handleResponses` adopted onto the serving attempt cannot retain commit-time counters. usage.jsonl persists it per attempt behind a drop-guard normalizer, so hand-edited rows cannot inject strings into the DTO. The record never carries conversation text, headers, close-reason text, or account identifiers, and it is not a fallback-eligibility signal: the no-replay-after-send contract stands regardless of what it says.

Eligible complete-input creates can retain a canonical upstream socket within
one selected account, credential, thread and turn. Model/tier and immutable
Expand Down
18 changes: 18 additions & 0 deletions tests/responses/ws-failure-stage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,24 @@ describe("codex ws stage record marker (#4191)", () => {
expect(readCodexWsStage(response)).toEqual(stage);
});

test("later stage updates finalize the record already adopted by logging", () => {
const response = new Response("ok");
markCodexWsStage(response, stage);
const adopted = readCodexWsStage(response);
const finalStage = {
...stage,
requestBytes: 4321,
upstreamFrames: 5,
relayedEvents: 4,
elapsedMs: 1200,
};

markCodexWsStage(response, finalStage);

expect(readCodexWsStage(response)).toBe(adopted);
expect(adopted).toEqual(finalStage);
});

test("a committed exchange ends with the final counters on its stage record", async () => {
installFake(ws => {
ws.emit("open", {});
Expand Down
Loading