@tanstack/ai 0.47.3 → 0.49.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.
Files changed (135) hide show
  1. package/README.md +2 -1
  2. package/dist/esm/activities/chat/adapter.d.ts +5 -4
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/index.d.ts +4 -7
  5. package/dist/esm/activities/chat/index.js +201 -243
  6. package/dist/esm/activities/chat/index.js.map +1 -1
  7. package/dist/esm/activities/chat/messages.js +115 -25
  8. package/dist/esm/activities/chat/messages.js.map +1 -1
  9. package/dist/esm/activities/chat/stream/processor.d.ts +38 -17
  10. package/dist/esm/activities/chat/stream/processor.js +186 -101
  11. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/tool-calls.d.ts +3 -2
  13. package/dist/esm/activities/chat/tools/tool-calls.js +15 -10
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateVideo/index.js +6 -6
  16. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  17. package/dist/esm/activities/stream-generation-result.js +7 -8
  18. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  19. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -1
  20. package/dist/esm/activities/summarize/chat-stream-summarize.js +56 -53
  21. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  22. package/dist/esm/adapter-internals.d.ts +2 -0
  23. package/dist/esm/adapter-internals.js +3 -1
  24. package/dist/esm/byok/define-provider.d.ts +23 -0
  25. package/dist/esm/byok/define-provider.js +19 -0
  26. package/dist/esm/byok/define-provider.js.map +1 -0
  27. package/dist/esm/byok/errors.d.ts +13 -0
  28. package/dist/esm/byok/errors.js +29 -0
  29. package/dist/esm/byok/errors.js.map +1 -0
  30. package/dist/esm/byok/get-key.d.ts +10 -0
  31. package/dist/esm/byok/get-key.js +26 -0
  32. package/dist/esm/byok/get-key.js.map +1 -0
  33. package/dist/esm/byok/missing.d.ts +11 -0
  34. package/dist/esm/byok/missing.js +30 -0
  35. package/dist/esm/byok/missing.js.map +1 -0
  36. package/dist/esm/byok/providers.d.ts +13 -0
  37. package/dist/esm/byok/providers.js +18 -0
  38. package/dist/esm/byok/providers.js.map +1 -0
  39. package/dist/esm/byok/scrub.d.ts +2 -0
  40. package/dist/esm/byok/scrub.js +17 -0
  41. package/dist/esm/byok/scrub.js.map +1 -0
  42. package/dist/esm/byok/server.d.ts +3 -0
  43. package/dist/esm/byok/server.js +3 -0
  44. package/dist/esm/byok.d.ts +8 -0
  45. package/dist/esm/byok.js +6 -0
  46. package/dist/esm/client.d.ts +8 -1
  47. package/dist/esm/client.js +7 -2
  48. package/dist/esm/client.js.map +1 -1
  49. package/dist/esm/index.d.ts +6 -0
  50. package/dist/esm/index.js +6 -2
  51. package/dist/esm/interrupt-resume.js +4 -4
  52. package/dist/esm/interrupt-resume.js.map +1 -1
  53. package/dist/esm/middlewares/otel.js +11 -4
  54. package/dist/esm/middlewares/otel.js.map +1 -1
  55. package/dist/esm/stream-to-response.js +8 -5
  56. package/dist/esm/stream-to-response.js.map +1 -1
  57. package/dist/esm/stream-to-websocket.js +4 -2
  58. package/dist/esm/stream-to-websocket.js.map +1 -1
  59. package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
  60. package/dist/esm/strip-to-spec-middleware.js +24 -22
  61. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  62. package/dist/esm/types.d.ts +82 -305
  63. package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
  64. package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
  65. package/dist/esm/utilities/ag-ui-usage.js +66 -0
  66. package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
  67. package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
  68. package/dist/esm/utilities/ag-ui-wire.js +71 -30
  69. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  70. package/dist/esm/utilities/chat-params.d.ts +3 -3
  71. package/dist/esm/utilities/chat-params.js +10 -30
  72. package/dist/esm/utilities/chat-params.js.map +1 -1
  73. package/dist/esm/utilities/chunk-ids.d.ts +5 -0
  74. package/dist/esm/utilities/chunk-ids.js +25 -0
  75. package/dist/esm/utilities/chunk-ids.js.map +1 -0
  76. package/dist/esm/utilities/merge-metadata.d.ts +14 -0
  77. package/dist/esm/utilities/merge-metadata.js +43 -0
  78. package/dist/esm/utilities/merge-metadata.js.map +1 -0
  79. package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
  80. package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
  81. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
  82. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  83. package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
  84. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
  85. package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
  86. package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
  87. package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
  88. package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
  89. package/dist/esm/utilities/spec-event-keys.js +56 -0
  90. package/dist/esm/utilities/spec-event-keys.js.map +1 -0
  91. package/dist/esm/utilities/structured-output-events.d.ts +3 -3
  92. package/dist/esm/utilities/structured-output-events.js.map +1 -1
  93. package/package.json +10 -2
  94. package/skills/ai-core/chat-experience/SKILL.md +2 -2
  95. package/src/activities/chat/adapter.ts +4 -4
  96. package/src/activities/chat/index.ts +398 -400
  97. package/src/activities/chat/messages.ts +174 -34
  98. package/src/activities/chat/stream/processor.ts +289 -218
  99. package/src/activities/chat/tools/tool-calls.ts +23 -22
  100. package/src/activities/generateVideo/index.ts +7 -6
  101. package/src/activities/stream-generation-result.ts +8 -12
  102. package/src/activities/summarize/chat-stream-summarize.ts +94 -70
  103. package/src/adapter-internals.ts +2 -0
  104. package/src/byok/define-provider.ts +46 -0
  105. package/src/byok/errors.ts +36 -0
  106. package/src/byok/get-key.ts +27 -0
  107. package/src/byok/missing.ts +43 -0
  108. package/src/byok/providers.ts +27 -0
  109. package/src/byok/scrub.ts +16 -0
  110. package/src/byok/server.ts +3 -0
  111. package/src/byok.ts +17 -0
  112. package/src/client.ts +13 -0
  113. package/src/index.ts +6 -0
  114. package/src/interrupt-resume.ts +14 -13
  115. package/src/middlewares/otel.ts +16 -8
  116. package/src/stream-to-response.ts +11 -4
  117. package/src/stream-to-websocket.ts +3 -1
  118. package/src/strip-to-spec-middleware.ts +48 -24
  119. package/src/types.ts +109 -393
  120. package/src/utilities/adapter-yield-chunk.ts +30 -0
  121. package/src/utilities/ag-ui-usage.test.ts +194 -0
  122. package/src/utilities/ag-ui-usage.ts +148 -0
  123. package/src/utilities/ag-ui-wire.ts +149 -31
  124. package/src/utilities/chat-params.ts +22 -39
  125. package/src/utilities/chunk-ids.ts +24 -0
  126. package/src/utilities/merge-metadata.test.ts +117 -0
  127. package/src/utilities/merge-metadata.ts +59 -0
  128. package/src/utilities/normalize-stream-chunk.test.ts +423 -0
  129. package/src/utilities/normalize-stream-chunk.ts +186 -0
  130. package/src/utilities/reasoning-encrypted-value.ts +18 -0
  131. package/src/utilities/restore-inbound-chunk.test.ts +133 -0
  132. package/src/utilities/restore-inbound-chunk.ts +72 -0
  133. package/src/utilities/spec-event-keys.test.ts +34 -0
  134. package/src/utilities/spec-event-keys.ts +74 -0
  135. package/src/utilities/structured-output-events.ts +3 -3
