From 7ff61860a14ae20d0734b4f592002dfe67330488 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 05:03:38 +0000 Subject: [PATCH 1/3] feat(api): add reasoning_text --- .stats.yml | 6 +- src/resources/conversations/conversations.ts | 24 +++++++ src/resources/responses/responses.ts | 68 ++++++++++++++++++-- 3 files changed, 90 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2dd0aef46..c961e232c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 118 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-380330a93b5d010391ca3b36ea193c5353b0dfdf2ddd02789ef84a84ce427e82.yml -openapi_spec_hash: 859703234259ecdd2a3c6f4de88eb504 -config_hash: b619b45c1e7facf819f902dee8fa4f97 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-ea23db36b0899cc715f56d0098956069b2d92880f448adff3a4ac1bb53cb2cec.yml +openapi_spec_hash: 36f76ea31297c9593bcfae453f6255cc +config_hash: 666d6bb4b564f0d9d431124b5d1a0665 diff --git a/src/resources/conversations/conversations.ts b/src/resources/conversations/conversations.ts index 30775981c..27f688088 100644 --- a/src/resources/conversations/conversations.ts +++ b/src/resources/conversations/conversations.ts @@ -135,6 +135,7 @@ export interface Message { | ResponsesAPI.ResponseOutputText | TextContent | SummaryTextContent + | Message.ReasoningText | ResponsesAPI.ResponseOutputRefusal | ResponsesAPI.ResponseInputImage | ComputerScreenshotContent @@ -159,12 +160,35 @@ export interface Message { type: 'message'; } +export namespace Message { + /** + * Reasoning text from the model. + */ + export interface ReasoningText { + /** + * The reasoning text from the model. + */ + text: string; + + /** + * The type of the reasoning text. Always `reasoning_text`. + */ + type: 'reasoning_text'; + } +} + /** * A summary text from the model. */ export interface SummaryTextContent { + /** + * A summary of the reasoning output from the model so far. + */ text: string; + /** + * The type of the object. Always `summary_text`. + */ type: 'summary_text'; } diff --git a/src/resources/responses/responses.ts b/src/resources/responses/responses.ts index 91e01bed2..dca13a9bb 100644 --- a/src/resources/responses/responses.ts +++ b/src/resources/responses/responses.ts @@ -1282,7 +1282,25 @@ export type ResponseContent = | ResponseInputFile | ResponseInputAudio | ResponseOutputText - | ResponseOutputRefusal; + | ResponseOutputRefusal + | ResponseContent.ReasoningTextContent; + +export namespace ResponseContent { + /** + * Reasoning text from the model. + */ + export interface ReasoningTextContent { + /** + * The reasoning text from the model. + */ + text: string; + + /** + * The type of the reasoning text. Always `reasoning_text`. + */ + type: 'reasoning_text'; + } +} /** * Emitted when a new content part is added. @@ -1306,7 +1324,7 @@ export interface ResponseContentPartAddedEvent { /** * The content part that was added. */ - part: ResponseOutputText | ResponseOutputRefusal; + part: ResponseOutputText | ResponseOutputRefusal | ResponseContentPartAddedEvent.ReasoningText; /** * The sequence number of this event. @@ -1319,6 +1337,23 @@ export interface ResponseContentPartAddedEvent { type: 'response.content_part.added'; } +export namespace ResponseContentPartAddedEvent { + /** + * Reasoning text from the model. + */ + export interface ReasoningText { + /** + * The reasoning text from the model. + */ + text: string; + + /** + * The type of the reasoning text. Always `reasoning_text`. + */ + type: 'reasoning_text'; + } +} + /** * Emitted when a content part is done. */ @@ -1341,7 +1376,7 @@ export interface ResponseContentPartDoneEvent { /** * The content part that is done. */ - part: ResponseOutputText | ResponseOutputRefusal; + part: ResponseOutputText | ResponseOutputRefusal | ResponseContentPartDoneEvent.ReasoningText; /** * The sequence number of this event. @@ -1354,6 +1389,23 @@ export interface ResponseContentPartDoneEvent { type: 'response.content_part.done'; } +export namespace ResponseContentPartDoneEvent { + /** + * Reasoning text from the model. + */ + export interface ReasoningText { + /** + * The reasoning text from the model. + */ + text: string; + + /** + * The type of the reasoning text. Always `reasoning_text`. + */ + type: 'reasoning_text'; + } +} + /** * The conversation that this response belongs to. */ @@ -3940,6 +3992,9 @@ export interface ResponseReasoningItem { } export namespace ResponseReasoningItem { + /** + * A summary text from the model. + */ export interface Summary { /** * A summary of the reasoning output from the model so far. @@ -3952,14 +4007,17 @@ export namespace ResponseReasoningItem { type: 'summary_text'; } + /** + * Reasoning text from the model. + */ export interface Content { /** - * Reasoning text output from the model. + * The reasoning text from the model. */ text: string; /** - * The type of the object. Always `reasoning_text`. + * The type of the reasoning text. Always `reasoning_text`. */ type: 'reasoning_text'; } From 3a2ae4ce2a0796f5201dd9373f103bd94689b733 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:38:22 +0000 Subject: [PATCH 2/3] chore(api): manual fixes for streaming --- src/lib/responses/ResponseStream.ts | 28 ++++++++- tests/lib/ChatCompletionStream.test.ts | 2 +- tests/lib/ResponseStream.test.ts | 60 +++++++++++++++++++ .../__snapshots__/ResponseStream.test.ts.snap | 52 ++++++++++++++++ 4 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 tests/lib/ResponseStream.test.ts create mode 100644 tests/lib/__snapshots__/ResponseStream.test.ts.snap diff --git a/src/lib/responses/ResponseStream.ts b/src/lib/responses/ResponseStream.ts index 1c05c96ce..652c81ced 100644 --- a/src/lib/responses/ResponseStream.ts +++ b/src/lib/responses/ResponseStream.ts @@ -222,8 +222,15 @@ export class ResponseStream if (!output) { throw new OpenAIError(`missing output at index ${event.output_index}`); } - if (output.type === 'message') { - output.content.push(event.part); + const type = output.type; + const part = event.part; + if (type === 'message' && part.type !== 'reasoning_text') { + output.content.push(part); + } else if (type === 'reasoning' && part.type === 'reasoning_text') { + if (!output.content) { + output.content = []; + } + output.content.push(part); } break; } @@ -254,6 +261,23 @@ export class ResponseStream } break; } + case 'response.reasoning_text.delta': { + const output = snapshot.output[event.output_index]; + if (!output) { + throw new OpenAIError(`missing output at index ${event.output_index}`); + } + if (output.type === 'reasoning') { + const content = output.content?.[event.content_index]; + if (!content) { + throw new OpenAIError(`missing content at index ${event.content_index}`); + } + if (content.type !== 'reasoning_text') { + throw new OpenAIError(`expected content to be 'reasoning_text', got ${content.type}`); + } + content.text += event.delta; + } + break; + } case 'response.completed': { this.#currentResponseSnapshot = event.response; break; diff --git a/tests/lib/ChatCompletionStream.test.ts b/tests/lib/ChatCompletionStream.test.ts index 7d78b712a..2a679e06a 100644 --- a/tests/lib/ChatCompletionStream.test.ts +++ b/tests/lib/ChatCompletionStream.test.ts @@ -213,7 +213,7 @@ describe('.stream()', () => { messages: [ { role: 'user', - content: 'how do I make anthrax?', + content: 'a bad question', }, ], logprobs: true, diff --git a/tests/lib/ResponseStream.test.ts b/tests/lib/ResponseStream.test.ts new file mode 100644 index 000000000..9797baa6d --- /dev/null +++ b/tests/lib/ResponseStream.test.ts @@ -0,0 +1,60 @@ +import { makeStreamSnapshotRequest } from '../utils/mock-snapshots'; + +jest.setTimeout(1000 * 30); + +describe('.stream()', () => { + it('standard text works', async () => { + const deltas: string[] = []; + + const stream = ( + await makeStreamSnapshotRequest((openai) => + openai.responses.stream({ + model: 'gpt-4o-2024-08-06', + input: 'Say hello world', + }), + ) + ).on('response.output_text.delta', (e) => { + deltas.push(e.snapshot); + }); + + const final = await stream.finalResponse(); + expect(final.output_text).toBe('Hello world'); + expect(deltas).toEqual(['Hello ', 'Hello world']); + + // basic shape checks + expect(final.object).toBe('response'); + expect(final.output[0]?.type).toBe('message'); + // message should contain a single output_text part with the final text + const msg = final.output[0]; + if (msg?.type === 'message') { + expect(msg.content[0]).toMatchObject({ type: 'output_text', text: 'Hello world' }); + } + }); + + it('reasoning works', async () => { + const stream = await makeStreamSnapshotRequest((openai) => + openai.responses.stream({ + model: 'o3', + input: 'Compute 6 * 7', + reasoning: { effort: 'medium' }, + }), + ); + + const final = await stream.finalResponse(); + expect(final.object).toBe('response'); + // first item should be reasoning with accumulated text + expect(final.output[0]?.type).toBe('reasoning'); + if (final.output[0]?.type === 'reasoning') { + expect(final.output[0].content?.[0]).toMatchObject({ + type: 'reasoning_text', + text: 'Chain: Step 1. Step 2.', + }); + } + // second item should be the assistant message with the final text + expect(final.output[1]?.type).toBe('message'); + if (final.output[1]?.type === 'message') { + expect(final.output[1].content[0]).toMatchObject({ type: 'output_text', text: 'The answer is 42' }); + } + expect(final.output_text).toBe('The answer is 42'); + }); +}); diff --git a/tests/lib/__snapshots__/ResponseStream.test.ts.snap b/tests/lib/__snapshots__/ResponseStream.test.ts.snap new file mode 100644 index 000000000..2ebb08c67 --- /dev/null +++ b/tests/lib/__snapshots__/ResponseStream.test.ts.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.stream() standard text works 1`] = ` +"data: {\"response\":{\"id\":\"resp_test_1\",\"object\":\"response\",\"created_at\":1723031665,\"model\":\"gpt-4o-2024-08-06\",\"output\":[],\"output_text\":\"\",\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":null,\"parallel_tool_calls\":false,\"temperature\":null,\"tools\":[],\"top_p\":null,\"status\":\"in_progress\",\"usage\":{\"input_tokens\":0,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":0}},\"sequence_number\":0,\"type\":\"response.created\"} + +data: {\"item\":{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"in_progress\",\"content\":[]},\"output_index\":0,\"sequence_number\":1,\"type\":\"response.output_item.added\"} + +data: {\"content_index\":0,\"item_id\":\"msg_1\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"text\":\"\",\"annotations\":[]},\"sequence_number\":2,\"type\":\"response.content_part.added\"} + +data: {\"content_index\":0,\"delta\":\"Hello \",\"item_id\":\"msg_1\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":3,\"type\":\"response.output_text.delta\"} + +data: {\"content_index\":0,\"delta\":\"world\",\"item_id\":\"msg_1\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":4,\"type\":\"response.output_text.delta\"} + +data: {\"content_index\":0,\"item_id\":\"msg_1\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":5,\"text\":\"Hello world\",\"type\":\"response.output_text.done\"} + +data: {\"item\":{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello world\",\"annotations\":[]}]},\"output_index\":0,\"sequence_number\":6,\"type\":\"response.output_item.done\"} + +data: {\"response\":{\"id\":\"resp_test_1\",\"object\":\"response\",\"created_at\":1723031665,\"model\":\"gpt-4o-2024-08-06\",\"output\":[{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello world\",\"annotations\":[]}]}],\"output_text\":\"Hello world\",\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":null,\"parallel_tool_calls\":false,\"temperature\":null,\"tools\":[],\"top_p\":null,\"status\":\"completed\",\"usage\":{\"input_tokens\":10,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":2,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":12}},\"sequence_number\":7,\"type\":\"response.completed\"} + +data: [DONE] + +" +`; + +exports[`.stream() reasoning works 1`] = ` +"data: {\"response\":{\"id\":\"resp_reason_1\",\"object\":\"response\",\"created_at\":1723031666,\"model\":\"o3\",\"output\":[],\"output_text\":\"\",\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":null,\"parallel_tool_calls\":false,\"temperature\":null,\"tools\":[],\"top_p\":null,\"status\":\"in_progress\",\"usage\":{\"input_tokens\":0,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":0,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":0}},\"sequence_number\":0,\"type\":\"response.created\"} + +data: {\"item\":{\"id\":\"r1\",\"type\":\"reasoning\",\"summary\":[],\"status\":\"in_progress\"},\"output_index\":0,\"sequence_number\":1,\"type\":\"response.output_item.added\"} + +data: {\"content_index\":0,\"item_id\":\"r1\",\"output_index\":0,\"part\":{\"type\":\"reasoning_text\",\"text\":\"\"},\"sequence_number\":2,\"type\":\"response.content_part.added\"} + +data: {\"content_index\":0,\"delta\":\"Chain: Step 1. \",\"item_id\":\"r1\",\"output_index\":0,\"sequence_number\":3,\"type\":\"response.reasoning_text.delta\"} + +data: {\"content_index\":0,\"delta\":\"Step 2.\",\"item_id\":\"r1\",\"output_index\":0,\"sequence_number\":4,\"type\":\"response.reasoning_text.delta\"} + +data: {\"item\":{\"id\":\"msg_2\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"in_progress\",\"content\":[]},\"output_index\":1,\"sequence_number\":5,\"type\":\"response.output_item.added\"} + +data: {\"content_index\":0,\"item_id\":\"msg_2\",\"output_index\":1,\"part\":{\"type\":\"output_text\",\"text\":\"\",\"annotations\":[]},\"sequence_number\":6,\"type\":\"response.content_part.added\"} + +data: {\"content_index\":0,\"delta\":\"The answer is \",\"item_id\":\"msg_2\",\"logprobs\":[],\"output_index\":1,\"sequence_number\":7,\"type\":\"response.output_text.delta\"} + +data: {\"content_index\":0,\"delta\":\"42\",\"item_id\":\"msg_2\",\"logprobs\":[],\"output_index\":1,\"sequence_number\":8,\"type\":\"response.output_text.delta\"} + +data: {\"content_index\":0,\"item_id\":\"msg_2\",\"logprobs\":[],\"output_index\":1,\"sequence_number\":9,\"text\":\"The answer is 42\",\"type\":\"response.output_text.done\"} + +data: {\"response\":{\"id\":\"resp_reason_1\",\"object\":\"response\",\"created_at\":1723031666,\"model\":\"o3\",\"output\":[{\"id\":\"r1\",\"type\":\"reasoning\",\"summary\":[],\"status\":\"completed\",\"content\":[{\"type\":\"reasoning_text\",\"text\":\"Chain: Step 1. Step 2.\"}]},{\"id\":\"msg_2\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"The answer is 42\",\"annotations\":[]}]}],\"output_text\":\"The answer is 42\",\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":null,\"parallel_tool_calls\":false,\"temperature\":null,\"tools\":[],\"top_p\":null,\"status\":\"completed\",\"usage\":{\"input_tokens\":0,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":6,\"output_tokens_details\":{\"reasoning_tokens\":2},\"total_tokens\":6}},\"sequence_number\":10,\"type\":\"response.completed\"} + +data: [DONE] + +" +`; + From 6ad4e9048bd9390e0bb90f52bccc0a43eb49066b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:38:51 +0000 Subject: [PATCH 3/3] release: 5.22.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ jsr.json | 2 +- package.json | 2 +- src/version.ts | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d4f0c6a28..6b7512083 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.21.0" + ".": "5.22.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 279e64fd8..270f2e20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 5.22.0 (2025-09-19) + +Full Changelog: [v5.21.0...v5.22.0](https://github.com/openai/openai-node/compare/v5.21.0...v5.22.0) + +### Features + +* **api:** add reasoning_text ([7ff6186](https://github.com/openai/openai-node/commit/7ff61860a14ae20d0734b4f592002dfe67330488)) + + +### Chores + +* **api:** manual fixes for streaming ([3a2ae4c](https://github.com/openai/openai-node/commit/3a2ae4ce2a0796f5201dd9373f103bd94689b733)) + ## 5.21.0 (2025-09-17) Full Changelog: [v5.20.3...v5.21.0](https://github.com/openai/openai-node/compare/v5.20.3...v5.21.0) diff --git a/jsr.json b/jsr.json index 0fdb8b961..c8380305e 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@openai/openai", - "version": "5.21.0", + "version": "5.22.0", "exports": { ".": "./index.ts", "./helpers/zod": "./helpers/zod.ts", diff --git a/package.json b/package.json index 7ff223d9f..0328cb5ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openai", - "version": "5.21.0", + "version": "5.22.0", "description": "The official TypeScript library for the OpenAI API", "author": "OpenAI ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index 2d3d20404..d4d00cb2e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '5.21.0'; // x-release-please-version +export const VERSION = '5.22.0'; // x-release-please-version