Skip to content
Merged
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 docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ Positional arguments after flags are joined into the optional initial task deliv

- OpenAI-compatible chat completions, streamed via `@intx/inference`
- JSON-schema tool definitions for director-layer tools (`ask_operator`, `present`, `submit_output`) and agent tools (`manage_tasks`, `tool_search`, `use_skill`, `search_agents`, …)
- Codex Responses (`@corbits/codex-provider` via `codex-responses.ts`): ChatGPT Codex is Responses-only. The host system prompt is wrapped with product identity as a leading `developer` input item; conversation turns follow in order. There is no official-prompt fetch, cache, or startup refresh. It requires `store: false` (`store: true` → 400) and rejects `previous_response_id`. Multi-turn continuity is full `input` replay; encrypted reasoning captured via `include: ["reasoning.encrypted_content"]` is resent as a `reasoning` item. `prompt_cache_key` (session id) is the cache-routing signal. `parallel_tool_calls` is sent `false` (serial at this surface); the reactor already fans out a multi-call batch concurrently. `max_output_tokens` is omitted (backend rejects it).
- Codex Responses (`@corbits/codex-provider` via `codex-responses.ts`): ChatGPT Codex is Responses-only. The host system prompt is wrapped with product identity as a leading `developer` input item; conversation turns follow in order. There is no official-prompt fetch, cache, or startup refresh. It requires `store: false` (`store: true` → 400) and rejects `previous_response_id`. Multi-turn continuity is full `input` replay; encrypted reasoning captured via `include: ["reasoning.encrypted_content"]` is resent as a `reasoning` item. `prompt_cache_key` (session id) is the cache-routing signal. `parallel_tool_calls` is sent `true` (CL-7420 live probe: backend 200s and echoes it true; the packaged adapter still pins `false`, so the host rewrites it on the wire); the reactor already fans out a multi-call batch concurrently. `max_output_tokens` is omitted (backend rejects it).

### State Persistence

Expand Down
13 changes: 13 additions & 0 deletions src/provider/codex-responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ describe("createCodexResponsesAdapter", () => {
expect(body.prompt_cache_key).toBe("sess-1");
});

test("sends parallel_tool_calls:true on the wire (CL-7420)", () => {
const adapter = createCodexResponsesAdapter(source);
const request = adapter.buildRequest(
[userTurn("hello")],
"gpt-5.1-codex",
{},
);
const body = JSON.parse(request.body) as {
parallel_tool_calls?: unknown;
};
expect(body.parallel_tool_calls).toBe(true);
});

test("wraps the system prompt with host product identity as a developer item", () => {
const adapter = createCodexResponsesAdapter(source);
const request = adapter.buildRequest([userTurn("x")], "gpt-5.1-codex", {
Expand Down
20 changes: 19 additions & 1 deletion src/provider/codex-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,27 @@ function withHostReasoningEffort(adapter: ProviderAdapter): ProviderAdapter {
};
}

// CL-7420 probe: the ChatGPT Codex backend accepts parallel_tool_calls:true
// (HTTP 200 on POST /codex/responses, echoed true in response.created). The
// packaged adapter still pins false, so the host rewrites the field on the
// wire; the reactor already fans out a multi-call batch concurrently.
function withParallelToolCalls(adapter: ProviderAdapter): ProviderAdapter {
return {
...adapter,
buildRequest: (messages, model, options) => {
const request = adapter.buildRequest(messages, model, options);
const body = JSON.parse(request.body) as Record<string, unknown>;
body["parallel_tool_calls"] = true;
return { ...request, body: JSON.stringify(body) };
},
};
}

// CodexQuirks are required by the package factory; Responses option keys are
// not part of that bag, so host reasoning_effort is aliased at buildRequest.
export const createCodexResponsesAdapter: AdapterFactory = (source) =>
withHostReasoningEffort(
createPackageCodexResponsesAdapter(source, CODEX_QUIRKS),
withParallelToolCalls(
createPackageCodexResponsesAdapter(source, CODEX_QUIRKS),
),
);
Loading