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: 4 additions & 1 deletion docs-site/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,10 @@ Outbound capture covers adapters that send through the shared upstream fetch hel
capture covers `/v1/responses`, `/v1/messages`, `/v1/chat/completions`, and `/v1/responses/compact`.
Proxy response-cache hits are traced without duplicating the cached response body: the usage trace records
`cacheHit`, a response hash/byte count, and the request id that originally populated the cache when known.
WebSocket/live/realtime traffic is not yet covered.
Responses WebSocket `response.create` turns are traced as separate logical requests: the inbound hash covers
the client WS frame, while the outbound hash covers the final provider wire body. Voice/realtime live-sideband
WebSockets remain outside this trace lane because they are long-lived bidirectional sessions rather than
request/response turns.

Stored traces can be inspected directly from the local SQLite store; the proxy does not need to be running:

Expand Down
35 changes: 21 additions & 14 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1678,11 +1678,10 @@ export function startServer(port?: number) {
ws.close(1009, "message too large");
return;
}
const rawText = typeof raw === "string" ? raw : raw.toString();
let frame: Record<string, unknown>;
try {
frame = JSON.parse(
typeof raw === "string" ? raw : raw.toString(),
) as Record<string, unknown>;
frame = JSON.parse(rawText) as Record<string, unknown>;
} catch {
return; // text-only contract; ignore unparseable frames
}
Expand Down Expand Up @@ -1740,24 +1739,32 @@ export function startServer(port?: number) {
headers: fwd,
body: JSON.stringify({ ...payload, stream: true }),
});
const traceReq = new Request("http://localhost/v1/responses", {
method: "POST",
headers: { "content-type": "application/json" },
body: rawText,
});
await beginTrace(logCtx, traceReq);
try {
let terminalRecorder:
| ((
status: ResponsesTerminalStatus,
httpStatusOverride?: number,
) => void)
| undefined;
const response = await handleResponses(req, config, logCtx, {
forceEmptyResponseId: true,
abortSignal: turnAbort.signal,
onFirstOutput: () => recordFirstOutput(logCtx, start),
onCodexAuthContextResolved: (context) =>
updateCodexWebSocketAuthContext(ws, context),
recordTerminalOutcomes: false,
setTerminalOutcomeRecorder: (recorder) => {
terminalRecorder = recorder;
},
});
const response = await runWithTrace(logCtx, () =>
handleResponses(req, config, logCtx, {
forceEmptyResponseId: true,
abortSignal: turnAbort.signal,
onFirstOutput: () => recordFirstOutput(logCtx, start),
onCodexAuthContextResolved: (context) =>
updateCodexWebSocketAuthContext(ws, context),
recordTerminalOutcomes: false,
setTerminalOutcomeRecorder: (recorder) => {
terminalRecorder = recorder;
},
}),
);
await sendResponseToWebSocket(ws, response, isCurrent, {
onSsePayload: (payload) =>
inspectResponseLogSsePayload(logCtx, payload),
Expand Down
12 changes: 12 additions & 0 deletions tests/openai-provider-option-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ describe("OpenAI provider-option integration spine", () => {
OPENCODEX_HOME: process.env.OPENCODEX_HOME,
CODEX_HOME: process.env.CODEX_HOME,
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
OCX_TRACE: process.env.OCX_TRACE,
};
const savedFetch = globalThis.fetch;
const captures: Capture[] = [];
Expand Down Expand Up @@ -154,6 +155,7 @@ describe("OpenAI provider-option integration spine", () => {
process.env.OPENCODEX_HOME = opencodexHome;
process.env.CODEX_HOME = codexHome;
process.env.CLAUDE_CONFIG_DIR = claudeConfigDir;
process.env.OCX_TRACE = "metadata";
const authPath = join(codexHome, "auth.json");
writeFileSync(authPath, JSON.stringify({
tokens: { access_token: "fixture-main-access", account_id: "fixture-main-account" },
Expand Down Expand Up @@ -361,6 +363,15 @@ describe("OpenAI provider-option integration spine", () => {
accountId: "fixture-pool-account",
body: { model: "gpt-5.6-sol" },
});
const wsTrace = requestLog.getRequestLogEntries().at(-1)?.trace;
expect(wsTrace).toMatchObject({
mode: "metadata",
stored: false,
responseBytes: expect.any(Number),
});
expect(wsTrace?.requestHash).toMatch(/^[0-9a-f]{32}$/);
expect(wsTrace?.outboundHash).toMatch(/^[0-9a-f]{32}$/);
expect(wsTrace?.responseHash).toMatch(/^[0-9a-f]{32}$/);
expect(websocketRegistry.getTrackedCodexWebSocketCountForAccount("fixture-pool")).toBe(1);

const directPatch = await patchMode("direct");
Expand Down Expand Up @@ -581,6 +592,7 @@ describe("OpenAI provider-option integration spine", () => {
restoreEnv("OPENCODEX_HOME", previousEnv.OPENCODEX_HOME);
restoreEnv("CODEX_HOME", previousEnv.CODEX_HOME);
restoreEnv("CLAUDE_CONFIG_DIR", previousEnv.CLAUDE_CONFIG_DIR);
restoreEnv("OCX_TRACE", previousEnv.OCX_TRACE);
rmSync(root, { recursive: true, force: true });
expect(hashTree(realClaudeDir)).toBe(realClaudeHashBefore);
}
Expand Down
2 changes: 2 additions & 0 deletions tests/ws-endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ describe("WS endpoint re-framer (120/132)", () => {
expect(source).toContain(
"finalizeLog(turnAbort.signal.aborted ? 499 : response.status);",
);
expect(source).toContain("await beginTrace(logCtx, traceReq);");
expect(source).toContain("const response = await runWithTrace(logCtx, () =>");
});

test("generate=false warmup completes locally without upstream and forces full next request", () => {
Expand Down
Loading