diff --git a/.changeset/codex-store-false.md b/.changeset/codex-store-false.md new file mode 100644 index 0000000000..846d38a053 --- /dev/null +++ b/.changeset/codex-store-false.md @@ -0,0 +1,5 @@ +--- +'@electric-ax/agents': patch +--- + +Stop forcing `store: true` on OpenAI Codex reasoning payloads. The ChatGPT-login Codex endpoint is stateless-only and rejects stateful requests with `{"detail":"Store must be set to false"}`, which broke every Codex (`gpt-5.x`) run. The stateful default now applies to the regular OpenAI Responses API only. diff --git a/packages/agents/src/model-catalog.ts b/packages/agents/src/model-catalog.ts index e1b74cc5d5..5346e3d05e 100644 --- a/packages/agents/src/model-catalog.ts +++ b/packages/agents/src/model-catalog.ts @@ -270,8 +270,12 @@ function withProviderPayloadDefaults( // OpenAI Responses reasoning/tool-call continuations replay rs_* // reasoning items. With store:false, OpenAI does not persist those // items server-side, which can make follow-up requests fail with - // "Item with id ... not found". Keep Responses stateful for built-ins. - store: true, + // "Item with id ... not found". Keep Responses stateful for the + // regular API. The ChatGPT-login Codex endpoint is the opposite: + // it is stateless-only and rejects any stateful request with + // `{"detail":"Store must be set to false"}`, so it must keep the + // upstream store:false default. + ...(choice.provider === `openai` && { store: true }), reasoning: { ...existingReasoning, effort, diff --git a/packages/agents/test/model-catalog.test.ts b/packages/agents/test/model-catalog.test.ts index ec1f3f9a57..6c56fd93ef 100644 --- a/packages/agents/test/model-catalog.test.ts +++ b/packages/agents/test/model-catalog.test.ts @@ -191,6 +191,38 @@ describe(`model catalog`, () => { expect(deepseekConfig.onPayload).toBeUndefined() }) + it(`keeps Codex reasoning model payloads stateless`, async () => { + // The ChatGPT-login Codex endpoint rejects stateful requests with + // `{"detail":"Store must be set to false"}` — the openai branch's + // store:true override must not leak into openai-codex payloads. + delete process.env.OPENAI_API_KEY + process.env.ELECTRIC_CODEX_ACCESS_TOKEN = `test-codex-token` + + const catalog = await createBuiltinModelCatalog() + const config = resolveBuiltinModelConfig(catalog!, { + model: `openai-codex:gpt-5.4`, + }) + + expect(config).toMatchObject({ + provider: `openai-codex`, + model: `gpt-5.4`, + }) + expect(config.onPayload).toBeTypeOf(`function`) + + const payload = config.onPayload!( + { store: false, reasoning: { effort: `none` } }, + {} as any + ) + + expect(payload).toEqual({ + store: false, + reasoning: { effort: `low` }, + }) + expect( + config.onPayload!({ reasoning: { effort: `none` } }, {} as any) + ).not.toHaveProperty(`store`) + }) + it(`does not expose providers whose keys are rejected`, async () => { vi.stubGlobal( `fetch`,