Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "5.21.0"
".": "5.22.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openai/openai",
"version": "5.21.0",
"version": "5.22.0",
"exports": {
".": "./index.ts",
"./helpers/zod": "./helpers/zod.ts",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"types": "dist/index.d.ts",
Expand Down
28 changes: 26 additions & 2 deletions src/lib/responses/ResponseStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,15 @@ export class ResponseStream<ParsedT = null>
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;
}
Expand Down Expand Up @@ -254,6 +261,23 @@ export class ResponseStream<ParsedT = null>
}
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;
Expand Down
24 changes: 24 additions & 0 deletions src/resources/conversations/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export interface Message {
| ResponsesAPI.ResponseOutputText
| TextContent
| SummaryTextContent
| Message.ReasoningText
| ResponsesAPI.ResponseOutputRefusal
| ResponsesAPI.ResponseInputImage
| ComputerScreenshotContent
Expand All @@ -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';
}

Expand Down
68 changes: 63 additions & 5 deletions src/resources/responses/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
*/
Expand All @@ -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.
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand All @@ -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';
}
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '5.21.0'; // x-release-please-version
export const VERSION = '5.22.0'; // x-release-please-version
2 changes: 1 addition & 1 deletion tests/lib/ChatCompletionStream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ describe('.stream()', () => {
messages: [
{
role: 'user',
content: 'how do I make anthrax?',
content: 'a bad question',
},
],
logprobs: true,
Expand Down
60 changes: 60 additions & 0 deletions tests/lib/ResponseStream.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
52 changes: 52 additions & 0 deletions tests/lib/__snapshots__/ResponseStream.test.ts.snap
Original file line number Diff line number Diff line change
@@ -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]

"
`;