diff --git a/README.md b/README.md index 1e2d2da..128db2a 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ OpenAI Codex ("Login with ChatGPT") for `@intx/inference`: the OAuth client conf ## Why @corbits/codex-provider? 1. **Inference on a ChatGPT subscription.** Codex has no API-key path. This package supplies the PKCE login config, the code exchange and refresh, and the `chatgpt-account-id` the backend requires on every request. -2. **The exact request Codex accepts.** `store: false`, `parallel_tool_calls: false`, no `max_output_tokens`, the host prompt as a leading `developer` message, and the Codex CLI's `originator` header are fixed in the adapter, not left to each host. +2. **The exact request Codex accepts.** `store: false`, `parallel_tool_calls: true`, no `max_output_tokens`, the host prompt as a leading `developer` message, and the Codex CLI's `originator` header are fixed in the adapter, not left to each host. 3. **Backend bugs repaired, host identity required.** `withCodexContentTypeRepair` restores the `content-type` the backend drops on some SSE streams. The host names itself through `CodexQuirks`; there is no default product name. ## Install ```bash -bun add @corbits/codex-provider @corbits/oauth-core@^0.2.0 @corbits/openai-responses@^0.2.0 @intx/inference@^0.4.0 @intx/types@^0.4.0 +bun add @corbits/codex-provider @corbits/oauth-core@^0.2.0 @corbits/openai-responses@^0.2.4 @intx/inference@^0.4.0 @intx/types@^0.4.0 ``` Runs on Bun >= 1.2 or Node >= 24. diff --git a/bun.lock b/bun.lock index 57131be..5846a32 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,7 @@ }, "devDependencies": { "@corbits/oauth-core": "0.3.4", - "@corbits/openai-responses": "0.2.3", + "@corbits/openai-responses": "0.2.4", "@intx/inference": "0.5.0", "@intx/types": "0.5.0", "@types/bun": "1.3.14", @@ -19,7 +19,7 @@ }, "peerDependencies": { "@corbits/oauth-core": "^0.3.4", - "@corbits/openai-responses": "^0.2.3", + "@corbits/openai-responses": "^0.2.4", "@intx/inference": "^0.5.0", "@intx/types": "^0.5.0", }, @@ -50,7 +50,7 @@ "@corbits/oauth-core": ["@corbits/oauth-core@0.3.4", "", { "dependencies": { "@intx/db": "^0.5.0", "@intx/hub-api": "^0.5.0", "@intx/hub-common": "^0.5.0", "@intx/types": "^0.5.0", "arktype": "^2.2.3", "drizzle-orm": "^0.45.2", "hono": "^4.11.9" } }, "sha512-cUVjfvEAM6v35dacR8kDqYj2dzAbCjB+0CjJv/t54b5NR6DykNYkfsR6KfQfj/oSf994mrY81dwQ1+Who1z5yg=="], - "@corbits/openai-responses": ["@corbits/openai-responses@0.2.3", "", { "dependencies": { "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": "^0.5.0", "@intx/types": "^0.5.0" } }, "sha512-9msL1hy3jINpJsdbv2+EZsjVvKIZ8lSO7oJcwKSyphog87U+fePhQtsBq/HNJbKY+zQeHYoaL2MbuYGo/aMIfA=="], + "@corbits/openai-responses": ["@corbits/openai-responses@0.2.4", "", { "dependencies": { "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": "^0.5.0", "@intx/types": "^0.5.0" } }, "sha512-RnSVsqyzYMGhkELrp6NK5CzAMNAgT55Vh0XexUvSw92kq9QoCKUvgJHlpC1nF6LXbaEOIjAkhDZURl/kROBI4Q=="], "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], diff --git a/package.json b/package.json index ae74dcc..fa9ea5c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ }, "devDependencies": { "@corbits/oauth-core": "0.3.4", - "@corbits/openai-responses": "0.2.3", + "@corbits/openai-responses": "0.2.4", "@intx/inference": "0.5.0", "@intx/types": "0.5.0", "@types/bun": "1.3.14", @@ -63,7 +63,7 @@ }, "peerDependencies": { "@corbits/oauth-core": "^0.3.4", - "@corbits/openai-responses": "^0.2.3", + "@corbits/openai-responses": "^0.2.4", "@intx/inference": "^0.5.0", "@intx/types": "^0.5.0" }, diff --git a/src/responses-adapter.test.ts b/src/responses-adapter.test.ts index eecc70c..6410b53 100644 --- a/src/responses-adapter.test.ts +++ b/src/responses-adapter.test.ts @@ -39,8 +39,8 @@ function parseCodexBody(rawBody: string) { // chatgpt-account-id header only rides when the option is set, session_id // rides in both the header and the body, `instructions` is omitted (the // host's own system prompt leads `input` as a wrapped developer message), -// and the three fields the backend requires explicit values for (`store`, -// `parallel_tool_calls`, `max_output_tokens`) are on the wire correctly. +// `store` and `max_output_tokens` are set as the backend requires, and +// `parallel_tool_calls` is on. describe("Codex responses adapter — request shape", () => { test("throws when the quirks bag is absent — there is no honest default product name", () => { expect(() => createCodexResponsesAdapter(source, undefined)).toThrow(); @@ -104,13 +104,13 @@ describe("Codex responses adapter — request shape", () => { expect(bridgeText).toContain("operate carefully"); }); - test("always sends store:false and parallel_tool_calls:false, and omits max_output_tokens", () => { + test("always sends store:false and parallel_tool_calls:true, and omits max_output_tokens", () => { const adapter = createCodexResponsesAdapter(source, quirks); const request = adapter.buildRequest(turns, "gpt-5.5", { maxTokens: 4096 }); const body = parseCodexBody(request.body); expect(body.store).toBe(false); - expect(body.parallel_tool_calls).toBe(false); + expect(body.parallel_tool_calls).toBe(true); expect(body.max_output_tokens).toBeUndefined(); }); diff --git a/src/responses-adapter.ts b/src/responses-adapter.ts index 7bbc453..8e95f05 100644 --- a/src/responses-adapter.ts +++ b/src/responses-adapter.ts @@ -15,8 +15,8 @@ import { import { wrapCodexBridgeMessage } from "./instructions.js"; import { parseCodexQuirks, type CodexQuirks } from "./quirks.js"; -// `store` and `parallelToolCalls` need an explicit `false` on the wire, and -// `maxOutputTokens: false` because the backend 400s on `max_output_tokens`. +// `store` needs an explicit `false` on the wire, and `maxOutputTokens: false` +// omits `max_output_tokens`, which the backend 400s on. function codexResponsesQuirks(quirks: CodexQuirks): ResponsesQuirks { return { path: CODEX_RESPONSES_PATH, @@ -37,7 +37,7 @@ function codexResponsesQuirks(quirks: CodexQuirks): ResponsesQuirks { systemPrompt: { role: "developer", shape: "parts" }, contentShape: "typed", store: false, - parallelToolCalls: false, + parallelToolCalls: true, maxOutputTokens: false, reasoning: { effortOption: CODEX_REASONING_EFFORT_OPTION,