Reasoning in LanguageModelV3 is text-only (text: string). Some providers produce non-text reasoning (images, files, audio). In theory even tool calls could be used in reasoning (as the model "thinks" about making tool calls). Either way, it probably makes sense to decouple reasoning from the requirement of being text in our spec.
Some alternative ideas:
Option 1: New Dedicated Types (e.g. reasoning-file)
Add new type discriminants prefixed with reasoning- for each non-text format.
// New union member in LanguageModelV4Content
{ type: 'reasoning-file'; mediaType: string; data: string | Uint8Array; }
// New stream part
{ type: 'reasoning-file'; mediaType: string; data: string | Uint8Array; }
Option 2: Reasoning Flag on Existing Types
Add reasoning?: boolean to existing content/stream types. Existing type: 'reasoning' is removed / deprecated.
// Existing types gain a flag
{ type: 'text'; text: string; reasoning?: boolean; }
{ type: 'file'; mediaType: string; data: string | Uint8Array; reasoning?: boolean; }
// Stream parts gain a flag
{ type: 'text-start'; id: string; reasoning?: boolean; }
{ type: 'file'; mediaType: string; data: ...; reasoning?: boolean; }
Alternatively, we could do something like this for type safety:
type WithReasoning<T> = T & { reasoning: true };
type ReasoningText = WithReasoning<{ type: 'text'; text: string }>;
Option 3: Separate reasoning Array
Store reasoning in a dedicated reasoning field using the same content types. Existing type: 'reasoning' is removed / deprecated.
type LanguageModelV4GenerateResult = {
content: Array<LanguageModelV4Content>;
reasoning: Array<LanguageModelV4Content>; // NEW
// ...
};
Comparison
|
Option 1: New Types |
Option 2: Flag |
Option 3: Separate Array |
| Backward compat |
Non-breaking, purely additive union members |
Breaking: existing reasoning type/stream parts become redundant |
Breaking: all providers must return new reasoning field |
| Provider burden |
Highest: must emit distinct types for reasoning vs non-reasoning content |
Lowest: just set reasoning: true on existing types |
Medium: must split output into two arrays |
| Future extensibility |
Each new content type needs a reasoning-* variant |
Automatic: any content type can carry the flag |
Automatic: reasoning array accepts all content types |
| Type safety |
Best: single-field narrowing on type |
Good: two-field narrowing on type + reasoning |
Good: same content types, but can't rely on part data alone |
| Filtering reasoning |
Must match multiple type strings (reasoning, reasoning-file, ...) |
Single check: part.reasoning === true |
Field access: result.reasoning |
| Public result type alignment |
Matches current ContentPart union pattern (add { type: 'reasoning-file' } alongside { type: 'reasoning' }) |
Requires reworking ReasoningOutput and ContentPart since type: 'reasoning' would be replaced |
Closest match: public API already has a separate result.reasoning field |
| Input prompt changes |
Parallel new part types needed (ReasoningFilePart, ...) |
Flag on existing input parts, mirrors output |
Restructure assistant messages or use mixed approach |
I'm unsure which option I prefer. Option 1 definitely feels easiest from a back compat / migration perspective, though that doesn't necessarily mean it's what we should go for. Curious what you think.
Reasoning in
LanguageModelV3is text-only (text: string). Some providers produce non-text reasoning (images, files, audio). In theory even tool calls could be used in reasoning (as the model "thinks" about making tool calls). Either way, it probably makes sense to decouple reasoning from the requirement of being text in our spec.Some alternative ideas:
Option 1: New Dedicated Types (e.g.
reasoning-file)Add new
typediscriminants prefixed withreasoning-for each non-text format.Option 2: Reasoning Flag on Existing Types
Add
reasoning?: booleanto existing content/stream types. Existingtype: 'reasoning'is removed / deprecated.Alternatively, we could do something like this for type safety:
Option 3: Separate
reasoningArrayStore reasoning in a dedicated
reasoningfield using the same content types. Existingtype: 'reasoning'is removed / deprecated.Comparison
reasoningtype/stream parts become redundantreasoningfieldreasoning: trueon existing typesreasoning-*varianttypetype+reasoningreasoning,reasoning-file, ...)part.reasoning === trueresult.reasoningContentPartunion pattern (add{ type: 'reasoning-file' }alongside{ type: 'reasoning' })ReasoningOutputandContentPartsincetype: 'reasoning'would be replacedresult.reasoningfieldReasoningFilePart, ...)I'm unsure which option I prefer. Option 1 definitely feels easiest from a back compat / migration perspective, though that doesn't necessarily mean it's what we should go for. Curious what you think.