@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
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssistantMessage,
|
|
3
|
+
InputContent,
|
|
4
|
+
ReasoningMessage,
|
|
5
|
+
SystemMessage,
|
|
6
|
+
ToolCall,
|
|
7
|
+
ToolMessage,
|
|
8
|
+
UserMessage,
|
|
9
|
+
} from '@ag-ui/core'
|
|
1
10
|
import type {
|
|
2
11
|
ContentPart,
|
|
3
12
|
MessagePart,
|
|
13
|
+
ModelMessage,
|
|
4
14
|
StructuredOutputPart,
|
|
5
15
|
TanStackMessageMetadata,
|
|
6
16
|
UIMessage,
|
|
@@ -8,72 +18,125 @@ import type {
|
|
|
8
18
|
} from '../types'
|
|
9
19
|
import type { MetadataRecord } from './merge-metadata'
|
|
10
20
|
import { tanstackMetadata } from './merge-metadata'
|
|
21
|
+
import { normalizeToolResult } from './tool-result'
|
|
22
|
+
import {
|
|
23
|
+
coerceCreatedAt,
|
|
24
|
+
modelMessageToUIMessage,
|
|
25
|
+
} from '../activities/chat/messages'
|
|
11
26
|
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type AGUIToolMessage = {
|
|
25
|
-
role: 'tool'
|
|
26
|
-
id: string
|
|
27
|
-
toolCallId: string
|
|
28
|
-
content: string
|
|
29
|
-
error?: string
|
|
30
|
-
}
|
|
27
|
+
type WithMetadata<T> = T & { metadata?: MetadataRecord }
|
|
28
|
+
type WireSystemMessage = WithMetadata<SystemMessage>
|
|
29
|
+
type WireUserMessage = WithMetadata<UserMessage>
|
|
30
|
+
type WireAssistantMessage = WithMetadata<AssistantMessage>
|
|
31
|
+
type WireToolMessage = WithMetadata<
|
|
32
|
+
ToolMessage & {
|
|
33
|
+
name?: string
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
type WireReasoningMessage = WithMetadata<ReasoningMessage>
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
id: string
|
|
35
|
-
content: string
|
|
36
|
-
encryptedValue?: string
|
|
37
|
-
metadata?: MetadataRecord
|
|
38
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
39
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
metadata
|
|
42
|
+
function rebuiltToolMetadata(
|
|
43
|
+
metadata: unknown,
|
|
44
|
+
createdAt: unknown,
|
|
45
|
+
id: string | undefined,
|
|
46
|
+
content: string | null | Array<ContentPart>,
|
|
47
|
+
anchorOwnsUiResources = false,
|
|
48
|
+
): MetadataRecord | undefined {
|
|
49
|
+
const source: MetadataRecord = isRecord(metadata) ? metadata : {}
|
|
50
|
+
const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {}
|
|
51
|
+
if (anchorOwnsUiResources) delete tanstack.uiResources
|
|
52
|
+
const date = coerceCreatedAt(createdAt)
|
|
53
|
+
const toolResult: NonNullable<TanStackMessageMetadata['toolResult']> = {
|
|
54
|
+
...(id !== undefined ? { id } : {}),
|
|
55
|
+
...(date && { createdAt: date.toISOString() }),
|
|
56
|
+
...(Array.isArray(content) && { content }),
|
|
57
|
+
}
|
|
58
|
+
const result = {
|
|
59
|
+
...source,
|
|
60
|
+
tanstack: { ...tanstack, toolResult },
|
|
61
|
+
}
|
|
62
|
+
return Object.keys(result).length ? result : undefined
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
export type WireMessage =
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
66
|
+
| WireSystemMessage
|
|
67
|
+
| WireUserMessage
|
|
68
|
+
| WireAssistantMessage
|
|
69
|
+
| WireToolMessage
|
|
70
|
+
| WireReasoningMessage
|
|
54
71
|
|
|
55
72
|
/**
|
|
56
|
-
* Serialize TanStack `UIMessage`s into the AG-UI
|
|
57
|
-
* wire shape. Anchors are spec-only (`id`, `role`,
|
|
58
|
-
* `toolCalls`, `metadata`). Tool results and thinking parts
|
|
59
|
-
* messages are additionally emitted as fan-out
|
|
60
|
-
* `{role:'reasoning',...}` entries for strict AG-UI
|
|
73
|
+
* Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI
|
|
74
|
+
* `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,
|
|
75
|
+
* `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts
|
|
76
|
+
* on assistant messages are additionally emitted as fan-out
|
|
77
|
+
* `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI
|
|
78
|
+
* server consumers. Set `includeSnapshotStructuredOutput` to retain complete
|
|
79
|
+
* structured-output metadata for UI snapshots.
|
|
61
80
|
*/
|
|
62
81
|
export function uiMessagesToWire(
|
|
63
|
-
messages: Array<UIMessage>,
|
|
82
|
+
messages: Array<UIMessage | ModelMessage>,
|
|
83
|
+
options?: { includeSnapshotStructuredOutput: boolean },
|
|
64
84
|
): Array<WireMessage> {
|
|
65
85
|
const wire: Array<WireMessage> = []
|
|
86
|
+
const usedWireIds = new Set<string>(
|
|
87
|
+
messages.flatMap((message) =>
|
|
88
|
+
'id' in message && message.id && message.role !== 'tool'
|
|
89
|
+
? [message.id]
|
|
90
|
+
: [],
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
const includeSnapshotStructuredOutput =
|
|
94
|
+
options?.includeSnapshotStructuredOutput ?? false
|
|
95
|
+
|
|
96
|
+
const assistantIds = new Set<string>()
|
|
97
|
+
for (const msg of messages) {
|
|
98
|
+
if (msg.role === 'assistant' && msg.id !== undefined) {
|
|
99
|
+
assistantIds.add(msg.id)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
66
102
|
|
|
67
103
|
for (const msg of messages) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
if (!('parts' in msg) && msg.role === 'tool' && msg.toolCallId) {
|
|
105
|
+
const id = uniqueToolWireId(
|
|
106
|
+
toolWireId(msg.id, msg.toolCallId, assistantIds),
|
|
107
|
+
usedWireIds,
|
|
108
|
+
)
|
|
109
|
+
const metadata = rebuiltToolMetadata(
|
|
110
|
+
msg.metadata,
|
|
111
|
+
msg.createdAt,
|
|
112
|
+
msg.id,
|
|
113
|
+
msg.content,
|
|
114
|
+
)
|
|
115
|
+
wire.push({
|
|
116
|
+
role: 'tool',
|
|
117
|
+
id,
|
|
118
|
+
...(msg.name !== undefined && { name: msg.name }),
|
|
119
|
+
toolCallId: msg.toolCallId,
|
|
120
|
+
content:
|
|
121
|
+
typeof msg.content === 'string'
|
|
122
|
+
? msg.content
|
|
123
|
+
: JSON.stringify(msg.content),
|
|
124
|
+
...(msg.error !== undefined && { error: msg.error }),
|
|
125
|
+
...(metadata !== undefined && { metadata }),
|
|
126
|
+
})
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const uiMessage: UIMessage =
|
|
131
|
+
'parts' in msg ? msg : modelMessageToUIMessage(msg, msg.id)
|
|
132
|
+
const parts: ReadonlyArray<MessagePart> = uiMessage.parts
|
|
133
|
+
usedWireIds.add(uiMessage.id)
|
|
72
134
|
|
|
73
135
|
if (msg.role === 'system') {
|
|
74
136
|
wire.push(
|
|
75
137
|
toAnchor(
|
|
76
|
-
|
|
138
|
+
uiMessage,
|
|
139
|
+
'system',
|
|
77
140
|
{
|
|
78
141
|
content:
|
|
79
142
|
parts.length > 0
|
|
@@ -81,6 +144,7 @@ export function uiMessagesToWire(
|
|
|
81
144
|
: ((msg as { content?: string }).content ?? ''),
|
|
82
145
|
},
|
|
83
146
|
parts,
|
|
147
|
+
includeSnapshotStructuredOutput,
|
|
84
148
|
),
|
|
85
149
|
)
|
|
86
150
|
continue
|
|
@@ -89,7 +153,8 @@ export function uiMessagesToWire(
|
|
|
89
153
|
if (msg.role === 'user') {
|
|
90
154
|
wire.push(
|
|
91
155
|
toAnchor(
|
|
92
|
-
|
|
156
|
+
uiMessage,
|
|
157
|
+
'user',
|
|
93
158
|
{
|
|
94
159
|
content:
|
|
95
160
|
parts.length > 0
|
|
@@ -97,6 +162,7 @@ export function uiMessagesToWire(
|
|
|
97
162
|
: ((msg as { content?: string }).content ?? ''),
|
|
98
163
|
},
|
|
99
164
|
parts,
|
|
165
|
+
includeSnapshotStructuredOutput,
|
|
100
166
|
),
|
|
101
167
|
)
|
|
102
168
|
continue
|
|
@@ -105,9 +171,9 @@ export function uiMessagesToWire(
|
|
|
105
171
|
// assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs
|
|
106
172
|
for (const part of parts) {
|
|
107
173
|
if (part.type === 'thinking') {
|
|
108
|
-
const reasoning:
|
|
174
|
+
const reasoning: WireReasoningMessage = {
|
|
109
175
|
role: 'reasoning',
|
|
110
|
-
id: deriveReasoningId(
|
|
176
|
+
id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),
|
|
111
177
|
content: part.content,
|
|
112
178
|
}
|
|
113
179
|
if (part.signature) {
|
|
@@ -121,26 +187,79 @@ export function uiMessagesToWire(
|
|
|
121
187
|
const toolCalls = collectToolCalls(parts)
|
|
122
188
|
wire.push(
|
|
123
189
|
toAnchor(
|
|
124
|
-
|
|
190
|
+
uiMessage,
|
|
191
|
+
'assistant',
|
|
125
192
|
{
|
|
126
193
|
...(text !== '' && { content: text }),
|
|
127
194
|
...(toolCalls && { toolCalls }),
|
|
128
195
|
},
|
|
129
196
|
parts,
|
|
197
|
+
includeSnapshotStructuredOutput,
|
|
130
198
|
),
|
|
131
199
|
)
|
|
132
200
|
|
|
201
|
+
const explicitToolResults = new Set(
|
|
202
|
+
parts.flatMap((part) =>
|
|
203
|
+
part.type === 'tool-result' ? [part.toolCallId] : [],
|
|
204
|
+
),
|
|
205
|
+
)
|
|
133
206
|
for (const part of parts) {
|
|
134
207
|
if (part.type === 'tool-result') {
|
|
208
|
+
const id = uniqueToolWireId(
|
|
209
|
+
part.id ?? deriveToolMessageId(part.toolCallId),
|
|
210
|
+
usedWireIds,
|
|
211
|
+
)
|
|
212
|
+
const metadata = rebuiltToolMetadata(
|
|
213
|
+
part.metadata,
|
|
214
|
+
part.createdAt,
|
|
215
|
+
part.id,
|
|
216
|
+
part.content,
|
|
217
|
+
true,
|
|
218
|
+
)
|
|
135
219
|
wire.push({
|
|
136
220
|
role: 'tool',
|
|
137
|
-
id
|
|
221
|
+
id,
|
|
138
222
|
toolCallId: part.toolCallId,
|
|
223
|
+
...(part.name !== undefined && { name: part.name }),
|
|
139
224
|
content:
|
|
140
225
|
typeof part.content === 'string'
|
|
141
226
|
? part.content
|
|
142
227
|
: JSON.stringify(part.content),
|
|
143
228
|
...(part.error !== undefined && { error: part.error }),
|
|
229
|
+
...(metadata !== undefined && { metadata }),
|
|
230
|
+
})
|
|
231
|
+
} else if (part.type === 'tool-call') {
|
|
232
|
+
const approved = part.approval?.approved
|
|
233
|
+
if (
|
|
234
|
+
explicitToolResults.has(part.id) ||
|
|
235
|
+
(part.output === undefined &&
|
|
236
|
+
(part.state !== 'approval-responded' || approved === undefined))
|
|
237
|
+
) {
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
const result =
|
|
241
|
+
part.output !== undefined
|
|
242
|
+
? normalizeToolResult(part.output)
|
|
243
|
+
: JSON.stringify({
|
|
244
|
+
approved,
|
|
245
|
+
...(approved && { pendingExecution: true }),
|
|
246
|
+
message: approved
|
|
247
|
+
? 'User approved this action'
|
|
248
|
+
: 'User denied this action',
|
|
249
|
+
})
|
|
250
|
+
const content =
|
|
251
|
+
typeof result === 'string' ? result : JSON.stringify(result)
|
|
252
|
+
wire.push({
|
|
253
|
+
role: 'tool',
|
|
254
|
+
id: uniqueToolWireId(deriveToolMessageId(part.id), usedWireIds),
|
|
255
|
+
toolCallId: part.id,
|
|
256
|
+
content,
|
|
257
|
+
metadata: rebuiltToolMetadata(
|
|
258
|
+
undefined,
|
|
259
|
+
undefined,
|
|
260
|
+
undefined,
|
|
261
|
+
result,
|
|
262
|
+
),
|
|
144
263
|
})
|
|
145
264
|
}
|
|
146
265
|
}
|
|
@@ -151,33 +270,78 @@ export function uiMessagesToWire(
|
|
|
151
270
|
|
|
152
271
|
function toAnchor(
|
|
153
272
|
msg: UIMessage,
|
|
273
|
+
role: 'system',
|
|
274
|
+
extras: { content: string },
|
|
275
|
+
parts: ReadonlyArray<MessagePart>,
|
|
276
|
+
includeSnapshotStructuredOutput: boolean,
|
|
277
|
+
): WireSystemMessage
|
|
278
|
+
function toAnchor(
|
|
279
|
+
msg: UIMessage,
|
|
280
|
+
role: 'user',
|
|
281
|
+
extras: { content: string | Array<InputContent> },
|
|
282
|
+
parts: ReadonlyArray<MessagePart>,
|
|
283
|
+
includeSnapshotStructuredOutput: boolean,
|
|
284
|
+
): WireUserMessage
|
|
285
|
+
function toAnchor(
|
|
286
|
+
msg: UIMessage,
|
|
287
|
+
role: 'assistant',
|
|
154
288
|
extras: {
|
|
155
|
-
content?: string
|
|
156
|
-
toolCalls?: Array<
|
|
289
|
+
content?: string
|
|
290
|
+
toolCalls?: Array<ToolCall>
|
|
157
291
|
},
|
|
158
292
|
parts: ReadonlyArray<MessagePart>,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
293
|
+
includeSnapshotStructuredOutput: boolean,
|
|
294
|
+
): WireAssistantMessage
|
|
295
|
+
function toAnchor(
|
|
296
|
+
msg: UIMessage,
|
|
297
|
+
role: UIMessage['role'],
|
|
298
|
+
extras: {
|
|
299
|
+
content?: string | Array<InputContent>
|
|
300
|
+
toolCalls?: Array<ToolCall>
|
|
301
|
+
},
|
|
302
|
+
parts: ReadonlyArray<MessagePart>,
|
|
303
|
+
includeSnapshotStructuredOutput: boolean,
|
|
304
|
+
): WireSystemMessage | WireUserMessage | WireAssistantMessage {
|
|
305
|
+
const metadata = messageMetadata(msg, parts, includeSnapshotStructuredOutput)
|
|
306
|
+
const base = {
|
|
163
307
|
id: msg.id,
|
|
164
|
-
|
|
165
|
-
...(name !== undefined && { name }),
|
|
166
|
-
...extras,
|
|
308
|
+
...(msg.name !== undefined && { name: msg.name }),
|
|
167
309
|
...(metadata !== undefined && { metadata }),
|
|
168
310
|
}
|
|
311
|
+
if (role === 'system') {
|
|
312
|
+
return { ...base, role, content: String(extras.content ?? '') }
|
|
313
|
+
}
|
|
314
|
+
if (role === 'user') {
|
|
315
|
+
return { ...base, role, content: extras.content ?? '' }
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
...base,
|
|
319
|
+
role,
|
|
320
|
+
...(typeof extras.content === 'string' && { content: extras.content }),
|
|
321
|
+
...(extras.toolCalls !== undefined && { toolCalls: extras.toolCalls }),
|
|
322
|
+
}
|
|
169
323
|
}
|
|
170
324
|
|
|
171
325
|
function messageMetadata(
|
|
172
326
|
msg: UIMessage,
|
|
173
327
|
parts: ReadonlyArray<MessagePart>,
|
|
328
|
+
includeSnapshotStructuredOutput: boolean,
|
|
174
329
|
): MetadataRecord | undefined {
|
|
175
330
|
const base: MetadataRecord = { ...(msg.metadata ?? {}) }
|
|
176
|
-
const
|
|
177
|
-
|
|
331
|
+
const previousTanstack = tanstackMetadata(msg)
|
|
332
|
+
const tanstack: TanStackMessageMetadata = {}
|
|
333
|
+
if (previousTanstack?.model !== undefined)
|
|
334
|
+
tanstack.model = previousTanstack.model
|
|
335
|
+
if (previousTanstack?.signature !== undefined)
|
|
336
|
+
tanstack.signature = previousTanstack.signature
|
|
337
|
+
const createdAt = coerceCreatedAt(msg.createdAt)
|
|
338
|
+
if (createdAt !== undefined) tanstack.createdAt = createdAt.toISOString()
|
|
178
339
|
|
|
179
|
-
const
|
|
180
|
-
|
|
340
|
+
const structuredOutput = serializedStructuredOutput(
|
|
341
|
+
parts,
|
|
342
|
+
includeSnapshotStructuredOutput,
|
|
343
|
+
)
|
|
344
|
+
if (structuredOutput) tanstack.structuredOutput = structuredOutput
|
|
181
345
|
|
|
182
346
|
const toolCallMetadata: Record<string, unknown> = {}
|
|
183
347
|
for (const part of parts) {
|
|
@@ -195,26 +359,41 @@ function messageMetadata(
|
|
|
195
359
|
if (uiResources.length > 0) tanstack.uiResources = uiResources
|
|
196
360
|
|
|
197
361
|
if (Object.keys(tanstack).length > 0) base.tanstack = tanstack
|
|
362
|
+
else delete base.tanstack
|
|
198
363
|
return Object.keys(base).length > 0 ? base : undefined
|
|
199
364
|
}
|
|
200
365
|
|
|
201
|
-
function
|
|
366
|
+
function serializedStructuredOutput(
|
|
202
367
|
parts: ReadonlyArray<MessagePart>,
|
|
368
|
+
includeSnapshotStructuredOutput: boolean,
|
|
203
369
|
): TanStackMessageMetadata['structuredOutput'] | undefined {
|
|
204
370
|
for (const p of parts) {
|
|
205
|
-
if (
|
|
206
|
-
|
|
371
|
+
if (
|
|
372
|
+
p.type === 'structured-output' &&
|
|
373
|
+
(includeSnapshotStructuredOutput || p.status !== 'complete')
|
|
374
|
+
) {
|
|
375
|
+
return structuredOutputMetadata(p, includeSnapshotStructuredOutput)
|
|
207
376
|
}
|
|
208
377
|
}
|
|
209
378
|
return undefined
|
|
210
379
|
}
|
|
211
380
|
|
|
212
|
-
function
|
|
381
|
+
function structuredOutputMetadata(
|
|
213
382
|
part: StructuredOutputPart,
|
|
383
|
+
includeSnapshotStructuredOutput: boolean,
|
|
214
384
|
): NonNullable<TanStackMessageMetadata['structuredOutput']> {
|
|
215
385
|
return {
|
|
216
386
|
status: part.status,
|
|
217
387
|
raw: part.raw,
|
|
388
|
+
...(includeSnapshotStructuredOutput && part.partial !== undefined
|
|
389
|
+
? { partial: part.partial }
|
|
390
|
+
: {}),
|
|
391
|
+
...(includeSnapshotStructuredOutput && part.data !== undefined
|
|
392
|
+
? { data: part.data }
|
|
393
|
+
: {}),
|
|
394
|
+
...(includeSnapshotStructuredOutput && part.reasoning
|
|
395
|
+
? { reasoning: part.reasoning }
|
|
396
|
+
: {}),
|
|
218
397
|
...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),
|
|
219
398
|
}
|
|
220
399
|
}
|
|
@@ -247,7 +426,7 @@ function collectText(parts: ReadonlyArray<MessagePart>): string {
|
|
|
247
426
|
|
|
248
427
|
function collectUserContent(
|
|
249
428
|
parts: ReadonlyArray<MessagePart>,
|
|
250
|
-
): string | Array<
|
|
429
|
+
): string | Array<InputContent> {
|
|
251
430
|
const hasMultimodal = parts.some(
|
|
252
431
|
(p) =>
|
|
253
432
|
p.type === 'image' ||
|
|
@@ -258,7 +437,7 @@ function collectUserContent(
|
|
|
258
437
|
if (!hasMultimodal) {
|
|
259
438
|
return collectText(parts)
|
|
260
439
|
}
|
|
261
|
-
const out: Array<
|
|
440
|
+
const out: Array<InputContent> = []
|
|
262
441
|
for (const p of parts) {
|
|
263
442
|
if (p.type === 'text') {
|
|
264
443
|
out.push({ type: 'text', text: p.content })
|
|
@@ -289,8 +468,8 @@ function thoughtSignatureFromMetadata(metadata: unknown): string | undefined {
|
|
|
289
468
|
|
|
290
469
|
function collectToolCalls(
|
|
291
470
|
parts: ReadonlyArray<MessagePart>,
|
|
292
|
-
): Array<
|
|
293
|
-
const calls: Array<
|
|
471
|
+
): Array<ToolCall> | undefined {
|
|
472
|
+
const calls: Array<ToolCall> = []
|
|
294
473
|
for (const p of parts) {
|
|
295
474
|
if (p.type === 'tool-call') {
|
|
296
475
|
const encryptedValue = thoughtSignatureFromMetadata(p.metadata)
|
|
@@ -313,6 +492,32 @@ function deriveToolMessageId(toolCallId: string): string {
|
|
|
313
492
|
return `tool-${toolCallId}`
|
|
314
493
|
}
|
|
315
494
|
|
|
495
|
+
function uniqueToolWireId(id: string, used: Set<string>): string {
|
|
496
|
+
return uniqueWireId(id, used)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function uniqueWireId(id: string, used: Set<string>): string {
|
|
500
|
+
if (!used.has(id)) {
|
|
501
|
+
used.add(id)
|
|
502
|
+
return id
|
|
503
|
+
}
|
|
504
|
+
let suffix = 2
|
|
505
|
+
while (used.has(`${id}-${suffix}`)) suffix++
|
|
506
|
+
const unique = `${id}-${suffix}`
|
|
507
|
+
used.add(unique)
|
|
508
|
+
return unique
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function toolWireId(
|
|
512
|
+
id: string | undefined,
|
|
513
|
+
toolCallId: string,
|
|
514
|
+
assistantIds: ReadonlySet<string>,
|
|
515
|
+
): string {
|
|
516
|
+
const derived = deriveToolMessageId(toolCallId)
|
|
517
|
+
if (id === undefined || assistantIds.has(id)) return derived
|
|
518
|
+
return id
|
|
519
|
+
}
|
|
520
|
+
|
|
316
521
|
function hashContent(s: string): string {
|
|
317
522
|
// Cheap deterministic id suffix; collisions are tolerable since
|
|
318
523
|
// reasoning ids only matter for AG-UI server consumers, not for our
|
package/src/utilities/errors.ts
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
import type { StreamChunk } from '../types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when a skills request exceeds a provider limit. Lives in core (rather
|
|
5
|
+
* than `@tanstack/ai-skills`) so the native tool factories in `ai-anthropic`
|
|
6
|
+
* and `openai-base` can throw it without depending on the skills package;
|
|
7
|
+
* `@tanstack/ai-skills` re-exports it for the portable path.
|
|
8
|
+
*
|
|
9
|
+
* `path` distinguishes the native provider cap (e.g. Anthropic's 8-skill
|
|
10
|
+
* limit) from a portable-path limit, so a portable user isn't sent chasing a
|
|
11
|
+
* cap that only applies to hosted skills.
|
|
12
|
+
*/
|
|
13
|
+
export interface SkillLimitErrorInit {
|
|
14
|
+
provider: 'anthropic' | 'openai' | 'gemini' | 'other'
|
|
15
|
+
path: 'native' | 'portable'
|
|
16
|
+
limit: string
|
|
17
|
+
allowed: number
|
|
18
|
+
actual: number
|
|
19
|
+
offending: Array<string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SkillLimitError extends Error {
|
|
23
|
+
readonly provider: 'anthropic' | 'openai' | 'gemini' | 'other'
|
|
24
|
+
readonly path: 'native' | 'portable'
|
|
25
|
+
readonly limit: string
|
|
26
|
+
readonly allowed: number
|
|
27
|
+
readonly actual: number
|
|
28
|
+
readonly offending: Array<string>
|
|
29
|
+
|
|
30
|
+
constructor(init: SkillLimitErrorInit) {
|
|
31
|
+
super(
|
|
32
|
+
`${init.provider} ${init.path} skills limit exceeded (${init.limit}): ` +
|
|
33
|
+
`${init.actual} > ${init.allowed}`,
|
|
34
|
+
)
|
|
35
|
+
this.name = 'SkillLimitError'
|
|
36
|
+
this.provider = init.provider
|
|
37
|
+
this.path = init.path
|
|
38
|
+
this.limit = init.limit
|
|
39
|
+
this.allowed = init.allowed
|
|
40
|
+
this.actual = init.actual
|
|
41
|
+
this.offending = init.offending
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
3
45
|
/**
|
|
4
46
|
* Best-effort extraction of a human-readable message from an unknown thrown
|
|
5
47
|
* value, returning `undefined` when none can be found.
|