@tanstack/ai 0.14.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/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/types.d.ts +8 -0
- package/package.json +15 -2
- 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/middlewares/index.ts +5 -0
- package/src/middlewares/otel.ts +861 -0
- package/src/types.ts +5 -0
|
@@ -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',
|
package/src/middlewares/index.ts
CHANGED
|
@@ -11,3 +11,8 @@ export {
|
|
|
11
11
|
type ContentGuardRule,
|
|
12
12
|
type ContentFilteredInfo,
|
|
13
13
|
} from './content-guard'
|
|
14
|
+
|
|
15
|
+
// otelMiddleware is exported from the dedicated subpath
|
|
16
|
+
// `@tanstack/ai/middlewares/otel` so that importing the main middlewares barrel
|
|
17
|
+
// does not eagerly require `@opentelemetry/api` (which is an optional peer
|
|
18
|
+
// dependency).
|