package/src/index.ts CHANGED
@@ -459,6 +459,12 @@ export { generationParamsFromBody, generationParamsFromRequest } from './client'
459
459
 
460
460
  // AG-UI wire serialization (used internally by @tanstack/ai-client)
461
461
  export { uiMessagesToWire } from './utilities/ag-ui-wire'
462
+ export { mergeMetadata, withTanstackMetadata } from './utilities/merge-metadata'
463
+ export { fromSpecTokenUsage, toSpecTokenUsage } from './utilities/ag-ui-usage'
464
+ export type { SpecTokenUsage } from './utilities/ag-ui-usage'
465
+ export { normalizeStreamChunk } from './utilities/normalize-stream-chunk'
466
+ export type { AdapterYieldChunk } from './utilities/adapter-yield-chunk'
467
+ export { getChunkRunId, getChunkThreadId } from './utilities/chunk-ids'
462
468
  export type { WireMessage } from './utilities/ag-ui-wire'
463
469
  export {
464
470
  isContentPart,
@@ -445,6 +445,20 @@ export async function validateInterruptResumeBatch(
445
445
  continue
446
446
  }
447
447
 
448
+ if (entry.status === 'cancelled') {
449
+ if (entry.payload !== undefined) {
450
+ errors.push(
451
+ interruptItemError(
452
+ input,
453
+ record.interruptId,
454
+ 'invalid-payload',
455
+ `Cancelled interrupt ${record.interruptId} must not include a payload.`,
456
+ ),
457
+ )
458
+ }
459
+ continue
460
+ }
461
+
448
462
  const tool = runtimeTool(input.tools, binding.toolName)
449
463
  if (!tool) {
450
464
  errors.push(
@@ -511,19 +525,6 @@ export async function validateInterruptResumeBatch(
511
525
  }
512
526
  }
513
527
 
514
- if (entry.status === 'cancelled') {
515
- if (entry.payload !== undefined) {
516
- errors.push(
517
- interruptItemError(
518
- input,
519
- record.interruptId,
520
- 'invalid-payload',
521
- `Cancelled interrupt ${record.interruptId} must not include a payload.`,
522
- ),
523
- )
524
- }
525
- continue
526
- }
527
528
  if (schemaDrifted) continue
528
529
 
529
530
  if (binding.kind === 'client-tool-execution') {
@@ -10,6 +10,8 @@ import {
10
10
  } from '../utilities/sampling-keys'
11
11
  import { firstNumber } from '../utilities/numbers'
12
12
  import { errorMessage, errorTypeName } from '../utilities/errors'
13
+ import { rebuildTokenUsage } from '../utilities/ag-ui-usage'
14
+ import { tanstackMetadata } from '../utilities/merge-metadata'
13
15
  import { usageAttributes } from './usage-attributes'
14
16
  import type {
15
17
  AttributeValue,
@@ -631,26 +633,32 @@ export function otelMiddleware(
631
633
  }
632
634
 
633
635
  if (chunk.type !== 'RUN_FINISHED') return
636
+ const tanstack = tanstackMetadata(chunk)
637
+ const extra = chunk as {
638
+ finishReason?: string | null
639
+ model?: string
640
+ }
641
+ const finishReason = extra.finishReason ?? tanstack?.finishReason
642
+ const model = extra.model ?? tanstack?.model
634
643
  // Capture for the root-span finish_reasons attribute set in onFinish,
635
644
  // which receives base-shaped info without a finishReason field.
636
- if (chunk.finishReason) state.lastFinishReason = chunk.finishReason
645
+ if (finishReason) state.lastFinishReason = finishReason
637
646
  const span = state.currentIterationSpan
638
647
  if (!span) return
639
648
 
640
- if (chunk.finishReason) {
641
- span.setAttribute('gen_ai.response.finish_reasons', [
642
- chunk.finishReason,
643
- ])
649
+ if (finishReason) {
650
+ span.setAttribute('gen_ai.response.finish_reasons', [finishReason])
644
651
  }
645
- if (chunk.model) span.setAttribute('gen_ai.response.model', chunk.model)
652
+ if (model) span.setAttribute('gen_ai.response.model', model)
646
653
 
647
654
  // Set usage attributes on the iteration span directly from the chunk
648
655
  // so they're available before `onUsage` fires. Histogram recording is
649
656
  // deliberately NOT done here — the chat runner always invokes
650
657
  // `runOnUsage` when `chunk.usage` is present, and `onUsage` is the
651
658
  // canonical place for the metric. Recording in both would double-count.
652
- if (chunk.usage) {
653
- span.setAttributes(usageAttributes(chunk.usage))
659
+ const tokenUsage = rebuildTokenUsage(chunk.usage, tanstack?.usage)
660
+ if (tokenUsage) {
661
+ span.setAttributes(usageAttributes(tokenUsage))
654
662
  }
655
663
 
656
664
  if (captureContent && state.assistantTextBuffer.length > 0) {
@@ -8,6 +8,7 @@ import { wasRunDetached } from './delivery-detach'
8
8
  import { notifyRunDisconnected } from './delivery-disconnect'
9
9
  import { resolveResumeRunId } from './stream-durability'
10
10
  import { EventType } from './types'
11
+ import { toWireChunk } from './strip-to-spec-middleware'
11
12
  import { resolveDebugOption } from './logger/resolve'
12
13
  import { runErrorEventToError } from './utilities/errors'
13
14
  import type { LockStore } from './activities/chat/middleware/locks'
@@ -287,10 +288,13 @@ function sseEncoders(
287
288
  encodeChunk: (chunk, index) => {
288
289
  const id = getId?.(chunk, index)
289
290
  const idLine = id === undefined ? '' : `id: ${id}\n`
290
- return encoder.encode(`${idLine}data: ${JSON.stringify(chunk)}\n\n`)
291
+ const wire = toWireChunk(chunk)
292
+ return encoder.encode(`${idLine}data: ${JSON.stringify(wire)}\n\n`)
291
293
  },
292
294
  encodeError: (error) =>
293
- encoder.encode(`data: ${JSON.stringify(runErrorChunk(error))}\n\n`),
295
+ encoder.encode(
296
+ `data: ${JSON.stringify(toWireChunk(runErrorChunk(error)))}\n\n`,
297
+ ),
294
298
  }
295
299
  }
296
300
 
@@ -1069,12 +1073,15 @@ function ndjsonEncoders(
1069
1073
  return {
1070
1074
  encodeChunk: (chunk, index) => {
1071
1075
  const id = getId?.(chunk, index)
1076
+ const wire = toWireChunk(chunk)
1072
1077
  const line =
1073
- id === undefined ? JSON.stringify(chunk) : JSON.stringify({ id, chunk })
1078
+ id === undefined
1079
+ ? JSON.stringify(wire)
1080
+ : JSON.stringify({ id, chunk: wire })
1074
1081
  return encoder.encode(`${line}\n`)
1075
1082
  },
1076
1083
  encodeError: (error) =>
1077
- encoder.encode(`${JSON.stringify(runErrorChunk(error))}\n`),
1084
+ encoder.encode(`${JSON.stringify(toWireChunk(runErrorChunk(error)))}\n`),
1078
1085
  }
1079
1086
  }
1080
1087
 
@@ -1,5 +1,6 @@
1
1
  import { chatParamsFromRequestBody } from './utilities/chat-params'
2
2
  import { durableStreamSource, runErrorChunk } from './stream-to-response'
3
+ import { toWireChunk } from './strip-to-spec-middleware'
3
4
  import { resolveDebugOption } from './logger/resolve'
4
5
  import type { StreamDurability } from './stream-durability'
5
6
  import type { DebugOption } from './logger/types'
@@ -35,7 +36,8 @@ export function encodeWsFrame(
35
36
  chunk: StreamChunk,
36
37
  id: string | undefined,
37
38
  ): string {
38
- return JSON.stringify(id === undefined ? chunk : { id, chunk })
39
+ const wire = toWireChunk(chunk)
40
+ return JSON.stringify(id === undefined ? wire : { id, chunk: wire })
39
41
  }
40
42
 
41
43
  /**
@@ -1,34 +1,58 @@
1
- import type { ChatMiddleware } from './activities/chat/middleware/types'
2
1
  import type { StreamChunk } from './types'
2
+ import { EventType } from './types'
3
+ import type { AdapterYieldChunk } from './utilities/adapter-yield-chunk'
4
+ import { isTanstackUsage, toSpecTokenUsage } from './utilities/ag-ui-usage'
5
+ import {
6
+ tanstackMetadata,
7
+ withTanstackMetadata,
8
+ } from './utilities/merge-metadata'
9
+ import { normalizeStreamChunk } from './utilities/normalize-stream-chunk'
10
+ import { isSpecTopLevelKey } from './utilities/spec-event-keys'
3
11
 
4
12
  /**
5
- * Strip only the deprecated nested `error` object from RUN_ERROR events.
6
- * The flat `message`/`code` fields are the spec-compliant form.
7
- *
8
- * All other fields pass through unchanged. @ag-ui/core's BaseEventSchema
9
- * uses `.passthrough()`, so extra fields (model, content, usage,
10
- * finishReason, toolName, stepId, etc.) are allowed and won't break
11
- * spec validation or verifyEvents.
13
+ * Delete unknown top-level keys from a stream chunk.
14
+ * Keep only AG-UI spec keys for this event type.
15
+ * Convert TanStack TokenUsage objects to the spec `usage[]` array.
12
16
  */
13
- export function stripToSpec(chunk: StreamChunk): StreamChunk {
14
- // Only strip the deprecated nested error object from RUN_ERROR.
15
- if (chunk.type === 'RUN_ERROR' && 'error' in chunk) {
16
- const { error: _deprecated, ...rest } = chunk
17
- return rest
17
+ export function stripToSpec(
18
+ chunk: StreamChunk | AdapterYieldChunk,
19
+ ): StreamChunk {
20
+ const out: Record<string, unknown> = {}
21
+ for (const [key, value] of Object.entries(chunk)) {
22
+ if (isSpecTopLevelKey(chunk.type, key) && value !== undefined) {
23
+ out[key] = value
24
+ }
18
25
  }
19
- return chunk
26
+
27
+ if (
28
+ (chunk.type === EventType.RUN_FINISHED ||
29
+ chunk.type === EventType.RUN_ERROR) &&
30
+ isTanstackUsage(out.usage)
31
+ ) {
32
+ const model = tanstackMetadata(chunk)?.model
33
+ const { usage, leftover } = toSpecTokenUsage(out.usage, {
34
+ model: typeof model === 'string' ? model : undefined,
35
+ })
36
+ out.usage = usage
37
+ if (leftover !== undefined) {
38
+ return withTanstackMetadata(out as StreamChunk, {
39
+ usage: leftover,
40
+ }) as StreamChunk
41
+ }
42
+ }
43
+
44
+ return out as StreamChunk
20
45
  }
21
46
 
22
47
  /**
23
- * Middleware that ensures events are AG-UI spec compliant.
24
- * Currently only strips the deprecated nested `error` object from RUN_ERROR.
25
- * All other fields pass through unchanged (passthrough allowed by spec).
48
+ * Move TanStack extras into `metadata.tanstack`, then keep only spec keys.
49
+ * Custom servers that skip `chat()` still round-trip `finishReason` on SSE/HTTP/WS.
50
+ * Fan-out extras (encrypted-value, TOOL_CALL_RESULT) stay on the `chat()` path;
51
+ * this encoder is 1:1 with the durability log offset.
26
52
  */
27
- export function stripToSpecMiddleware(): ChatMiddleware {
28
- return {
29
- name: 'strip-to-spec',
30
- onChunk(_ctx, chunk) {
31
- return stripToSpec(chunk)
32
- },
33
- }
53
+ export function toWireChunk(
54
+ chunk: StreamChunk | AdapterYieldChunk,
55
+ ): StreamChunk {
56
+ const [normalized] = normalizeStreamChunk(chunk)
57
+ return stripToSpec(normalized ?? chunk)
34
58
  }