diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 2a035edf3..2570bac1d 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed OpenAI-compatible Chat Completions requests sending `tool_choice` without tools, which gateways can reject during compaction ([#8607](https://github.com/earendil-works/pi/issues/8607)). + ## [0.84.3] - 2026-08-24 ### Breaking Changes diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts index 04326ca2f..0529db628 100644 --- a/packages/ai/src/api/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -835,7 +835,7 @@ function buildParams( applyAnthropicCacheControl(messages, params.tools, cacheControl); } - if (options?.toolChoice) { + if (options?.toolChoice && params.tools?.length) { params.tool_choice = options.toolChoice; } diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index c453f3d54..35be54538 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -150,6 +150,30 @@ describe("openai-completions tool_choice", () => { expect(params.tools?.length ?? 0).toBeGreaterThan(0); }); + it("omits toolChoice when no tools are provided", async () => { + const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!; + const model = { ...baseModel, api: "openai-completions" } as const; + let payload: unknown; + + await streamSimple( + model, + { + messages: [{ role: "user", content: "Summarize the conversation", timestamp: Date.now() }], + }, + { + apiKey: "test", + toolChoice: "none", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { tool_choice?: string; tools?: unknown[] }; + expect(params).not.toHaveProperty("tool_choice"); + expect(params).not.toHaveProperty("tools"); + }); + it("omits strict when compat disables strict mode", async () => { const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!; const model = {