diff --git a/.changeset/fix-reasoning-streaming.md b/.changeset/fix-reasoning-streaming.md new file mode 100644 index 00000000..2e312e81 --- /dev/null +++ b/.changeset/fix-reasoning-streaming.md @@ -0,0 +1,11 @@ +--- +'@tanstack/ai-openai': patch +--- + +Fix reasoning token streaming for `gpt-5-mini` and `gpt-5-nano` models + +- Added `OpenAIReasoningOptions` to type definitions for `gpt-5-mini` and `gpt-5-nano` models +- Fixed `summary` option placement in `OpenAIReasoningOptions` (moved inside `reasoning` object to match OpenAI SDK) +- Added handler for `response.reasoning_summary_text.delta` events to stream reasoning summaries +- Added model-specific `reasoning.summary` types: `concise` only available for `computer-use-preview` +- Added `OpenAIReasoningOptionsWithConcise` for `computer-use-preview` model diff --git a/docs/adapters/openai.md b/docs/adapters/openai.md index 2beaeb3e..1d1392b0 100644 --- a/docs/adapters/openai.md +++ b/docs/adapters/openai.md @@ -131,7 +131,8 @@ Enable reasoning for models that support it (e.g., GPT-5). This allows the model ```typescript providerOptions: { reasoning: { - effort: "medium", // "minimal" | "low" | "medium" | "high" + effort: "medium", // "none" | "minimal" | "low" | "medium" | "high" + summary: "detailed", // "auto" | "detailed" (optional) }, } ``` diff --git a/packages/typescript/ai-openai/src/model-meta.ts b/packages/typescript/ai-openai/src/model-meta.ts index 67458882..024a6c1a 100644 --- a/packages/typescript/ai-openai/src/model-meta.ts +++ b/packages/typescript/ai-openai/src/model-meta.ts @@ -2,6 +2,7 @@ import type { OpenAIBaseOptions, OpenAIMetadataOptions, OpenAIReasoningOptions, + OpenAIReasoningOptionsWithConcise, OpenAIStreamingOptions, OpenAIStructuredOutputOptions, OpenAIToolsOptions, @@ -198,6 +199,7 @@ const GPT5_MINI = { }, } as const satisfies ModelMeta< OpenAIBaseOptions & + OpenAIReasoningOptions & OpenAIStructuredOutputOptions & OpenAIToolsOptions & OpenAIStreamingOptions & @@ -233,6 +235,7 @@ const GPT5_NANO = { }, } as const satisfies ModelMeta< OpenAIBaseOptions & + OpenAIReasoningOptions & OpenAIStructuredOutputOptions & OpenAIToolsOptions & OpenAIStreamingOptions & @@ -815,6 +818,7 @@ const COMPUTER_USE_PREVIEW = { }, } as const satisfies ModelMeta< OpenAIBaseOptions & + OpenAIReasoningOptionsWithConcise & OpenAIToolsOptions & OpenAIStreamingOptions & OpenAIMetadataOptions @@ -1787,11 +1791,13 @@ export type OpenAIChatModelProviderOptionsByName = { OpenAIStreamingOptions & OpenAIMetadataOptions [GPT5_MINI.name]: OpenAIBaseOptions & + OpenAIReasoningOptions & OpenAIStructuredOutputOptions & OpenAIToolsOptions & OpenAIStreamingOptions & OpenAIMetadataOptions [GPT5_NANO.name]: OpenAIBaseOptions & + OpenAIReasoningOptions & OpenAIStructuredOutputOptions & OpenAIToolsOptions & OpenAIStreamingOptions & @@ -1920,6 +1926,7 @@ export type OpenAIChatModelProviderOptionsByName = { // Special models [COMPUTER_USE_PREVIEW.name]: OpenAIBaseOptions & + OpenAIReasoningOptionsWithConcise & OpenAIToolsOptions & OpenAIStreamingOptions & OpenAIMetadataOptions diff --git a/packages/typescript/ai-openai/src/openai-adapter.ts b/packages/typescript/ai-openai/src/openai-adapter.ts index 3c51e77f..676d5257 100644 --- a/packages/typescript/ai-openai/src/openai-adapter.ts +++ b/packages/typescript/ai-openai/src/openai-adapter.ts @@ -343,6 +343,29 @@ export class OpenAI extends BaseAdapter< } } + // Handle reasoning summary deltas (when using reasoning.summary option) + // response.reasoning_summary_text.delta provides incremental summary updates + if ( + chunk.type === 'response.reasoning_summary_text.delta' && + chunk.delta + ) { + const summaryDelta = + typeof chunk.delta === 'string' ? chunk.delta : '' + + if (summaryDelta) { + accumulatedReasoning += summaryDelta + hasStreamedReasoningDeltas = true + yield { + type: 'thinking', + id: responseId || generateId(), + model: model || options.model, + timestamp, + delta: summaryDelta, + content: accumulatedReasoning, + } + } + } + // handle content_part added events for text, reasoning and refusals if (chunk.type === 'response.content_part.added') { const contentPart = chunk.part diff --git a/packages/typescript/ai-openai/src/text/text-provider-options.ts b/packages/typescript/ai-openai/src/text/text-provider-options.ts index 23fdf0fa..85424f00 100644 --- a/packages/typescript/ai-openai/src/text/text-provider-options.ts +++ b/packages/typescript/ai-openai/src/text/text-provider-options.ts @@ -125,6 +125,14 @@ https://platform.openai.com/docs/api-reference/responses/create#responses_create } // Feature fragments that can be stitched per-model + +// Shared base types for reasoning options +type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' +type ReasoningSummary = 'auto' | 'detailed' + +/** + * Reasoning options for most models (excludes 'concise' summary). + */ export interface OpenAIReasoningOptions { /** * Reasoning controls for models that support it. @@ -138,13 +146,39 @@ export interface OpenAIReasoningOptions { * All models before gpt-5.1 default to medium reasoning effort, and do not support none. * The gpt-5-pro model defaults to (and only supports) high reasoning effort. */ - effort?: 'none' | 'minimal' | 'low' | 'medium' | 'high' + effort?: ReasoningEffort + /** + * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. + * https://platform.openai.com/docs/api-reference/responses/create#responses_create-reasoning-summary + */ + summary?: ReasoningSummary } +} + +/** + * Reasoning options for computer-use-preview model (includes 'concise' summary). + */ +export interface OpenAIReasoningOptionsWithConcise { /** - * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process - * https://platform.openai.com/docs/api-reference/responses/create#responses_create-reasoning-summary + * Reasoning controls for models that support it. + * Lets you guide how much chain-of-thought computation to spend. + * https://platform.openai.com/docs/api-reference/responses/create#responses_create-reasoning + * https://platform.openai.com/docs/guides/reasoning */ - summary?: 'auto' | 'concise' | 'detailed' + reasoning?: { + /** + * gpt-5.1 defaults to none, which does not perform reasoning. The supported reasoning values for gpt-5.1 are none, low, medium, and high. Tool calls are supported for all reasoning values in gpt-5.1. + * All models before gpt-5.1 default to medium reasoning effort, and do not support none. + * The gpt-5-pro model defaults to (and only supports) high reasoning effort. + */ + effort?: ReasoningEffort + /** + * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. + * `concise` is only supported for `computer-use-preview` models. + * https://platform.openai.com/docs/api-reference/responses/create#responses_create-reasoning-summary + */ + summary?: ReasoningSummary | 'concise' + } } export interface OpenAIStructuredOutputOptions { diff --git a/packages/typescript/ai-openai/tests/model-meta.test.ts b/packages/typescript/ai-openai/tests/model-meta.test.ts index bd8f316e..39d81470 100644 --- a/packages/typescript/ai-openai/tests/model-meta.test.ts +++ b/packages/typescript/ai-openai/tests/model-meta.test.ts @@ -6,6 +6,7 @@ import type { import type { OpenAIBaseOptions, OpenAIReasoningOptions, + OpenAIReasoningOptionsWithConcise, OpenAIStructuredOutputOptions, OpenAIToolsOptions, OpenAIStreamingOptions, @@ -111,12 +112,12 @@ describe('OpenAI Chat Model Provider Options Type Assertions', () => { }) }) - describe('Models WITH structured output AND tools but WITHOUT reasoning (Standard)', () => { - it('gpt-5-mini should have structured output and tools but NOT reasoning', () => { + describe('Models WITH reasoning AND structured output AND tools (gpt-5-mini/nano)', () => { + it('gpt-5-mini should have reasoning, structured output and tools', () => { type Options = OpenAIChatModelProviderOptionsByName['gpt-5-mini'] - // Should NOT have reasoning options - expectTypeOf().not.toExtend() + // Should have reasoning options + expectTypeOf().toExtend() // Should have structured output options expectTypeOf().toExtend() @@ -131,16 +132,18 @@ describe('OpenAI Chat Model Provider Options Type Assertions', () => { expectTypeOf().toExtend() }) - it('gpt-5-nano should have structured output and tools but NOT reasoning', () => { + it('gpt-5-nano should have reasoning, structured output and tools', () => { type Options = OpenAIChatModelProviderOptionsByName['gpt-5-nano'] - expectTypeOf().not.toExtend() + expectTypeOf().toExtend() expectTypeOf().toExtend() expectTypeOf().toExtend() expectTypeOf().toExtend() expectTypeOf().toExtend() }) + }) + describe('Models WITH structured output AND tools but WITHOUT reasoning (Standard)', () => { it('gpt-5-codex should have structured output and tools but NOT reasoning', () => { type Options = OpenAIChatModelProviderOptionsByName['gpt-5-codex'] @@ -439,11 +442,12 @@ describe('OpenAI Chat Model Provider Options Type Assertions', () => { expectTypeOf().toExtend() }) - it('computer-use-preview should have tools but NOT structured output', () => { + it('computer-use-preview should have tools and reasoning with concise but NOT structured output', () => { type Options = OpenAIChatModelProviderOptionsByName['computer-use-preview'] - expectTypeOf().not.toExtend() + // Should have reasoning options with 'concise' summary support + expectTypeOf().toExtend() expectTypeOf().not.toExtend() expectTypeOf().toExtend() expectTypeOf().toExtend() @@ -698,13 +702,16 @@ describe('OpenAI Chat Model Provider Options Type Assertions', () => { >().toExtend() }) - it('standard models should NOT extend reasoning options but should extend structured output and tools', () => { + it('gpt-5-mini and gpt-5-nano should extend FullFeaturedOptions (reasoning + structured output + tools)', () => { expectTypeOf< OpenAIChatModelProviderOptionsByName['gpt-5-mini'] - >().toExtend() + >().toExtend() expectTypeOf< OpenAIChatModelProviderOptionsByName['gpt-5-nano'] - >().toExtend() + >().toExtend() + }) + + it('standard models should NOT extend reasoning options but should extend structured output and tools', () => { expectTypeOf< OpenAIChatModelProviderOptionsByName['gpt-4.1'] >().toExtend() @@ -719,9 +726,6 @@ describe('OpenAI Chat Model Provider Options Type Assertions', () => { >().toExtend() // Verify these do NOT extend reasoning options (discrimination already tested above) - expectTypeOf< - OpenAIChatModelProviderOptionsByName['gpt-5-mini'] - >().not.toExtend() expectTypeOf< OpenAIChatModelProviderOptionsByName['gpt-4.1'] >().not.toExtend()