diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 882ce5609..9d1b49309 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -28,6 +28,7 @@ - Fixed pi-messages streams accepting a `contentIndex` that is not the next or an existing content block, such as `__proto__` or a huge index - Fixed `pi-ai` CLI auth file being readable by other users. It is now written with mode `0600` - Fixed OpenCode DeepSeek V4.1 Flash sending `prompt_cache_retention`, which the OpenCode gateway rejects, with long cache retention +- Fixed GPT-5.4 and GPT-5.5 failing with `Missing namespace for function_call` after switching from another model that called tools. On the OpenAI and OpenCode providers they now declare their tools in the top-level tool list instead of loading them through `additional_tools` or tool search ([#11415](https://github.com/earendil-works/pi/issues/11415)) ## [1.4.0] - 2026-10-21 diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index b556d2505..966604358 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -366,11 +366,10 @@ const ANT_LING_RING_THINKING_LEVEL_MAP = { const BEDROCK_INFERENCE_PROFILE_ONLY_MODEL_IDS = new Set(["anthropic.claude-opus-5"]); const MODELS_DEV_OPENAI_UNSUPPORTED_MODEL_IDS = new Set(["gpt-5.6"]); +// GPT-5.5 and older put tools loaded through tool search or additional_tools in a namespace named +// after the tool and reject replayed calls without it, so history from other models fails there +// (#11415). Like Codex, dynamic tool loading therefore starts with GPT-5.6, which does not do this. const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([ - "gpt-5.4", - "gpt-5.4-mini", - "gpt-5.4-pro", - "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", @@ -381,7 +380,13 @@ const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([ "gpt-6.1-terra", ]); const OPENAI_ADDITIONAL_TOOLS_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS; -const OPENAI_MID_CONVO_SYSTEM_MESSAGE_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS; +const OPENAI_MID_CONVO_SYSTEM_MESSAGE_MODEL_IDS = new Set([ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-pro", + "gpt-5.5", + ...OPENAI_TOOL_SEARCH_MODEL_IDS, +]); const OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS = new Set([ "gpt-5.6-sol", "gpt-5.6-terra", @@ -985,7 +990,7 @@ function applyOpenAIResponsesTranscriptMetadata(model: Model): void { model.compat = { ...(model.compat as OpenAIResponsesCompat | undefined), supportsMidConvoSystemMessages: true, - ...(isProxiedResponses ? { supportsAdditionalTools: true } : {}), + ...(isProxiedResponses && OPENAI_ADDITIONAL_TOOLS_MODEL_IDS.has(model.id) ? { supportsAdditionalTools: true } : {}), }; } diff --git a/packages/ai/test/openai-responses-compat.test.ts b/packages/ai/test/openai-responses-compat.test.ts index 3b61686e8..ec7783313 100644 --- a/packages/ai/test/openai-responses-compat.test.ts +++ b/packages/ai/test/openai-responses-compat.test.ts @@ -119,7 +119,7 @@ describe("openai-responses provider defaults", () => { ); const stream = streamOpenAIResponses( - getModel("openai", "gpt-5.4"), + getModel("openai", "gpt-5.6-luna"), normalizeContext({ messages: [ { @@ -149,7 +149,7 @@ describe("openai-responses provider defaults", () => { if (event.type === "done" || event.type === "error") break; } - // gpt-5.4 declares its tools in additional_tools items; tool_choice applies to them. + // gpt-5.6-luna declares its tools in additional_tools items; tool_choice applies to them. expect(capturedPayload).toMatchObject({ tool_choice: "required", input: [{ type: "additional_tools", tools: [expect.objectContaining({ name: "ping" })] }, expect.anything()],