@tanstack/ai 0.45.1 → 0.47.1
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.d.ts +36 -11
- package/dist/esm/activities/chat/index.js +462 -66
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +1 -0
- package/dist/esm/activities/chat/messages.js +12 -7
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
- package/dist/esm/activities/chat/middleware/types.js +16 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -5
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/unique-tool-names.d.ts +20 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js +57 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js.map +1 -0
- package/dist/esm/adapter-internals.d.ts +7 -0
- package/dist/esm/adapter-internals.js +5 -1
- package/dist/esm/client.d.ts +4 -0
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
- package/dist/esm/generic-interrupt-continuation.js +80 -0
- package/dist/esm/generic-interrupt-continuation.js.map +1 -0
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/interrupt-definition.d.ts +113 -0
- package/dist/esm/interrupt-definition.js +169 -0
- package/dist/esm/interrupt-definition.js.map +1 -0
- package/dist/esm/interrupt-resume.d.ts +3 -0
- package/dist/esm/interrupt-resume.js +77 -16
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.d.ts +12 -3
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.d.ts +2 -2
- package/dist/esm/middlewares/usage-attributes.js +9 -2
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +26 -0
- package/dist/esm/stream-to-response.js +1 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.d.ts +123 -0
- package/dist/esm/stream-to-websocket.js +249 -0
- package/dist/esm/stream-to-websocket.js.map +1 -0
- package/dist/esm/types.d.ts +19 -10
- package/dist/esm/utilities/chat-params.js +10 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/skills/ai-core/media-generation/SKILL.md +13 -9
- package/skills/ai-core/middleware/SKILL.md +53 -44
- package/skills/ai-core/structured-outputs/SKILL.md +59 -55
- package/skills/ai-core/tool-calling/SKILL.md +54 -1
- package/src/activities/chat/index.ts +1076 -194
- package/src/activities/chat/messages.ts +11 -3
- package/src/activities/chat/middleware/builder.ts +29 -4
- package/src/activities/chat/middleware/compose.ts +95 -5
- package/src/activities/chat/middleware/define.ts +13 -3
- package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
- package/src/activities/chat/middleware/index.ts +15 -0
- package/src/activities/chat/middleware/types.ts +127 -2
- package/src/activities/chat/stream/processor.ts +21 -0
- package/src/activities/chat/tools/unique-tool-names.ts +73 -0
- package/src/adapter-internals.ts +24 -0
- package/src/client.ts +20 -0
- package/src/generic-interrupt-continuation.ts +162 -0
- package/src/index.ts +51 -0
- package/src/interrupt-definition.ts +581 -0
- package/src/interrupt-resume.ts +156 -25
- package/src/interrupts.ts +13 -3
- package/src/middlewares/usage-attributes.ts +12 -2
- package/src/stream-to-response.ts +2 -2
- package/src/stream-to-websocket.ts +418 -0
- package/src/types.ts +21 -8
- package/src/utilities/chat-params.ts +16 -3
package/src/interrupt-resume.ts
CHANGED
|
@@ -25,6 +25,10 @@ import type {
|
|
|
25
25
|
ChatMiddlewareConfig,
|
|
26
26
|
ChatResumeToolState,
|
|
27
27
|
} from './activities/chat/middleware/types'
|
|
28
|
+
import type {
|
|
29
|
+
GenericInterruptRequest,
|
|
30
|
+
InterruptDefinition,
|
|
31
|
+
} from './interrupt-definition'
|
|
28
32
|
import type { Interrupt, RunAgentResumeItem } from './types'
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -46,6 +50,10 @@ export interface PendingInterruptResumeRecord {
|
|
|
46
50
|
interruptId: string
|
|
47
51
|
payload: unknown
|
|
48
52
|
binding: InterruptBinding
|
|
53
|
+
/** Present for a first-party generic interrupt. */
|
|
54
|
+
genericRequest?: GenericInterruptRequest<
|
|
55
|
+
InterruptDefinition<any, any, any, any>
|
|
56
|
+
>
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export interface ValidateInterruptResumeBatchInput {
|
|
@@ -164,6 +172,17 @@ function runtimeTool(
|
|
|
164
172
|
return tools.find((tool) => tool.name === name) as RuntimeTool | undefined
|
|
165
173
|
}
|
|
166
174
|
|
|
175
|
+
async function parseSchemaValue(
|
|
176
|
+
schema: unknown,
|
|
177
|
+
value: unknown,
|
|
178
|
+
): Promise<{ success: true; data: unknown } | { success: false }> {
|
|
179
|
+
if (!isStandardSchema(schema)) return { success: true, data: value }
|
|
180
|
+
const result = await validateWithStandardSchema<unknown>(schema, value)
|
|
181
|
+
return result.success
|
|
182
|
+
? { success: true, data: result.data }
|
|
183
|
+
: { success: false }
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
function descriptorResponseSchema(
|
|
168
187
|
record: PendingInterruptResumeRecord,
|
|
169
188
|
): unknown {
|
|
@@ -219,9 +238,14 @@ function validateDescriptorSchema(
|
|
|
219
238
|
errors: Array<InterruptSubmissionError>,
|
|
220
239
|
): unknown {
|
|
221
240
|
const schema = descriptorResponseSchema(record)
|
|
241
|
+
const responseSchemaHash = binding.responseSchemaHash
|
|
242
|
+
if (schema === undefined && responseSchemaHash === undefined) {
|
|
243
|
+
return undefined
|
|
244
|
+
}
|
|
222
245
|
if (
|
|
223
246
|
schema === undefined ||
|
|
224
|
-
|
|
247
|
+
responseSchemaHash === undefined ||
|
|
248
|
+
schemaHash(schema) !== responseSchemaHash
|
|
225
249
|
) {
|
|
226
250
|
errors.push(
|
|
227
251
|
interruptItemError(
|
|
@@ -276,12 +300,24 @@ export async function validateInterruptResumeBatch(
|
|
|
276
300
|
}
|
|
277
301
|
}
|
|
278
302
|
|
|
303
|
+
const pendingGenerics = input.pending.filter(
|
|
304
|
+
(record) => record.binding.kind === 'generic',
|
|
305
|
+
)
|
|
306
|
+
const genericBatchSatisfied =
|
|
307
|
+
pendingGenerics.length > 0 &&
|
|
308
|
+
pendingGenerics.every((record) => resumeById.has(record.interruptId))
|
|
309
|
+
|
|
279
310
|
let incomplete = false
|
|
280
311
|
for (const record of input.pending) {
|
|
281
312
|
const errors = group(record.interruptId)
|
|
282
313
|
const entry = resumeById.get(record.interruptId)
|
|
283
314
|
const binding = record.binding
|
|
284
315
|
if (!entry) {
|
|
316
|
+
// Client tools that share a generic interrupt batch wait for
|
|
317
|
+
// `toolResume`. `continue` re-emits them; `cancel` / `stop` skip them.
|
|
318
|
+
if (genericBatchSatisfied && binding.kind === 'client-tool-execution') {
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
285
321
|
incomplete = true
|
|
286
322
|
errors.push(
|
|
287
323
|
interruptItemError(
|
|
@@ -307,19 +343,29 @@ export async function validateInterruptResumeBatch(
|
|
|
307
343
|
),
|
|
308
344
|
)
|
|
309
345
|
}
|
|
310
|
-
if (
|
|
311
|
-
binding.expiresAt
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
)
|
|
322
|
-
)
|
|
346
|
+
if (binding.expiresAt !== undefined) {
|
|
347
|
+
const expiresAt = Date.parse(binding.expiresAt)
|
|
348
|
+
if (!Number.isFinite(expiresAt)) {
|
|
349
|
+
errors.push(
|
|
350
|
+
interruptItemError(
|
|
351
|
+
input,
|
|
352
|
+
record.interruptId,
|
|
353
|
+
'invalid-payload',
|
|
354
|
+
`Interrupt ${record.interruptId} has an invalid expiresAt.`,
|
|
355
|
+
{ source: 'server' },
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
} else if (expiresAt <= (input.now ?? Date.now())) {
|
|
359
|
+
errors.push(
|
|
360
|
+
interruptItemError(
|
|
361
|
+
input,
|
|
362
|
+
record.interruptId,
|
|
363
|
+
'expired',
|
|
364
|
+
`Interrupt ${record.interruptId} has expired.`,
|
|
365
|
+
{ source: 'server' },
|
|
366
|
+
),
|
|
367
|
+
)
|
|
368
|
+
}
|
|
323
369
|
}
|
|
324
370
|
|
|
325
371
|
const responseSchema = validateDescriptorSchema(
|
|
@@ -342,6 +388,28 @@ export async function validateInterruptResumeBatch(
|
|
|
342
388
|
continue
|
|
343
389
|
}
|
|
344
390
|
if (binding.kind === 'generic') {
|
|
391
|
+
const genericRequest = record.genericRequest
|
|
392
|
+
if (genericRequest !== undefined) {
|
|
393
|
+
const batchIndex = binding.batchIndex
|
|
394
|
+
if (
|
|
395
|
+
binding.definitionId !== genericRequest.definition.id ||
|
|
396
|
+
binding.key !== genericRequest.key ||
|
|
397
|
+
binding.interruptId !== record.interruptId ||
|
|
398
|
+
batchIndex === undefined ||
|
|
399
|
+
!Number.isInteger(batchIndex) ||
|
|
400
|
+
batchIndex < 0
|
|
401
|
+
) {
|
|
402
|
+
errors.push(
|
|
403
|
+
interruptItemError(
|
|
404
|
+
input,
|
|
405
|
+
record.interruptId,
|
|
406
|
+
'stale',
|
|
407
|
+
`Generic interrupt ${record.interruptId} has stale definition metadata.`,
|
|
408
|
+
{ source: 'server' },
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
345
413
|
if (entry.status === 'cancelled') {
|
|
346
414
|
if (entry.payload !== undefined) {
|
|
347
415
|
errors.push(
|
|
@@ -353,6 +421,16 @@ export async function validateInterruptResumeBatch(
|
|
|
353
421
|
),
|
|
354
422
|
)
|
|
355
423
|
}
|
|
424
|
+
} else if (genericRequest !== undefined) {
|
|
425
|
+
await pushSchemaIssues({
|
|
426
|
+
request: input,
|
|
427
|
+
errors,
|
|
428
|
+
interruptId: record.interruptId,
|
|
429
|
+
schema: genericRequest.definition.responseSchema,
|
|
430
|
+
value: entry.payload,
|
|
431
|
+
code: 'invalid-payload',
|
|
432
|
+
label: `Interrupt ${record.interruptId} payload is invalid`,
|
|
433
|
+
})
|
|
356
434
|
} else if (responseSchema !== undefined) {
|
|
357
435
|
await pushSchemaIssues({
|
|
358
436
|
request: input,
|
|
@@ -616,16 +694,42 @@ export async function validateInterruptResumeBatch(
|
|
|
616
694
|
if (!entry) continue
|
|
617
695
|
const binding = record.binding
|
|
618
696
|
if (binding.kind === 'generic') {
|
|
619
|
-
|
|
620
|
-
record.interruptId,
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
697
|
+
if (entry.status !== 'resolved') {
|
|
698
|
+
genericInterrupts.set(record.interruptId, {
|
|
699
|
+
interruptId: record.interruptId,
|
|
700
|
+
status: 'cancelled',
|
|
701
|
+
})
|
|
702
|
+
continue
|
|
703
|
+
}
|
|
704
|
+
if (record.genericRequest === undefined) {
|
|
705
|
+
genericInterrupts.set(record.interruptId, {
|
|
706
|
+
interruptId: record.interruptId,
|
|
707
|
+
status: 'resolved',
|
|
708
|
+
payload: entry.payload,
|
|
709
|
+
})
|
|
710
|
+
continue
|
|
711
|
+
}
|
|
712
|
+
const parsed = await parseSchemaValue(
|
|
713
|
+
record.genericRequest.definition.responseSchema,
|
|
714
|
+
entry.payload,
|
|
628
715
|
)
|
|
716
|
+
if (!parsed.success) {
|
|
717
|
+
return {
|
|
718
|
+
errors: [
|
|
719
|
+
interruptItemError(
|
|
720
|
+
input,
|
|
721
|
+
record.interruptId,
|
|
722
|
+
'invalid-payload',
|
|
723
|
+
`Interrupt ${record.interruptId} payload is invalid.`,
|
|
724
|
+
),
|
|
725
|
+
],
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
genericInterrupts.set(record.interruptId, {
|
|
729
|
+
interruptId: record.interruptId,
|
|
730
|
+
status: 'resolved',
|
|
731
|
+
payload: parsed.data,
|
|
732
|
+
})
|
|
629
733
|
continue
|
|
630
734
|
}
|
|
631
735
|
if (entry.status === 'cancelled') {
|
|
@@ -711,17 +815,44 @@ export function readUnopenedInterruptBinding(
|
|
|
711
815
|
const interruptId = stringField(raw, 'interruptId')
|
|
712
816
|
const responseSchemaHash = stringField(raw, 'responseSchemaHash')
|
|
713
817
|
const expiresAt = stringField(raw, 'expiresAt')
|
|
714
|
-
if (!interruptId ||
|
|
818
|
+
if (!interruptId || responseSchemaHash === '') return undefined
|
|
819
|
+
if (expiresAt !== undefined && !Number.isFinite(Date.parse(expiresAt))) {
|
|
820
|
+
return undefined
|
|
821
|
+
}
|
|
715
822
|
const v = INTERRUPT_BINDING_VERSION
|
|
716
823
|
if (kind === 'generic') {
|
|
824
|
+
const definitionId = stringField(raw, 'definitionId')
|
|
825
|
+
const key = stringField(raw, 'key')
|
|
826
|
+
const batchIndex = raw['batchIndex']
|
|
827
|
+
const payloadSchemaHash = stringField(raw, 'payloadSchemaHash')
|
|
828
|
+
const hasFirstPartyFields =
|
|
829
|
+
definitionId !== undefined ||
|
|
830
|
+
key !== undefined ||
|
|
831
|
+
batchIndex !== undefined ||
|
|
832
|
+
payloadSchemaHash !== undefined
|
|
833
|
+
if (
|
|
834
|
+
hasFirstPartyFields &&
|
|
835
|
+
(!definitionId ||
|
|
836
|
+
!key ||
|
|
837
|
+
typeof batchIndex !== 'number' ||
|
|
838
|
+
!Number.isInteger(batchIndex) ||
|
|
839
|
+
batchIndex < 0)
|
|
840
|
+
) {
|
|
841
|
+
return undefined
|
|
842
|
+
}
|
|
717
843
|
return {
|
|
718
844
|
v,
|
|
719
845
|
kind,
|
|
720
846
|
interruptId,
|
|
721
|
-
responseSchemaHash,
|
|
847
|
+
...(responseSchemaHash ? { responseSchemaHash } : {}),
|
|
722
848
|
...(expiresAt ? { expiresAt } : {}),
|
|
849
|
+
...(definitionId ? { definitionId } : {}),
|
|
850
|
+
...(key ? { key } : {}),
|
|
851
|
+
...(typeof batchIndex === 'number' ? { batchIndex } : {}),
|
|
852
|
+
...(payloadSchemaHash ? { payloadSchemaHash } : {}),
|
|
723
853
|
}
|
|
724
854
|
}
|
|
855
|
+
if (!responseSchemaHash) return undefined
|
|
725
856
|
const toolName = stringField(raw, 'toolName')
|
|
726
857
|
const toolCallId = stringField(raw, 'toolCallId')
|
|
727
858
|
if (!toolName || !toolCallId) return undefined
|
package/src/interrupts.ts
CHANGED
|
@@ -84,12 +84,15 @@ interface InterruptBindingBase {
|
|
|
84
84
|
interruptId: string
|
|
85
85
|
interruptedRunId: string
|
|
86
86
|
generation: number
|
|
87
|
-
responseSchemaHash: string
|
|
88
87
|
expiresAt?: string
|
|
89
88
|
}
|
|
90
89
|
|
|
90
|
+
interface ResponseSchemaInterruptBindingBase extends InterruptBindingBase {
|
|
91
|
+
responseSchemaHash: string
|
|
92
|
+
}
|
|
93
|
+
|
|
91
94
|
export type InterruptBinding =
|
|
92
|
-
| (
|
|
95
|
+
| (ResponseSchemaInterruptBindingBase & {
|
|
93
96
|
kind: 'tool-approval'
|
|
94
97
|
toolName: string
|
|
95
98
|
toolCallId: string
|
|
@@ -97,7 +100,7 @@ export type InterruptBinding =
|
|
|
97
100
|
inputSchemaHash: string
|
|
98
101
|
approvalSchemaHash: string
|
|
99
102
|
})
|
|
100
|
-
| (
|
|
103
|
+
| (ResponseSchemaInterruptBindingBase & {
|
|
101
104
|
kind: 'client-tool-execution'
|
|
102
105
|
toolName: string
|
|
103
106
|
toolCallId: string
|
|
@@ -105,6 +108,13 @@ export type InterruptBinding =
|
|
|
105
108
|
})
|
|
106
109
|
| (InterruptBindingBase & {
|
|
107
110
|
kind: 'generic'
|
|
111
|
+
/** Omitted when the generic interrupt accepts an unvalidated response. */
|
|
112
|
+
responseSchemaHash?: string
|
|
113
|
+
/** Present only for a first-party generic interrupt. */
|
|
114
|
+
definitionId?: string
|
|
115
|
+
key?: string
|
|
116
|
+
batchIndex?: number
|
|
117
|
+
payloadSchemaHash?: string
|
|
108
118
|
})
|
|
109
119
|
|
|
110
120
|
export type UnopenedInterruptBinding = InterruptBinding extends infer TBinding
|
|
@@ -12,8 +12,8 @@ import type { TokenUsage } from '../types'
|
|
|
12
12
|
* `gen_ai.usage.cost` and `gen_ai.usage.total_tokens` are de-facto extensions
|
|
13
13
|
* consumed by backends like PostHog (which otherwise re-derive cost from their
|
|
14
14
|
* own price tables, losing cache discounts and gateway markup). Fields with no
|
|
15
|
-
* semconv or de-facto convention (`
|
|
16
|
-
* `unitsBilled`) are TanStack-namespaced.
|
|
15
|
+
* semconv or de-facto convention (`billed`, `costDetails`, and the deprecated
|
|
16
|
+
* `durationSeconds`/`unitsBilled`) are TanStack-namespaced.
|
|
17
17
|
*
|
|
18
18
|
* Shared by `otelMiddleware` across every activity (chat and the media
|
|
19
19
|
* activities) so usage lands identically whichever activity produced the span.
|
|
@@ -30,6 +30,16 @@ export function usageAttributes(
|
|
|
30
30
|
'gen_ai.usage.input_tokens': usage.promptTokens,
|
|
31
31
|
'gen_ai.usage.output_tokens': usage.completionTokens,
|
|
32
32
|
}
|
|
33
|
+
// The self-describing billed quantity: the unit rides along as a string
|
|
34
|
+
// attribute so backends can label/aggregate non-token usage without
|
|
35
|
+
// out-of-band knowledge of the provider.
|
|
36
|
+
if (usage.billed !== undefined) {
|
|
37
|
+
const quantity = firstNumber(usage.billed.quantity)
|
|
38
|
+
if (quantity !== undefined) {
|
|
39
|
+
attrs['tanstack.ai.usage.billed_quantity'] = quantity
|
|
40
|
+
attrs['tanstack.ai.usage.billed_unit'] = usage.billed.unit
|
|
41
|
+
}
|
|
42
|
+
}
|
|
33
43
|
const optional: Array<[key: string, value: unknown]> = [
|
|
34
44
|
['gen_ai.usage.total_tokens', usage.totalTokens],
|
|
35
45
|
['gen_ai.usage.cost', usage.cost],
|
|
@@ -78,7 +78,7 @@ function combineFailures(
|
|
|
78
78
|
)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function runErrorChunk(
|
|
81
|
+
export function runErrorChunk(
|
|
82
82
|
error: unknown,
|
|
83
83
|
): Extract<StreamChunk, { type: 'RUN_ERROR' }> {
|
|
84
84
|
const payload = toRunErrorPayload(error)
|
|
@@ -366,7 +366,7 @@ export const RUN_ACCEPTED_EVENT = 'run.accepted'
|
|
|
366
366
|
* The returned `getId` maps each forwarded chunk to the exact opaque offset
|
|
367
367
|
* returned by the durability adapter for the SSE `id:` line.
|
|
368
368
|
*/
|
|
369
|
-
function durableStreamSource<TOffset extends string>(
|
|
369
|
+
export function durableStreamSource<TOffset extends string>(
|
|
370
370
|
stream: AsyncIterable<StreamChunk>,
|
|
371
371
|
durability: StreamDurability<TOffset>,
|
|
372
372
|
options: {
|