@tanstack/ai 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.js +61 -9
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +22 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +3 -2
- package/dist/esm/activities/chat/stream/message-updaters.js +7 -3
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +21 -5
- package/dist/esm/activities/chat/stream/processor.js +107 -15
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +4 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +2 -1
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/error-payload.d.ts +12 -0
- package/dist/esm/activities/error-payload.js +25 -0
- package/dist/esm/activities/error-payload.js.map +1 -0
- package/dist/esm/activities/generateAudio/adapter.d.ts +62 -0
- package/dist/esm/activities/generateAudio/adapter.js +14 -0
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -0
- package/dist/esm/activities/generateAudio/index.d.ts +74 -0
- package/dist/esm/activities/generateAudio/index.js +80 -0
- package/dist/esm/activities/generateAudio/index.js.map +1 -0
- package/dist/esm/activities/generateImage/adapter.d.ts +1 -1
- package/dist/esm/activities/generateImage/adapter.js +1 -1
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.d.ts +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +3 -3
- package/dist/esm/activities/generateSpeech/index.js +11 -0
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.d.ts +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +3 -3
- package/dist/esm/activities/generateTranscription/index.js +11 -0
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +7 -7
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +5 -2
- package/dist/esm/activities/index.js +11 -6
- package/dist/esm/activities/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +5 -6
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +1 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/adapter-internals.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/middlewares/otel.d.ts +75 -0
- package/dist/esm/middlewares/otel.js +624 -0
- package/dist/esm/middlewares/otel.js.map +1 -0
- package/dist/esm/stream-to-response.js +3 -8
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/types.d.ts +71 -6
- package/package.json +15 -2
- package/skills/ai-core/media-generation/SKILL.md +154 -10
- package/src/activities/chat/index.ts +74 -11
- package/src/activities/chat/messages.ts +23 -1
- package/src/activities/chat/stream/message-updaters.ts +10 -3
- package/src/activities/chat/stream/processor.ts +140 -17
- package/src/activities/chat/stream/types.ts +4 -1
- package/src/activities/chat/tools/tool-calls.ts +3 -1
- package/src/activities/error-payload.ts +35 -0
- package/src/activities/generateAudio/adapter.ts +89 -0
- package/src/activities/generateAudio/index.ts +224 -0
- package/src/activities/generateImage/adapter.ts +1 -1
- package/src/activities/generateSpeech/adapter.ts +1 -1
- package/src/activities/generateSpeech/index.ts +17 -7
- package/src/activities/generateTranscription/adapter.ts +1 -1
- package/src/activities/generateTranscription/index.ts +27 -4
- package/src/activities/generateVideo/index.ts +8 -8
- package/src/activities/index.ts +22 -0
- package/src/activities/stream-generation-result.ts +6 -7
- package/src/adapter-internals.ts +1 -0
- package/src/index.ts +4 -0
- package/src/middlewares/index.ts +5 -0
- package/src/middlewares/otel.ts +861 -0
- package/src/stream-to-response.ts +5 -10
- package/src/types.ts +79 -5
|
@@ -281,6 +281,10 @@ class TextEngine<
|
|
|
281
281
|
private totalChunkCount = 0
|
|
282
282
|
private currentMessageId: string | null = null
|
|
283
283
|
private accumulatedContent = ''
|
|
284
|
+
private accumulatedThinking: Array<{ content: string; signature?: string }> =
|
|
285
|
+
[]
|
|
286
|
+
private currentThinkingContent = ''
|
|
287
|
+
private currentThinkingSignature = ''
|
|
284
288
|
private eventOptions?: Record<string, unknown>
|
|
285
289
|
private eventToolNames?: Array<string>
|
|
286
290
|
private finishedEvent: RunFinishedEvent | null = null
|
|
@@ -556,6 +560,9 @@ class TextEngine<
|
|
|
556
560
|
private async beginIteration(): Promise<void> {
|
|
557
561
|
this.currentMessageId = this.createId('msg')
|
|
558
562
|
this.accumulatedContent = ''
|
|
563
|
+
this.accumulatedThinking = []
|
|
564
|
+
this.currentThinkingContent = ''
|
|
565
|
+
this.currentThinkingSignature = ''
|
|
559
566
|
this.finishedEvent = null
|
|
560
567
|
|
|
561
568
|
// Update mutable context fields
|
|
@@ -666,6 +673,9 @@ class TextEngine<
|
|
|
666
673
|
case 'RUN_ERROR':
|
|
667
674
|
this.handleRunErrorEvent(chunk)
|
|
668
675
|
break
|
|
676
|
+
case 'STEP_STARTED':
|
|
677
|
+
this.handleStepStartedEvent()
|
|
678
|
+
break
|
|
669
679
|
case 'STEP_FINISHED':
|
|
670
680
|
this.handleStepFinishedEvent(chunk)
|
|
671
681
|
break
|
|
@@ -683,7 +693,7 @@ class TextEngine<
|
|
|
683
693
|
break
|
|
684
694
|
|
|
685
695
|
default:
|
|
686
|
-
// RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END,
|
|
696
|
+
// RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END,
|
|
687
697
|
// STATE_SNAPSHOT, STATE_DELTA, CUSTOM
|
|
688
698
|
// - no special handling needed in chat activity
|
|
689
699
|
break
|
|
@@ -726,10 +736,32 @@ class TextEngine<
|
|
|
726
736
|
this.earlyTermination = true
|
|
727
737
|
}
|
|
728
738
|
|
|
739
|
+
private finalizeCurrentThinkingStep(): void {
|
|
740
|
+
if (this.currentThinkingContent) {
|
|
741
|
+
this.accumulatedThinking.push({
|
|
742
|
+
content: this.currentThinkingContent,
|
|
743
|
+
...(this.currentThinkingSignature && {
|
|
744
|
+
signature: this.currentThinkingSignature,
|
|
745
|
+
}),
|
|
746
|
+
})
|
|
747
|
+
this.currentThinkingContent = ''
|
|
748
|
+
this.currentThinkingSignature = ''
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private handleStepStartedEvent(): void {
|
|
753
|
+
this.finalizeCurrentThinkingStep()
|
|
754
|
+
}
|
|
755
|
+
|
|
729
756
|
private handleStepFinishedEvent(
|
|
730
|
-
|
|
757
|
+
chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,
|
|
731
758
|
): void {
|
|
732
|
-
|
|
759
|
+
if (chunk.delta) {
|
|
760
|
+
this.currentThinkingContent += chunk.delta
|
|
761
|
+
}
|
|
762
|
+
if (chunk.signature) {
|
|
763
|
+
this.currentThinkingSignature = chunk.signature
|
|
764
|
+
}
|
|
733
765
|
}
|
|
734
766
|
|
|
735
767
|
private async *checkForPendingToolCalls(): AsyncGenerator<
|
|
@@ -1057,12 +1089,17 @@ class TextEngine<
|
|
|
1057
1089
|
}
|
|
1058
1090
|
|
|
1059
1091
|
private addAssistantToolCallMessage(toolCalls: Array<ToolCall>): void {
|
|
1092
|
+
this.finalizeCurrentThinkingStep()
|
|
1093
|
+
|
|
1060
1094
|
this.messages = [
|
|
1061
1095
|
...this.messages,
|
|
1062
1096
|
{
|
|
1063
1097
|
role: 'assistant',
|
|
1064
1098
|
content: this.accumulatedContent || null,
|
|
1065
1099
|
toolCalls,
|
|
1100
|
+
...(this.accumulatedThinking.length > 0 && {
|
|
1101
|
+
thinking: this.accumulatedThinking,
|
|
1102
|
+
}),
|
|
1066
1103
|
},
|
|
1067
1104
|
]
|
|
1068
1105
|
}
|
|
@@ -1213,6 +1250,7 @@ class TextEngine<
|
|
|
1213
1250
|
timestamp: Date.now(),
|
|
1214
1251
|
model: finishEvent.model,
|
|
1215
1252
|
toolCallId: result.toolCallId,
|
|
1253
|
+
toolCallName: result.toolName,
|
|
1216
1254
|
toolName: result.toolName,
|
|
1217
1255
|
} as StreamChunk)
|
|
1218
1256
|
|
|
@@ -1248,14 +1286,39 @@ class TextEngine<
|
|
|
1248
1286
|
role: 'tool',
|
|
1249
1287
|
} as StreamChunk)
|
|
1250
1288
|
|
|
1251
|
-
this
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1289
|
+
// If a placeholder tool message exists for this toolCallId (created by
|
|
1290
|
+
// uiMessageToModelMessages for an approval-responded part with no
|
|
1291
|
+
// output yet), replace it with the real result. Otherwise the LLM sees
|
|
1292
|
+
// both messages — and since the Anthropic adapter dedupes tool_result
|
|
1293
|
+
// blocks by tool_use_id keeping the first match, the placeholder wins
|
|
1294
|
+
// and the real result is dropped (see issue #532).
|
|
1295
|
+
const placeholderIdx = this.messages.findIndex((m) => {
|
|
1296
|
+
if (m.role !== 'tool' || m.toolCallId !== result.toolCallId) {
|
|
1297
|
+
return false
|
|
1298
|
+
}
|
|
1299
|
+
if (typeof m.content !== 'string') return false
|
|
1300
|
+
try {
|
|
1301
|
+
return JSON.parse(m.content)?.pendingExecution === true
|
|
1302
|
+
} catch {
|
|
1303
|
+
return false
|
|
1304
|
+
}
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
const newToolMessage: ModelMessage = {
|
|
1308
|
+
role: 'tool',
|
|
1309
|
+
content,
|
|
1310
|
+
toolCallId: result.toolCallId,
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (placeholderIdx >= 0) {
|
|
1314
|
+
this.messages = [
|
|
1315
|
+
...this.messages.slice(0, placeholderIdx),
|
|
1316
|
+
newToolMessage,
|
|
1317
|
+
...this.messages.slice(placeholderIdx + 1),
|
|
1318
|
+
]
|
|
1319
|
+
} else {
|
|
1320
|
+
this.messages = [...this.messages, newToolMessage]
|
|
1321
|
+
}
|
|
1259
1322
|
}
|
|
1260
1323
|
|
|
1261
1324
|
return chunks
|
|
@@ -165,6 +165,7 @@ function isToolCallIncluded(part: ToolCallPart): boolean {
|
|
|
165
165
|
function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
166
166
|
const messageList: Array<ModelMessage> = []
|
|
167
167
|
let current = createSegment()
|
|
168
|
+
let pendingThinking: Array<{ content: string; signature?: string }> = []
|
|
168
169
|
|
|
169
170
|
// Track emitted tool result IDs to avoid duplicates.
|
|
170
171
|
// A tool call can have BOTH an explicit tool-result part AND an output
|
|
@@ -181,7 +182,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
181
182
|
role: 'assistant',
|
|
182
183
|
content,
|
|
183
184
|
...(hasToolCalls && { toolCalls: current.toolCalls }),
|
|
185
|
+
...(pendingThinking.length > 0 && { thinking: pendingThinking }),
|
|
184
186
|
})
|
|
187
|
+
pendingThinking = []
|
|
185
188
|
}
|
|
186
189
|
current = createSegment()
|
|
187
190
|
}
|
|
@@ -227,7 +230,15 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
227
230
|
}
|
|
228
231
|
break
|
|
229
232
|
|
|
230
|
-
|
|
233
|
+
case 'thinking':
|
|
234
|
+
if (part.content) {
|
|
235
|
+
pendingThinking.push({
|
|
236
|
+
content: part.content,
|
|
237
|
+
...(part.signature && { signature: part.signature }),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
break
|
|
241
|
+
|
|
231
242
|
default:
|
|
232
243
|
break
|
|
233
244
|
}
|
|
@@ -306,6 +317,17 @@ export function modelMessageToUIMessage(
|
|
|
306
317
|
): UIMessage {
|
|
307
318
|
const parts: Array<MessagePart> = []
|
|
308
319
|
|
|
320
|
+
if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {
|
|
321
|
+
for (const thinking of modelMessage.thinking) {
|
|
322
|
+
if (!thinking.content) continue
|
|
323
|
+
parts.push({
|
|
324
|
+
type: 'thinking',
|
|
325
|
+
content: thinking.content,
|
|
326
|
+
...(thinking.signature && { signature: thinking.signature }),
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
309
331
|
// Handle tool results (when role is "tool") - only produce tool-result part,
|
|
310
332
|
// not a text part (the content IS the tool result, not display text)
|
|
311
333
|
if (modelMessage.role === 'tool' && modelMessage.toolCallId) {
|
|
@@ -244,12 +244,15 @@ export function updateToolCallApprovalResponse(
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
* Update or add a thinking part to a message.
|
|
247
|
+
* Update or add a thinking part to a message, keyed by stepId.
|
|
248
|
+
* Each distinct stepId produces its own ThinkingPart.
|
|
248
249
|
*/
|
|
249
250
|
export function updateThinkingPart(
|
|
250
251
|
messages: Array<UIMessage>,
|
|
251
252
|
messageId: string,
|
|
253
|
+
stepId: string,
|
|
252
254
|
content: string,
|
|
255
|
+
signature?: string,
|
|
253
256
|
): Array<UIMessage> {
|
|
254
257
|
return messages.map((msg) => {
|
|
255
258
|
if (msg.id !== messageId) {
|
|
@@ -257,15 +260,19 @@ export function updateThinkingPart(
|
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
const parts = [...msg.parts]
|
|
260
|
-
const thinkingPartIndex = parts.findIndex(
|
|
263
|
+
const thinkingPartIndex = parts.findIndex(
|
|
264
|
+
(p) => p.type === 'thinking' && p.stepId === stepId,
|
|
265
|
+
)
|
|
261
266
|
|
|
262
267
|
const thinkingPart: ThinkingPart = {
|
|
263
268
|
type: 'thinking',
|
|
264
269
|
content,
|
|
270
|
+
stepId,
|
|
271
|
+
...(signature && { signature }),
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
if (thinkingPartIndex >= 0) {
|
|
268
|
-
// Update existing thinking part
|
|
275
|
+
// Update existing thinking part for this step
|
|
269
276
|
parts[thinkingPartIndex] = thinkingPart
|
|
270
277
|
} else {
|
|
271
278
|
// Add new thinking part at the end (preserve natural streaming order)
|
|
@@ -90,7 +90,11 @@ export interface StreamProcessorEvents {
|
|
|
90
90
|
state: ToolCallState,
|
|
91
91
|
args: string,
|
|
92
92
|
) => void
|
|
93
|
-
onThinkingUpdate?: (
|
|
93
|
+
onThinkingUpdate?: (
|
|
94
|
+
messageId: string,
|
|
95
|
+
stepId: string,
|
|
96
|
+
content: string,
|
|
97
|
+
) => void
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
/**
|
|
@@ -139,6 +143,7 @@ export class StreamProcessor {
|
|
|
139
143
|
private activeMessageIds: Set<string> = new Set()
|
|
140
144
|
private toolCallToMessage: Map<string, string> = new Map()
|
|
141
145
|
private pendingManualMessageId: string | null = null
|
|
146
|
+
private pendingThinkingStepId: string | null = null
|
|
142
147
|
|
|
143
148
|
// Run tracking (for concurrent run safety)
|
|
144
149
|
private activeRuns = new Set<string>()
|
|
@@ -541,8 +546,14 @@ export class StreamProcessor {
|
|
|
541
546
|
)
|
|
542
547
|
break
|
|
543
548
|
|
|
549
|
+
case 'STEP_STARTED':
|
|
550
|
+
this.handleStepStartedEvent(
|
|
551
|
+
chunk as Extract<StreamChunk, { type: 'STEP_STARTED' }>,
|
|
552
|
+
)
|
|
553
|
+
break
|
|
554
|
+
|
|
544
555
|
default:
|
|
545
|
-
//
|
|
556
|
+
// STATE_SNAPSHOT, STATE_DELTA - no special handling needed
|
|
546
557
|
break
|
|
547
558
|
}
|
|
548
559
|
}
|
|
@@ -564,8 +575,11 @@ export class StreamProcessor {
|
|
|
564
575
|
totalTextContent: '',
|
|
565
576
|
currentSegmentText: '',
|
|
566
577
|
lastEmittedText: '',
|
|
567
|
-
thinkingContent: '',
|
|
568
578
|
hasSeenReasoningEvents: false,
|
|
579
|
+
thinkingSteps: new Map(),
|
|
580
|
+
thinkingStepSignatures: new Map(),
|
|
581
|
+
thinkingStepOrder: [],
|
|
582
|
+
currentThinkingStepId: null,
|
|
569
583
|
toolCalls: new Map(),
|
|
570
584
|
toolCallOrder: [],
|
|
571
585
|
hasToolCallsSinceTextStart: false,
|
|
@@ -582,6 +596,23 @@ export class StreamProcessor {
|
|
|
582
596
|
return this.messageStates.get(messageId)
|
|
583
597
|
}
|
|
584
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Promote a pending stepId from a STEP_STARTED that fired before the
|
|
601
|
+
* assistant message existed onto the given message state, so the next
|
|
602
|
+
* thinking event (STEP_FINISHED or REASONING_MESSAGE_CONTENT) attributes
|
|
603
|
+
* to the correct step.
|
|
604
|
+
*/
|
|
605
|
+
private consumePendingThinkingStep(state: MessageStreamState): void {
|
|
606
|
+
if (!this.pendingThinkingStepId) return
|
|
607
|
+
const stepId = this.pendingThinkingStepId
|
|
608
|
+
state.currentThinkingStepId = stepId
|
|
609
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
610
|
+
state.thinkingSteps.set(stepId, '')
|
|
611
|
+
state.thinkingStepOrder.push(stepId)
|
|
612
|
+
}
|
|
613
|
+
this.pendingThinkingStepId = null
|
|
614
|
+
}
|
|
615
|
+
|
|
585
616
|
/**
|
|
586
617
|
* Get the most recent active assistant message ID.
|
|
587
618
|
* Used as fallback for events that don't include a messageId.
|
|
@@ -897,7 +928,13 @@ export class StreamProcessor {
|
|
|
897
928
|
// New tool call starting
|
|
898
929
|
const initialState: ToolCallState = 'awaiting-input'
|
|
899
930
|
|
|
900
|
-
|
|
931
|
+
// `toolName` is a deprecated alias for `toolCallName` (see ToolCallStartEvent
|
|
932
|
+
// in types.ts). Accept either so chunks from older code paths or any
|
|
933
|
+
// adapter that only sets the deprecated field still produce a named part.
|
|
934
|
+
// The type marks both as required strings, but in practice some emitters
|
|
935
|
+
// only set one — fall back via the runtime value rather than the type.
|
|
936
|
+
const toolName =
|
|
937
|
+
(chunk as { toolCallName?: string }).toolCallName ?? chunk.toolName
|
|
901
938
|
|
|
902
939
|
const newToolCall: InternalToolCallState = {
|
|
903
940
|
id: chunk.toolCallId,
|
|
@@ -1155,11 +1192,45 @@ export class StreamProcessor {
|
|
|
1155
1192
|
this.events.onError?.(new Error(errorMessage))
|
|
1156
1193
|
}
|
|
1157
1194
|
|
|
1195
|
+
/**
|
|
1196
|
+
* Handle STEP_STARTED event (for thinking/reasoning content).
|
|
1197
|
+
*
|
|
1198
|
+
* Records the stepId so that subsequent STEP_FINISHED deltas accumulate
|
|
1199
|
+
* into their own ThinkingPart. Does not create a message — the message
|
|
1200
|
+
* is lazily created when the first STEP_FINISHED content arrives.
|
|
1201
|
+
*/
|
|
1202
|
+
private handleStepStartedEvent(
|
|
1203
|
+
chunk: Extract<StreamChunk, { type: 'STEP_STARTED' }>,
|
|
1204
|
+
): void {
|
|
1205
|
+
const stepId = chunk.stepId ?? generateMessageId()
|
|
1206
|
+
const activeId = this.getActiveAssistantMessageId()
|
|
1207
|
+
if (activeId) {
|
|
1208
|
+
const state = this.getMessageState(activeId)
|
|
1209
|
+
if (state) {
|
|
1210
|
+
state.currentThinkingStepId = stepId
|
|
1211
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
1212
|
+
state.thinkingSteps.set(stepId, '')
|
|
1213
|
+
state.thinkingStepOrder.push(stepId)
|
|
1214
|
+
}
|
|
1215
|
+
// Clear any pending stepId from a prior STEP_STARTED that fired
|
|
1216
|
+
// before the assistant message existed. Now that we're tracking
|
|
1217
|
+
// the step directly on message state, the pending value is stale
|
|
1218
|
+
// and must not leak into the next STEP_FINISHED (which would
|
|
1219
|
+
// misattribute its delta to the stale step).
|
|
1220
|
+
this.pendingThinkingStepId = null
|
|
1221
|
+
return
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// No active message yet — defer until ensureAssistantMessage in STEP_FINISHED
|
|
1226
|
+
this.pendingThinkingStepId = stepId
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1158
1229
|
/**
|
|
1159
1230
|
* Handle STEP_FINISHED event (for thinking/reasoning content).
|
|
1160
1231
|
*
|
|
1161
|
-
* Accumulates delta into
|
|
1162
|
-
*
|
|
1232
|
+
* Accumulates delta into the current thinking step's content and updates
|
|
1233
|
+
* the corresponding ThinkingPart in the UIMessage.
|
|
1163
1234
|
*
|
|
1164
1235
|
* @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow
|
|
1165
1236
|
*/
|
|
@@ -1175,10 +1246,38 @@ export class StreamProcessor {
|
|
|
1175
1246
|
// REASONING_MESSAGE_CONTENT events for this message, skip the duplicate
|
|
1176
1247
|
// thinking content from STEP_FINISHED to avoid doubled content.
|
|
1177
1248
|
if (state.hasSeenReasoningEvents) {
|
|
1249
|
+
if (chunk.signature) {
|
|
1250
|
+
const stepId = state.currentThinkingStepId ?? chunk.stepId
|
|
1251
|
+
if (!stepId) return
|
|
1252
|
+
const thinking = state.thinkingSteps.get(stepId)
|
|
1253
|
+
if (thinking !== undefined) {
|
|
1254
|
+
state.thinkingStepSignatures.set(stepId, chunk.signature)
|
|
1255
|
+
this.messages = updateThinkingPart(
|
|
1256
|
+
this.messages,
|
|
1257
|
+
messageId,
|
|
1258
|
+
stepId,
|
|
1259
|
+
thinking,
|
|
1260
|
+
chunk.signature,
|
|
1261
|
+
)
|
|
1262
|
+
this.emitMessagesChange()
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1178
1265
|
return
|
|
1179
1266
|
}
|
|
1180
1267
|
|
|
1181
|
-
|
|
1268
|
+
this.consumePendingThinkingStep(state)
|
|
1269
|
+
|
|
1270
|
+
const stepId =
|
|
1271
|
+
state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId()
|
|
1272
|
+
|
|
1273
|
+
// Auto-initialize if no prior STEP_STARTED (backward compat)
|
|
1274
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
1275
|
+
state.thinkingSteps.set(stepId, '')
|
|
1276
|
+
state.thinkingStepOrder.push(stepId)
|
|
1277
|
+
state.currentThinkingStepId = stepId
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const previous = state.thinkingSteps.get(stepId)!
|
|
1182
1281
|
let nextThinking = previous
|
|
1183
1282
|
|
|
1184
1283
|
// Prefer delta over content
|
|
@@ -1194,25 +1293,31 @@ export class StreamProcessor {
|
|
|
1194
1293
|
}
|
|
1195
1294
|
}
|
|
1196
1295
|
|
|
1197
|
-
state.
|
|
1296
|
+
state.thinkingSteps.set(stepId, nextThinking)
|
|
1297
|
+
|
|
1298
|
+
if (chunk.signature) {
|
|
1299
|
+
state.thinkingStepSignatures.set(stepId, chunk.signature)
|
|
1300
|
+
}
|
|
1198
1301
|
|
|
1199
1302
|
// Update UIMessage
|
|
1200
1303
|
this.messages = updateThinkingPart(
|
|
1201
1304
|
this.messages,
|
|
1202
1305
|
messageId,
|
|
1203
|
-
|
|
1306
|
+
stepId,
|
|
1307
|
+
nextThinking,
|
|
1308
|
+
state.thinkingStepSignatures.get(stepId),
|
|
1204
1309
|
)
|
|
1205
1310
|
this.emitMessagesChange()
|
|
1206
1311
|
|
|
1207
1312
|
// Emit granular event
|
|
1208
|
-
this.events.onThinkingUpdate?.(messageId,
|
|
1313
|
+
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)
|
|
1209
1314
|
}
|
|
1210
1315
|
|
|
1211
1316
|
/**
|
|
1212
1317
|
* Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).
|
|
1213
1318
|
*
|
|
1214
|
-
* Accumulates reasoning delta into
|
|
1215
|
-
* in the UIMessage.
|
|
1319
|
+
* Accumulates reasoning delta into thinking content and updates the
|
|
1320
|
+
* corresponding ThinkingPart in the UIMessage.
|
|
1216
1321
|
*/
|
|
1217
1322
|
private handleReasoningMessageContentEvent(
|
|
1218
1323
|
chunk: Extract<StreamChunk, { type: 'REASONING_MESSAGE_CONTENT' }>,
|
|
@@ -1223,16 +1328,29 @@ export class StreamProcessor {
|
|
|
1223
1328
|
|
|
1224
1329
|
state.hasSeenReasoningEvents = true
|
|
1225
1330
|
const delta = chunk.delta || ''
|
|
1226
|
-
|
|
1331
|
+
|
|
1332
|
+
this.consumePendingThinkingStep(state)
|
|
1333
|
+
|
|
1334
|
+
const stepId = state.currentThinkingStepId ?? chunk.messageId
|
|
1335
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
1336
|
+
state.thinkingSteps.set(stepId, '')
|
|
1337
|
+
state.thinkingStepOrder.push(stepId)
|
|
1338
|
+
state.currentThinkingStepId = stepId
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const nextThinking = (state.thinkingSteps.get(stepId) ?? '') + delta
|
|
1342
|
+
state.thinkingSteps.set(stepId, nextThinking)
|
|
1227
1343
|
|
|
1228
1344
|
this.messages = updateThinkingPart(
|
|
1229
1345
|
this.messages,
|
|
1230
1346
|
messageId,
|
|
1231
|
-
|
|
1347
|
+
stepId,
|
|
1348
|
+
nextThinking,
|
|
1349
|
+
state.thinkingStepSignatures.get(stepId),
|
|
1232
1350
|
)
|
|
1233
1351
|
this.emitMessagesChange()
|
|
1234
1352
|
|
|
1235
|
-
this.events.onThinkingUpdate?.(messageId,
|
|
1353
|
+
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking)
|
|
1236
1354
|
}
|
|
1237
1355
|
|
|
1238
1356
|
/**
|
|
@@ -1518,7 +1636,9 @@ export class StreamProcessor {
|
|
|
1518
1636
|
|
|
1519
1637
|
for (const state of this.messageStates.values()) {
|
|
1520
1638
|
content += state.totalTextContent
|
|
1521
|
-
|
|
1639
|
+
for (const stepId of state.thinkingStepOrder) {
|
|
1640
|
+
thinking += state.thinkingSteps.get(stepId) ?? ''
|
|
1641
|
+
}
|
|
1522
1642
|
}
|
|
1523
1643
|
|
|
1524
1644
|
return {
|
|
@@ -1540,7 +1660,9 @@ export class StreamProcessor {
|
|
|
1540
1660
|
|
|
1541
1661
|
for (const state of this.messageStates.values()) {
|
|
1542
1662
|
content += state.totalTextContent
|
|
1543
|
-
|
|
1663
|
+
for (const stepId of state.thinkingStepOrder) {
|
|
1664
|
+
thinking += state.thinkingSteps.get(stepId) ?? ''
|
|
1665
|
+
}
|
|
1544
1666
|
for (const [id, tc] of state.toolCalls) {
|
|
1545
1667
|
toolCalls.set(id, tc)
|
|
1546
1668
|
}
|
|
@@ -1586,6 +1708,7 @@ export class StreamProcessor {
|
|
|
1586
1708
|
this.activeRuns.clear()
|
|
1587
1709
|
this.toolCallToMessage.clear()
|
|
1588
1710
|
this.pendingManualMessageId = null
|
|
1711
|
+
this.pendingThinkingStepId = null
|
|
1589
1712
|
this.finishReason = null
|
|
1590
1713
|
this.hasError = false
|
|
1591
1714
|
this.isDone = false
|
|
@@ -56,8 +56,11 @@ export interface MessageStreamState {
|
|
|
56
56
|
totalTextContent: string
|
|
57
57
|
currentSegmentText: string
|
|
58
58
|
lastEmittedText: string
|
|
59
|
-
thinkingContent: string
|
|
60
59
|
hasSeenReasoningEvents: boolean
|
|
60
|
+
thinkingSteps: Map<string, string>
|
|
61
|
+
thinkingStepSignatures: Map<string, string>
|
|
62
|
+
thinkingStepOrder: Array<string>
|
|
63
|
+
currentThinkingStepId: string | null
|
|
61
64
|
toolCalls: Map<string, InternalToolCallState>
|
|
62
65
|
toolCallOrder: Array<string>
|
|
63
66
|
hasToolCallsSinceTextStart: boolean
|
|
@@ -93,7 +93,9 @@ export class ToolCallManager {
|
|
|
93
93
|
*/
|
|
94
94
|
addToolCallStartEvent(event: ToolCallStartEvent): void {
|
|
95
95
|
const index = event.index ?? this.toolCallsMap.size
|
|
96
|
-
const
|
|
96
|
+
const runtimeEvent = event as Partial<ToolCallStartEvent> &
|
|
97
|
+
Pick<ToolCallStartEvent, 'toolName'>
|
|
98
|
+
const name = runtimeEvent.toolCallName ?? runtimeEvent.toolName
|
|
97
99
|
this.toolCallsMap.set(index, {
|
|
98
100
|
id: event.toolCallId,
|
|
99
101
|
type: 'function',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared error-narrowing helper for activities that convert thrown values
|
|
3
|
+
* into structured `RUN_ERROR` events.
|
|
4
|
+
*
|
|
5
|
+
* Accepts Error instances, objects with string-ish `message`/`code`, or bare
|
|
6
|
+
* strings; always returns a shape safe to serialize. Never leaks the full
|
|
7
|
+
* error object (which may carry request/response state from an SDK).
|
|
8
|
+
*/
|
|
9
|
+
export function toRunErrorPayload(
|
|
10
|
+
error: unknown,
|
|
11
|
+
fallbackMessage = 'Unknown error occurred',
|
|
12
|
+
): { message: string; code: string | undefined } {
|
|
13
|
+
if (error instanceof Error) {
|
|
14
|
+
const codeField = (error as Error & { code?: unknown }).code
|
|
15
|
+
return {
|
|
16
|
+
message: error.message || fallbackMessage,
|
|
17
|
+
code: typeof codeField === 'string' ? codeField : undefined,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (typeof error === 'object' && error !== null) {
|
|
21
|
+
const messageField = (error as { message?: unknown }).message
|
|
22
|
+
const codeField = (error as { code?: unknown }).code
|
|
23
|
+
return {
|
|
24
|
+
message:
|
|
25
|
+
typeof messageField === 'string' && messageField.length > 0
|
|
26
|
+
? messageField
|
|
27
|
+
: fallbackMessage,
|
|
28
|
+
code: typeof codeField === 'string' ? codeField : undefined,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (typeof error === 'string' && error.length > 0) {
|
|
32
|
+
return { message: error, code: undefined }
|
|
33
|
+
}
|
|
34
|
+
return { message: fallbackMessage, code: undefined }
|
|
35
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AudioGenerationOptions, AudioGenerationResult } from '../../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for audio generation adapter instances
|
|
5
|
+
*/
|
|
6
|
+
export interface AudioAdapterConfig {
|
|
7
|
+
apiKey?: string
|
|
8
|
+
baseUrl?: string
|
|
9
|
+
timeout?: number
|
|
10
|
+
maxRetries?: number
|
|
11
|
+
headers?: Record<string, string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Audio generation adapter interface with pre-resolved generics.
|
|
16
|
+
*
|
|
17
|
+
* An adapter is created by a provider function: `provider('model')` → `adapter`
|
|
18
|
+
* All type resolution happens at the provider call site, not in this interface.
|
|
19
|
+
*
|
|
20
|
+
* Generic parameters:
|
|
21
|
+
* - TModel: The specific model name (e.g., 'fal-ai/diffrhythm')
|
|
22
|
+
* - TProviderOptions: Provider-specific options (already resolved)
|
|
23
|
+
*/
|
|
24
|
+
export interface AudioAdapter<
|
|
25
|
+
TModel extends string = string,
|
|
26
|
+
TProviderOptions extends object = Record<string, unknown>,
|
|
27
|
+
> {
|
|
28
|
+
/** Discriminator for adapter kind - used to determine API shape */
|
|
29
|
+
readonly kind: 'audio'
|
|
30
|
+
/** Adapter name identifier */
|
|
31
|
+
readonly name: string
|
|
32
|
+
/** The model this adapter is configured for */
|
|
33
|
+
readonly model: TModel
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @internal Type-only properties for inference. Not assigned at runtime.
|
|
37
|
+
*/
|
|
38
|
+
'~types': {
|
|
39
|
+
providerOptions: TProviderOptions
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate audio from a text prompt
|
|
44
|
+
*/
|
|
45
|
+
generateAudio: (
|
|
46
|
+
options: AudioGenerationOptions<TProviderOptions>,
|
|
47
|
+
) => Promise<AudioGenerationResult>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* An AudioAdapter with any/unknown type parameters.
|
|
52
|
+
* Useful as a constraint in generic functions and interfaces.
|
|
53
|
+
*/
|
|
54
|
+
export type AnyAudioAdapter = AudioAdapter<any, any>
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Abstract base class for audio generation adapters.
|
|
58
|
+
* Extend this class to implement an audio adapter for a specific provider.
|
|
59
|
+
*
|
|
60
|
+
* Generic parameters match AudioAdapter - all pre-resolved by the provider function.
|
|
61
|
+
*/
|
|
62
|
+
export abstract class BaseAudioAdapter<
|
|
63
|
+
TModel extends string = string,
|
|
64
|
+
TProviderOptions extends object = Record<string, unknown>,
|
|
65
|
+
> implements AudioAdapter<TModel, TProviderOptions> {
|
|
66
|
+
readonly kind = 'audio' as const
|
|
67
|
+
abstract readonly name: string
|
|
68
|
+
readonly model: TModel
|
|
69
|
+
|
|
70
|
+
// Type-only property - never assigned at runtime
|
|
71
|
+
declare '~types': {
|
|
72
|
+
providerOptions: TProviderOptions
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected config: AudioAdapterConfig
|
|
76
|
+
|
|
77
|
+
constructor(model: TModel, config: AudioAdapterConfig = {}) {
|
|
78
|
+
this.config = config
|
|
79
|
+
this.model = model
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
abstract generateAudio(
|
|
83
|
+
options: AudioGenerationOptions<TProviderOptions>,
|
|
84
|
+
): Promise<AudioGenerationResult>
|
|
85
|
+
|
|
86
|
+
protected generateId(): string {
|
|
87
|
+
return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
88
|
+
}
|
|
89
|
+
}
|