From 81d6759fc90439857b3ed8a020abe34ccdc8d3eb Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Tue, 27 Oct 2026 14:25:23 +0100 Subject: [PATCH] fix(ai): declare GPT-5.4 and GPT-5.5 tools at the top level GPT-5.5 and older namespace tools loaded through additional_tools or tool search and reject replayed calls without the namespace, so switching to them after another model's tool calls failed with 400. Drop dynamic tool loading for these models on OpenAI and OpenCode; keep mid-conversation system messages. closes #11415 --- packages/ai/CHANGELOG.md | 1 + packages/ai/scripts/generate-models.ts | 17 +++++++++++------ .../ai/test/openai-responses-compat.test.ts | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) 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()],