@tanstack/ai 0.49.1 → 0.51.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 +4 -4
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +7 -0
- package/dist/esm/activities/chat/messages.js +270 -64
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +2 -0
- package/dist/esm/activities/chat/stream/processor.js +46 -19
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/client.js +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +4 -3
- package/dist/esm/types.d.ts +18 -0
- package/dist/esm/utilities/ag-ui-wire.d.ts +20 -44
- package/dist/esm/utilities/ag-ui-wire.js +143 -34
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/errors.d.ts +27 -0
- package/dist/esm/utilities/errors.js +19 -1
- package/dist/esm/utilities/errors.js.map +1 -1
- package/package.json +2 -2
- package/skills/ai-core/SKILL.md +17 -0
- package/src/activities/chat/index.ts +3 -4
- package/src/activities/chat/messages.ts +344 -61
- package/src/activities/chat/stream/processor.ts +70 -16
- package/src/index.ts +2 -0
- package/src/types.ts +18 -0
- package/src/utilities/ag-ui-wire.ts +278 -73
- package/src/utilities/errors.ts +42 -0
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import {
|
|
21
21
|
aguiSnapshotMessageToUIMessage,
|
|
22
|
+
coerceCreatedAt,
|
|
22
23
|
generateMessageId,
|
|
23
24
|
uiMessageToModelMessages,
|
|
24
25
|
} from '../messages.js'
|
|
@@ -211,6 +212,7 @@ export class StreamProcessor {
|
|
|
211
212
|
private finishReason: string | null = null
|
|
212
213
|
private hasError = false
|
|
213
214
|
private isDone = false
|
|
215
|
+
private streamEndEmitted = false
|
|
214
216
|
|
|
215
217
|
// Recording
|
|
216
218
|
private recording: ChunkRecording | null = null
|
|
@@ -728,9 +730,26 @@ export class StreamProcessor {
|
|
|
728
730
|
return id
|
|
729
731
|
}
|
|
730
732
|
}
|
|
733
|
+
// finalizeStream() clears activeMessageIds but keeps messageStates.
|
|
734
|
+
// Leftover reasoning after an early RUN_FINISHED must resume that
|
|
735
|
+
// assistant. A new user turn calls prepareAssistantMessage(), which
|
|
736
|
+
// clears messageStates first.
|
|
737
|
+
for (const [id, state] of [...this.messageStates].reverse()) {
|
|
738
|
+
if (state.role === 'assistant') {
|
|
739
|
+
return id
|
|
740
|
+
}
|
|
741
|
+
}
|
|
731
742
|
return null
|
|
732
743
|
}
|
|
733
744
|
|
|
745
|
+
private resumeAssistantState(id: string, state: MessageStreamState): void {
|
|
746
|
+
this.activeMessageIds.add(id)
|
|
747
|
+
if (state.isComplete || this.isDone) {
|
|
748
|
+
state.isComplete = false
|
|
749
|
+
this.isDone = false
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
734
753
|
/**
|
|
735
754
|
* Ensure an active assistant message exists, creating one if needed.
|
|
736
755
|
* Used for backward compat when events arrive without prior TEXT_MESSAGE_START.
|
|
@@ -747,14 +766,20 @@ export class StreamProcessor {
|
|
|
747
766
|
// Try to find state by preferred ID
|
|
748
767
|
if (preferredId) {
|
|
749
768
|
const state = this.getMessageState(preferredId)
|
|
750
|
-
if (state)
|
|
769
|
+
if (state) {
|
|
770
|
+
this.resumeAssistantState(preferredId, state)
|
|
771
|
+
return { messageId: preferredId, state }
|
|
772
|
+
}
|
|
751
773
|
}
|
|
752
774
|
|
|
753
775
|
// Try active assistant message
|
|
754
776
|
const activeId = this.getActiveAssistantMessageId()
|
|
755
777
|
if (activeId) {
|
|
756
778
|
const state = this.getMessageState(activeId)
|
|
757
|
-
if (state)
|
|
779
|
+
if (state) {
|
|
780
|
+
this.resumeAssistantState(activeId, state)
|
|
781
|
+
return { messageId: activeId, state }
|
|
782
|
+
}
|
|
758
783
|
}
|
|
759
784
|
|
|
760
785
|
// Check if a message with preferredId already exists (reconnect/resume case).
|
|
@@ -835,11 +860,10 @@ export class StreamProcessor {
|
|
|
835
860
|
}
|
|
836
861
|
: incomingRecord
|
|
837
862
|
const metadata = mergeMetadata(message.metadata, toMerge)
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const createdAtValid =
|
|
842
|
-
createdAt !== undefined && !Number.isNaN(createdAt.getTime())
|
|
863
|
+
const createdAt = coerceCreatedAt(
|
|
864
|
+
tanstackMetadata(incomingRecord)?.createdAt,
|
|
865
|
+
)
|
|
866
|
+
const createdAtValid = createdAt !== undefined
|
|
843
867
|
this.messages = this.messages.map((msg) =>
|
|
844
868
|
msg.id === messageId
|
|
845
869
|
? {
|
|
@@ -900,9 +924,21 @@ export class StreamProcessor {
|
|
|
900
924
|
}
|
|
901
925
|
|
|
902
926
|
// Ensure state exists
|
|
903
|
-
|
|
904
|
-
|
|
927
|
+
let pendingState = this.messageStates.get(messageId)
|
|
928
|
+
if (!pendingState) {
|
|
929
|
+
pendingState = this.createMessageState(messageId, uiRole)
|
|
905
930
|
this.activeMessageIds.add(messageId)
|
|
931
|
+
} else if (pendingState.hasToolCallsSinceTextStart) {
|
|
932
|
+
// A tool call (e.g. TOOL_CALL_START with parentMessageId) marked
|
|
933
|
+
// this message before its "real" TEXT_MESSAGE_START arrived — same
|
|
934
|
+
// reset Case 2 performs, so the segment accumulator doesn't carry
|
|
935
|
+
// stale tool-call state into the text that follows.
|
|
936
|
+
if (pendingState.currentSegmentText !== pendingState.lastEmittedText) {
|
|
937
|
+
this.emitTextUpdateForMessage(messageId)
|
|
938
|
+
}
|
|
939
|
+
pendingState.currentSegmentText = ''
|
|
940
|
+
pendingState.lastEmittedText = ''
|
|
941
|
+
pendingState.hasToolCallsSinceTextStart = false
|
|
906
942
|
}
|
|
907
943
|
|
|
908
944
|
this.mergeMessageMetadata(messageId, chunk.metadata)
|
|
@@ -1050,8 +1086,10 @@ export class StreamProcessor {
|
|
|
1050
1086
|
msg.parts.every((part) => part.type === 'thinking')
|
|
1051
1087
|
const isToolResultOnly = (msg: UIMessage) =>
|
|
1052
1088
|
msg.role === 'assistant' &&
|
|
1053
|
-
msg.parts.
|
|
1054
|
-
msg.parts
|
|
1089
|
+
msg.parts.some((part) => part.type === 'tool-result') &&
|
|
1090
|
+
msg.parts.every(
|
|
1091
|
+
(part) => part.type === 'tool-result' || part.type === 'ui-resource',
|
|
1092
|
+
)
|
|
1055
1093
|
const flushPending = () => {
|
|
1056
1094
|
out.push(...pending)
|
|
1057
1095
|
pending = []
|
|
@@ -1109,9 +1147,16 @@ export class StreamProcessor {
|
|
|
1109
1147
|
|
|
1110
1148
|
const reconciled: Array<UIMessage> = []
|
|
1111
1149
|
for (const msg of snapshot) {
|
|
1150
|
+
const toolResultParts = msg.parts.filter(
|
|
1151
|
+
(part): part is ToolResultPart => part.type === 'tool-result',
|
|
1152
|
+
)
|
|
1112
1153
|
const toolResultPart =
|
|
1113
|
-
msg.role === 'assistant' &&
|
|
1114
|
-
|
|
1154
|
+
msg.role === 'assistant' &&
|
|
1155
|
+
toolResultParts.length === 1 &&
|
|
1156
|
+
msg.parts.every(
|
|
1157
|
+
(part) => part.type === 'tool-result' || part.type === 'ui-resource',
|
|
1158
|
+
)
|
|
1159
|
+
? toolResultParts[0]
|
|
1115
1160
|
: undefined
|
|
1116
1161
|
|
|
1117
1162
|
if (!toolResultPart) {
|
|
@@ -1170,7 +1215,7 @@ export class StreamProcessor {
|
|
|
1170
1215
|
)
|
|
1171
1216
|
}
|
|
1172
1217
|
}
|
|
1173
|
-
parts.push(
|
|
1218
|
+
parts.push(...msg.parts)
|
|
1174
1219
|
// Replace rather than push into `target.parts`: a snapshot message that
|
|
1175
1220
|
// arrived already carrying `parts` (TanStack server echoing UIMessages)
|
|
1176
1221
|
// shares its array with the incoming chunk, and mutating it in place
|
|
@@ -1638,8 +1683,14 @@ export class StreamProcessor {
|
|
|
1638
1683
|
}
|
|
1639
1684
|
|
|
1640
1685
|
if (this.activeRuns.size === 0) {
|
|
1641
|
-
this.isDone = true
|
|
1642
1686
|
this.completeAllToolCalls()
|
|
1687
|
+
const isIntermediateToolTurn =
|
|
1688
|
+
this.finishReason === 'tool_calls' &&
|
|
1689
|
+
chunk.outcome?.type !== 'interrupt'
|
|
1690
|
+
if (isIntermediateToolTurn) {
|
|
1691
|
+
return
|
|
1692
|
+
}
|
|
1693
|
+
this.isDone = true
|
|
1643
1694
|
this.finalizeStream()
|
|
1644
1695
|
}
|
|
1645
1696
|
}
|
|
@@ -2335,6 +2386,7 @@ export class StreamProcessor {
|
|
|
2335
2386
|
* @see docs/chat-architecture.md#single-shot-text-response — Finalization step
|
|
2336
2387
|
*/
|
|
2337
2388
|
finalizeStream(): void {
|
|
2389
|
+
this.isDone = true
|
|
2338
2390
|
let lastAssistantMessage: UIMessage | undefined
|
|
2339
2391
|
|
|
2340
2392
|
// Finalize ALL active messages
|
|
@@ -2398,7 +2450,8 @@ export class StreamProcessor {
|
|
|
2398
2450
|
}
|
|
2399
2451
|
|
|
2400
2452
|
// Emit stream end for the last assistant message
|
|
2401
|
-
if (lastAssistantMessage) {
|
|
2453
|
+
if (lastAssistantMessage && !this.streamEndEmitted) {
|
|
2454
|
+
this.streamEndEmitted = true
|
|
2402
2455
|
this.events.onStreamEnd?.(lastAssistantMessage)
|
|
2403
2456
|
}
|
|
2404
2457
|
}
|
|
@@ -2517,6 +2570,7 @@ export class StreamProcessor {
|
|
|
2517
2570
|
this.finishReason = null
|
|
2518
2571
|
this.hasError = false
|
|
2519
2572
|
this.isDone = false
|
|
2573
|
+
this.streamEndEmitted = false
|
|
2520
2574
|
this.chunkStrategy.reset?.()
|
|
2521
2575
|
}
|
|
2522
2576
|
|
package/src/index.ts
CHANGED
|
@@ -104,6 +104,8 @@ export type {
|
|
|
104
104
|
// MCP error classes (value exports — usable with instanceof)
|
|
105
105
|
export { MCPDuplicateToolNameError } from './activities/chat/mcp/manager'
|
|
106
106
|
export { DuplicateToolNameError } from './activities/chat/tools/unique-tool-names'
|
|
107
|
+
export { SkillLimitError } from './utilities/errors'
|
|
108
|
+
export type { SkillLimitErrorInit } from './utilities/errors'
|
|
107
109
|
|
|
108
110
|
// Schema conversion (Standard JSON Schema compliant)
|
|
109
111
|
export {
|
package/src/types.ts
CHANGED
|
@@ -372,6 +372,10 @@ export interface ModelMessage<
|
|
|
372
372
|
toolCalls?: Array<ToolCall>
|
|
373
373
|
toolCallId?: string
|
|
374
374
|
thinking?: Array<{ content: string; signature?: string }>
|
|
375
|
+
/** Error reported by an AG-UI tool message. */
|
|
376
|
+
error?: string
|
|
377
|
+
/** Optional AG-UI message metadata. TanStack-owned fields live under `tanstack`. */
|
|
378
|
+
metadata?: Record<string, any>
|
|
375
379
|
/**
|
|
376
380
|
* Completed structured output represented by this assistant message.
|
|
377
381
|
* `content` remains the provider-facing JSON text; this field preserves the
|
|
@@ -435,10 +439,14 @@ export interface ToolCallPart<TMetadata = unknown> {
|
|
|
435
439
|
|
|
436
440
|
export interface ToolResultPart {
|
|
437
441
|
type: 'tool-result'
|
|
442
|
+
id?: string
|
|
443
|
+
name?: string
|
|
438
444
|
toolCallId: string
|
|
439
445
|
content: string | Array<ContentPart>
|
|
440
446
|
state: ToolResultState
|
|
441
447
|
error?: string // Error message if state is "error"
|
|
448
|
+
metadata?: Record<string, unknown>
|
|
449
|
+
createdAt?: Date
|
|
442
450
|
}
|
|
443
451
|
|
|
444
452
|
export interface ThinkingPart {
|
|
@@ -524,9 +532,17 @@ export interface TanStackMessageMetadata {
|
|
|
524
532
|
signature?: string
|
|
525
533
|
/** Per-tool-call provider metadata keyed by tool call id (e.g. Gemini thoughtSignature). */
|
|
526
534
|
toolCallMetadata?: Record<string, unknown>
|
|
535
|
+
toolResult?: {
|
|
536
|
+
id?: string
|
|
537
|
+
createdAt?: string
|
|
538
|
+
content?: Array<ContentPart>
|
|
539
|
+
}
|
|
527
540
|
structuredOutput?: {
|
|
528
541
|
status?: 'streaming' | 'complete' | 'error'
|
|
542
|
+
partial?: unknown
|
|
543
|
+
data?: unknown
|
|
529
544
|
raw?: string
|
|
545
|
+
reasoning?: string
|
|
530
546
|
errorMessage?: string
|
|
531
547
|
}
|
|
532
548
|
uiResources?: Array<UIResourcePart>
|
|
@@ -562,6 +578,8 @@ export interface UIMessage<TData = unknown> {
|
|
|
562
578
|
role: 'system' | 'user' | 'assistant'
|
|
563
579
|
parts: Array<MessagePart<TData>>
|
|
564
580
|
createdAt?: Date
|
|
581
|
+
/** Optional AG-UI sender name. Converters preserve it across wire and persist. */
|
|
582
|
+
name?: string
|
|
565
583
|
/**
|
|
566
584
|
* Optional AG-UI metadata bag. TanStack writes the `tanstack` key.
|
|
567
585
|
* User keys stay at the top.
|