From bab58f821dcf51b47479e6323630e0a11a689f16 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 24 Mar 2026 20:39:11 +0100 Subject: [PATCH] fix(ai): omit copilot responses reasoning default closes #2567 --- packages/ai/CHANGELOG.md | 4 ++ packages/ai/src/providers/openai-responses.ts | 2 +- .../openai-responses-copilot-provider.test.ts | 44 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/ai/test/openai-responses-copilot-provider.test.ts diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 42c4e68f..3a344f75 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed GitHub Copilot OpenAI Responses requests to omit the `reasoning` field entirely when no reasoning effort is requested, avoiding `400` errors from Copilot `gpt-5-mini` rejecting `reasoning: { effort: "none" }` during internal summary calls ([#2567](https://github.com/badlogic/pi-mono/issues/2567)) + ## [0.62.0] - 2026-03-23 ### Added diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index 238403e0..51c1a170 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -220,7 +220,7 @@ function buildParams(model: Model<"openai-responses">, context: Context, options summary: options?.reasoningSummary || "auto", }; params.include = ["reasoning.encrypted_content"]; - } else { + } else if (model.provider !== "github-copilot") { params.reasoning = { effort: "none" }; } } diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-copilot-provider.test.ts new file mode 100644 index 00000000..04b2856c --- /dev/null +++ b/packages/ai/test/openai-responses-copilot-provider.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { getModel } from "../src/models.js"; +import { streamOpenAIResponses } from "../src/providers/openai-responses.js"; + +describe("openai-responses github-copilot defaults", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("omits reasoning when no reasoning is requested", async () => { + const model = getModel("github-copilot", "gpt-5-mini"); + let capturedPayload: unknown; + + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response("data: [DONE]\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" }, + }), + ); + + const stream = streamOpenAIResponses( + model, + { + systemPrompt: "sys", + messages: [{ role: "user", content: "hi", timestamp: Date.now() }], + }, + { + apiKey: "test-key", + onPayload: (payload) => { + capturedPayload = payload; + }, + }, + ); + + for await (const event of stream) { + if (event.type === "done" || event.type === "error") break; + } + + expect(capturedPayload).not.toBeNull(); + expect(capturedPayload).not.toMatchObject({ + reasoning: expect.anything(), + }); + }); +});