From a35f96f427e93cdfbfa7882d0c5ff1de7402f8b5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 28 Aug 2026 16:55:13 -0400 Subject: [PATCH] fix(core): preserve late opaque reasoning (#45694) --- .../openai-compatible-chat-language-model.ts | 6 ++++- .../github-copilot/copilot-chat-model.test.ts | 26 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts b/packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts index 6ad8757eb8a..de277fa69e9 100644 --- a/packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts +++ b/packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts @@ -653,7 +653,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 { } if (isActiveText) { - controller.enqueue({ type: "text-end", id: "txt-0" }) + controller.enqueue({ + type: "text-end", + id: "txt-0", + providerMetadata: reasoningOpaque ? { copilot: { reasoningOpaque } } : undefined, + }) } // go through all tool calls and send the ones that are not finished diff --git a/packages/core/test/github-copilot/copilot-chat-model.test.ts b/packages/core/test/github-copilot/copilot-chat-model.test.ts index bc1e2ecd956..0bc388787d5 100644 --- a/packages/core/test/github-copilot/copilot-chat-model.test.ts +++ b/packages/core/test/github-copilot/copilot-chat-model.test.ts @@ -244,14 +244,25 @@ describe("doStream", () => { expect(reasoningEndIndex).toBeLessThan(textStartIndex) // In this fixture, reasoning_opaque comes AFTER content has started (in chunk 4) - // So it arrives too late to be attached to reasoning-end. But it should still - // be captured and included in the finish event's providerMetadata. + // So it arrives too late to be attached to reasoning-end. It should still be + // captured on the completed text part and the finish event. const reasoningEnd = parts.find((p) => p.type === "reasoning-end") expect(reasoningEnd).toMatchObject({ type: "reasoning-end", id: "reasoning-0", }) + const textEnd = parts.find((p) => p.type === "text-end") + expect(textEnd).toEqual({ + type: "text-end", + id: "txt-0", + providerMetadata: { + copilot: { + reasoningOpaque: "/PMlTqxqSJZnUBDHgnnJKLVI4eZQ", + }, + }, + }) + // reasoning_opaque should be in the finish event's providerMetadata const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ @@ -305,6 +316,17 @@ describe("doStream", () => { }, }) + const textEnd = parts.find((p) => p.type === "text-end") + expect(textEnd).toEqual({ + type: "text-end", + id: "txt-0", + providerMetadata: { + copilot: { + reasoningOpaque: "ExXaGwW7jBo39OXRe9EPoFGN1rOtLJBx", + }, + }, + }) + // Check text deltas const textDeltas = parts.filter((p) => p.type === "text-delta") expect(textDeltas).toHaveLength(2)