Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,6 @@ export function startServer(port?: number, deps: StartServerDeps = {}) {
}

if (url.pathname === "/v1/responses" && req.method === "POST") {
disableResponsesRequestTimeout(req, requestServer);
if (isDraining()) {
return drainingResponse(req);
}
Expand Down Expand Up @@ -901,6 +900,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}) {
return runAdmittedHttpTurn(req, async turnAdmissionLease => {
const response = await handleResponses(req, config, logCtx, {
turnAdmissionLease,
onRequestBodyRead: () => disableResponsesRequestTimeout(req, requestServer),
abortSignal: req.signal,
onFirstOutput: () => recordFirstOutput(logCtx, start),
onNativePassthroughTerminal: status => {
Expand Down
3 changes: 3 additions & 0 deletions src/server/responses/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ export interface ConsumedComboFailure {

export interface HandleResponsesOptions {
turnAdmissionLease?: AdmissionLease;
/** Called only after the complete inbound body has been read and parsed successfully. */
onRequestBodyRead?: () => void;
forceEmptyResponseId?: boolean;
abortSignal?: AbortSignal;
/** One-shot TTFT callback: first non-empty model output observed (WP4). */
Expand Down Expand Up @@ -1366,6 +1368,7 @@ async function handleResponsesInner(
}
return formatErrorResponse(400, "invalid_request_error", err instanceof Error ? err.message : String(err));
}
options.onRequestBodyRead?.();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 20 \
  'handleComboResponses|comboIdFromRawBody|comboAttempt|onRequestBodyRead' \
  src/server/responses/core.ts

rg -n -C 10 \
  'onRequestBodyRead|handleResponses\(' \
  src/server/index.ts tests/server-auth.test.ts

Repository: luvs01/opencodex

Length of output: 30248


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- handleComboResponses implementation ---'
sed -n '974,1235p' src/server/responses/core.ts

printf '%s\n' '--- callback gate definition and usages ---'
rg -n -C 20 'createChildPassthroughCallbackGate|onRequestBodyRead' src/server/responses/core.ts

printf '%s\n' '--- combo-related tests and callback assertions ---'
rg -n -C 12 'combo|onRequestBodyRead|timeout' tests src/server | head -n 500

Repository: luvs01/opencodex

Length of output: 50372


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- handleResponses entry and callback propagation ---'
rg -n -C 18 '^export async function handleResponses|onRequestBodyRead' src/server/responses/core.ts

printf '%s\n' '--- focused combo test files ---'
git ls-files 'tests/*' | while IFS= read -r file; do
  if rg -q 'handleComboResponses|comboAttempt|comboIdFromRawBody|combos:' "$file"; then
    printf '%s\n' "$file"
  fi
done

printf '%s\n' '--- callback-related test locations ---'
rg -n -C 8 'onRequestBodyRead' tests src/server/index.ts

Repository: luvs01/opencodex

Length of output: 8382


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- timeout callback implementation ---'
rg -n -C 12 'function disableResponsesRequestTimeout|disableResponsesRequestTimeout' src/server

printf '%s\n' '--- combo failover test structure ---'
rg -n -C 8 'describe|test\(|failover|retry|targets' tests/server-combo-failover-e2e.test.ts | head -n 240

printf '%s\n' '--- read-only source verifier ---'
python3 - <<'PY'
from pathlib import Path

source = Path("src/server/responses/core.ts").read_text()
combo_branch = "if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {\n    return handleComboResponses(req, body, comboId, config, logCtx, options);"
callback_call = "options.onRequestBodyRead?.();"
child_forward = "...options,"
child_guard = "comboAttempt: true,"

assert combo_branch in source
assert callback_call in source
assert source.index(combo_branch) < source.index(callback_call)
combo_start = source.index("export async function handleComboResponses(")
combo_end = source.index("\n\n\nfunction finalizeOwnedTranslatorBudget", combo_start)
combo = source[combo_start:combo_end]
assert child_forward in combo and child_guard in combo
assert callback_call not in combo

print("combo branch returns before the outer callback")
print("combo child forwards the parent options and invokes the shared callback in handleResponsesInner")
print("no combo-specific one-shot guard exists")
PY

Repository: luvs01/opencodex

Length of output: 26032


🏁 Script executed:

#!/usr/bin/env bash
set -u

python3 - <<'PY'
from pathlib import Path

source = Path("src/server/responses/core.ts").read_text()
combo_branch = """if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {
    return handleComboResponses(req, body, comboId, config, logCtx, options);"""
callback_call = "options.onRequestBodyRead?.();"
combo_start = source.index("export async function handleComboResponses(")
combo_end = source.index("\n\n\nfunction finalizeOwnedTranslatorBudget", combo_start)
combo = source[combo_start:combo_end]

checks = {
    "combo branch returns before outer callback": source.index(combo_branch) < source.index(callback_call),
    "combo forwards parent options to child": "...options," in combo,
    "child marks recursive call as combo attempt": "comboAttempt: true," in combo,
    "combo handler has no direct callback call": callback_call not in combo,
}
for name, value in checks.items():
    print(f"{name}: {value}")
assert all(checks.values())

print("Conclusion: each successfully parsed child reaches the shared callback, so a two-target failover can invoke it twice.")
PY

Repository: luvs01/opencodex

Length of output: 464


Invoke onRequestBodyRead once for the original combo request. In src/server/responses/core.ts:1320-1323, the combo path returns before line 1371. handleComboResponses forwards ...options to each child at lines 1097-1114, so each parsed child invokes the callback. A two-target failover can invoke it twice. Call it once after validating the original body, then omit it from child options or guard it as one-shot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/responses/core.ts` at line 1371, Update the combo request flow
around handleComboResponses so options.onRequestBodyRead?.() is invoked exactly
once after validating the original request body, before returning or dispatching
child requests. Remove or guard onRequestBodyRead when forwarding options to
parsed child requests, preserving the callback behavior for non-combo requests.

// Prefer a pre-populated id (routed Claude) over Responses headers that may be
// absent or synthetically injected (session_id from prompt_cache_key).
if (!logCtx.conversationId) {
Expand Down
35 changes: 35 additions & 0 deletions tests/server-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from "../src/server";
import { clearRequestLogsForTests, getRequestLogEntries } from "../src/server/request-log";
import { handleManagementAPI } from "../src/server/management-api";
import { handleResponses } from "../src/server/responses";
import type { OcxConfig } from "../src/types";
import { fakeChatGptJwt } from "./helpers/fake-chatgpt-jwt";
import { installIsolatedCodexHome, type IsolatedCodexHome } from "./helpers/isolated-codex-home";
Expand Down Expand Up @@ -345,6 +346,40 @@ describe("server local API auth", () => {
})).toBe(false);
});

test("responses handler keeps the request timeout until the body is fully parsed", async () => {
let controller!: ReadableStreamDefaultController<Uint8Array>;
const body = new ReadableStream<Uint8Array>({
start(value) {
controller = value;
},
});
const req = new Request("http://localhost/v1/responses", {
method: "POST",
headers: { "content-type": "application/json" },
body,
});
let bodyRead = false;
const responsePromise = handleResponses(req, config(), {
model: "unknown",
provider: "unknown",
}, {
onRequestBodyRead: () => {
bodyRead = true;
},
});

controller.enqueue(new TextEncoder().encode('{"model":"missing/provider","input":"hello"'));
await Bun.sleep(10);
expect(bodyRead).toBe(false);

controller.enqueue(new TextEncoder().encode("}"));
controller.close();
const response = await responsePromise;
expect(bodyRead).toBe(true);
expect(response.status).toBeGreaterThanOrEqual(400);
expect(response.status).toBeLessThan(500);
});

test("loopback hostnames do not require opencodex API auth", () => {
expect(isLoopbackHostname(undefined)).toBe(true);
expect(isLoopbackHostname("")).toBe(true);
Expand Down
Loading