From 0bd3014e07112e311966abe0acd9cf5961c5e00e Mon Sep 17 00:00:00 2001 From: ai-sdk-factory <308175966+ai-sdk-factory@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:02:33 +0000 Subject: [PATCH] reproduction (reproduced) Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com> --- .ai-sdk-factory/reproduction-replay.json | 13 +++ ...-19659-open-responses-reasoning-summary.ts | 95 +++++++++++++++++++ ...openai-reasoning-summary-text.1.chunks.txt | 5 + .../open-responses-language-model.test.ts | 28 ++++++ 4 files changed, 141 insertions(+) create mode 100644 .ai-sdk-factory/reproduction-replay.json create mode 100644 examples/ai-functions/src/reproduction/issue-19659-open-responses-reasoning-summary.ts create mode 100644 packages/open-responses/src/responses/__fixtures__/openai-reasoning-summary-text.1.chunks.txt diff --git a/.ai-sdk-factory/reproduction-replay.json b/.ai-sdk-factory/reproduction-replay.json new file mode 100644 index 000000000000..908d9ea347b8 --- /dev/null +++ b/.ai-sdk-factory/reproduction-replay.json @@ -0,0 +1,13 @@ +{ + "version": 2, + "issueId": 19659, + "expectationRevision": 1, + "command": "pnpm -C examples/ai-functions exec tsx src/reproduction/issue-19659-open-responses-reasoning-summary.ts", + "artifactPaths": [ + "examples/ai-functions/src/reproduction/issue-19659-open-responses-reasoning-summary.ts" + ], + "expectedFailure": { + "exitCode": 1, + "signal": "ISSUE_19659_REPRODUCED: expected streamed reasoning \"Thinking.\" but received \"\"" + } +} diff --git a/examples/ai-functions/src/reproduction/issue-19659-open-responses-reasoning-summary.ts b/examples/ai-functions/src/reproduction/issue-19659-open-responses-reasoning-summary.ts new file mode 100644 index 000000000000..03f4416969ab --- /dev/null +++ b/examples/ai-functions/src/reproduction/issue-19659-open-responses-reasoning-summary.ts @@ -0,0 +1,95 @@ +import { createOpenResponses } from '@ai-sdk/open-responses'; +import { streamText } from 'ai'; + +async function main() { + const sse = [ + { + type: 'response.output_item.added', + output_index: 0, + item: { id: 'rs_1', type: 'reasoning', summary: [] }, + }, + { + type: 'response.reasoning_summary_text.delta', + item_id: 'rs_1', + output_index: 0, + summary_index: 0, + delta: 'Think', + }, + { + type: 'response.reasoning_summary_text.delta', + item_id: 'rs_1', + output_index: 0, + summary_index: 0, + delta: 'ing.', + }, + { + type: 'response.reasoning_summary_text.done', + item_id: 'rs_1', + output_index: 0, + summary_index: 0, + text: 'Thinking.', + }, + { + type: 'response.output_item.done', + output_index: 0, + item: { + id: 'rs_1', + type: 'reasoning', + summary: [{ type: 'summary_text', text: 'Thinking.' }], + }, + }, + { + type: 'response.completed', + response: { + id: 'resp_1', + status: 'completed', + incomplete_details: null, + usage: { + input_tokens: 1, + input_tokens_details: { cached_tokens: 0 }, + output_tokens: 1, + output_tokens_details: { reasoning_tokens: 1 }, + total_tokens: 2, + }, + }, + }, + ] + .map(event => `data: ${JSON.stringify(event)}\n\n`) + .join(''); + + const provider = createOpenResponses({ + name: 'reproduction', + url: 'https://example.invalid/v1/responses', + apiKey: 'test', + fetch: async () => + new Response(sse, { + headers: { 'content-type': 'text/event-stream' }, + }), + }); + + const result = streamText({ + model: provider('any-model'), + prompt: 'hi', + }); + + let streamedReasoning = ''; + + for await (const part of result.fullStream) { + if (part.type === 'reasoning-delta') { + streamedReasoning += part.text; + } + } + + const expectedReasoning = 'Thinking.'; + + if (streamedReasoning !== expectedReasoning) { + throw new Error( + `ISSUE_19659_REPRODUCED: expected streamed reasoning "${expectedReasoning}" but received "${streamedReasoning}"`, + ); + } +} + +main().catch(error => { + console.error(error); + process.exit(1); +}); diff --git a/packages/open-responses/src/responses/__fixtures__/openai-reasoning-summary-text.1.chunks.txt b/packages/open-responses/src/responses/__fixtures__/openai-reasoning-summary-text.1.chunks.txt new file mode 100644 index 000000000000..3d65158d1279 --- /dev/null +++ b/packages/open-responses/src/responses/__fixtures__/openai-reasoning-summary-text.1.chunks.txt @@ -0,0 +1,5 @@ +{"type":"response.output_item.added","output_index":0,"item":{"id":"rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828","type":"reasoning","summary":[]}} +{"type":"response.reasoning_summary_text.delta","item_id":"rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828","output_index":0,"summary_index":0,"delta":"**Providing concise arithmetic answer**"} +{"type":"response.reasoning_summary_text.done","item_id":"rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828","output_index":0,"summary_index":0,"text":"**Providing concise arithmetic answer**"} +{"type":"response.output_item.done","output_index":0,"item":{"id":"rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828","type":"reasoning","summary":[{"type":"summary_text","text":"**Providing concise arithmetic answer**"}]}} +{"type":"response.completed","response":{"id":"resp_084abd547a8ac210006a8effb60d2887d2bc8a15bc8f8b7f1a","model":"gpt-5-mini-2025-08-07","status":"completed","incomplete_details":null,"usage":{"input_tokens":13,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":42,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":55}}} diff --git a/packages/open-responses/src/responses/open-responses-language-model.test.ts b/packages/open-responses/src/responses/open-responses-language-model.test.ts index 56309f0cf283..f00fdcd0c980 100644 --- a/packages/open-responses/src/responses/open-responses-language-model.test.ts +++ b/packages/open-responses/src/responses/open-responses-language-model.test.ts @@ -659,6 +659,34 @@ describe('OpenResponsesLanguageModel', () => { }); }); + it('should stream OpenAI reasoning summary text deltas', async () => { + prepareChunksFixtureResponse('openai-reasoning-summary-text.1'); + + const result = await createModel().doStream({ + prompt: TEST_PROMPT, + }); + + const reasoningParts = ( + await convertReadableStreamToArray(result.stream) + ).filter(part => part.type.startsWith('reasoning')); + + expect(reasoningParts).toStrictEqual([ + { + type: 'reasoning-start', + id: 'rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828', + }, + { + type: 'reasoning-delta', + id: 'rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828', + delta: '**Providing concise arithmetic answer**', + }, + { + type: 'reasoning-end', + id: 'rs_084abd547a8ac210006a8effb672d487d2a1c772cd76b19828', + }, + ]); + }); + it('should not pollute Object.prototype from tool call item ids', async () => { const originalArgumentsDescriptor = Object.getOwnPropertyDescriptor( Object.prototype,