@tanstack/ai 0.47.3 → 0.48.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.
Files changed (101) 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/client.d.ts +8 -1
  25. package/dist/esm/client.js +7 -2
  26. package/dist/esm/client.js.map +1 -1
  27. package/dist/esm/index.d.ts +6 -0
  28. package/dist/esm/index.js +6 -2
  29. package/dist/esm/middlewares/otel.js +11 -4
  30. package/dist/esm/middlewares/otel.js.map +1 -1
  31. package/dist/esm/stream-to-response.js +8 -5
  32. package/dist/esm/stream-to-response.js.map +1 -1
  33. package/dist/esm/stream-to-websocket.js +4 -2
  34. package/dist/esm/stream-to-websocket.js.map +1 -1
  35. package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
  36. package/dist/esm/strip-to-spec-middleware.js +24 -22
  37. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  38. package/dist/esm/types.d.ts +82 -305
  39. package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
  40. package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
  41. package/dist/esm/utilities/ag-ui-usage.js +66 -0
  42. package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
  43. package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
  44. package/dist/esm/utilities/ag-ui-wire.js +71 -30
  45. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  46. package/dist/esm/utilities/chat-params.d.ts +3 -3
  47. package/dist/esm/utilities/chat-params.js +10 -30
  48. package/dist/esm/utilities/chat-params.js.map +1 -1
  49. package/dist/esm/utilities/chunk-ids.d.ts +5 -0
  50. package/dist/esm/utilities/chunk-ids.js +25 -0
  51. package/dist/esm/utilities/chunk-ids.js.map +1 -0
  52. package/dist/esm/utilities/merge-metadata.d.ts +14 -0
  53. package/dist/esm/utilities/merge-metadata.js +43 -0
  54. package/dist/esm/utilities/merge-metadata.js.map +1 -0
  55. package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
  56. package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
  57. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
  58. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  59. package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
  60. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
  61. package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
  62. package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
  63. package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
  64. package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
  65. package/dist/esm/utilities/spec-event-keys.js +56 -0
  66. package/dist/esm/utilities/spec-event-keys.js.map +1 -0
  67. package/dist/esm/utilities/structured-output-events.d.ts +3 -3
  68. package/dist/esm/utilities/structured-output-events.js.map +1 -1
  69. package/package.json +2 -2
  70. package/src/activities/chat/adapter.ts +4 -4
  71. package/src/activities/chat/index.ts +398 -400
  72. package/src/activities/chat/messages.ts +174 -34
  73. package/src/activities/chat/stream/processor.ts +289 -218
  74. package/src/activities/chat/tools/tool-calls.ts +23 -22
  75. package/src/activities/generateVideo/index.ts +7 -6
  76. package/src/activities/stream-generation-result.ts +8 -12
  77. package/src/activities/summarize/chat-stream-summarize.ts +94 -70
  78. package/src/adapter-internals.ts +2 -0
  79. package/src/client.ts +13 -0
  80. package/src/index.ts +6 -0
  81. package/src/middlewares/otel.ts +16 -8
  82. package/src/stream-to-response.ts +11 -4
  83. package/src/stream-to-websocket.ts +3 -1
  84. package/src/strip-to-spec-middleware.ts +48 -24
  85. package/src/types.ts +109 -393
  86. package/src/utilities/adapter-yield-chunk.ts +30 -0
  87. package/src/utilities/ag-ui-usage.test.ts +194 -0
  88. package/src/utilities/ag-ui-usage.ts +148 -0
  89. package/src/utilities/ag-ui-wire.ts +149 -31
  90. package/src/utilities/chat-params.ts +22 -39
  91. package/src/utilities/chunk-ids.ts +24 -0
  92. package/src/utilities/merge-metadata.test.ts +117 -0
  93. package/src/utilities/merge-metadata.ts +59 -0
  94. package/src/utilities/normalize-stream-chunk.test.ts +423 -0
  95. package/src/utilities/normalize-stream-chunk.ts +186 -0
  96. package/src/utilities/reasoning-encrypted-value.ts +18 -0
  97. package/src/utilities/restore-inbound-chunk.test.ts +133 -0
  98. package/src/utilities/restore-inbound-chunk.ts +72 -0
  99. package/src/utilities/spec-event-keys.test.ts +34 -0
  100. package/src/utilities/spec-event-keys.ts +74 -0
  101. package/src/utilities/structured-output-events.ts +3 -3
@@ -0,0 +1,133 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { EventType } from '../types'
3
+ import type { RunFinishedEvent, StreamChunk } from '../types'
4
+ import {
5
+ restoreInboundChunk,
6
+ restorePublicUsage,
7
+ } from './restore-inbound-chunk'
8
+
9
+ function runFinished(
10
+ overrides?: Omit<Partial<RunFinishedEvent>, 'type'>,
11
+ ): RunFinishedEvent {
12
+ return {
13
+ type: EventType.RUN_FINISHED,
14
+ threadId: 't1',
15
+ runId: 'r1',
16
+ ...overrides,
17
+ }
18
+ }
19
+
20
+ describe('restorePublicUsage', () => {
21
+ it('rebuilds TokenUsage promptTokens from spec usage[] and leftover', () => {
22
+ const chunk = runFinished({
23
+ usage: [
24
+ {
25
+ inputTokens: 10,
26
+ outputTokens: 5,
27
+ totalTokens: 15,
28
+ cachedInputTokens: 3,
29
+ },
30
+ ],
31
+ metadata: {
32
+ tanstack: {
33
+ model: 'gpt-5.5',
34
+ usage: {
35
+ cost: 0.02,
36
+ promptTokensDetails: { audioTokens: 1 },
37
+ },
38
+ },
39
+ },
40
+ })
41
+
42
+ restorePublicUsage(chunk)
43
+
44
+ expect(chunk.usage).toEqual({
45
+ promptTokens: 10,
46
+ completionTokens: 5,
47
+ totalTokens: 15,
48
+ cost: 0.02,
49
+ promptTokensDetails: { cachedTokens: 3, audioTokens: 1 },
50
+ })
51
+ expect(chunk).not.toHaveProperty('model')
52
+ })
53
+
54
+ it('restores TOOL_CALL_START toolName from toolCallName', () => {
55
+ const chunk: StreamChunk = {
56
+ type: EventType.TOOL_CALL_START,
57
+ toolCallId: 'tc1',
58
+ toolCallName: 'get_weather',
59
+ }
60
+
61
+ restorePublicUsage(chunk)
62
+
63
+ if (chunk.type !== EventType.TOOL_CALL_START) {
64
+ throw new Error('expected TOOL_CALL_START')
65
+ }
66
+ expect(chunk.toolName).toBe('get_weather')
67
+ })
68
+
69
+ it('restores TOOL_CALL_END input from metadata.tanstack.input', () => {
70
+ const chunk: StreamChunk = {
71
+ type: EventType.TOOL_CALL_END,
72
+ toolCallId: 'tc1',
73
+ metadata: { tanstack: { input: { q: 'sf' } } },
74
+ }
75
+
76
+ restorePublicUsage(chunk)
77
+
78
+ if (chunk.type !== EventType.TOOL_CALL_END) {
79
+ throw new Error('expected TOOL_CALL_END')
80
+ }
81
+ expect(chunk.input).toEqual({ q: 'sf' })
82
+ })
83
+ })
84
+
85
+ describe('restoreInboundChunk', () => {
86
+ it('copies metadata.tanstack extras back to top-level fields', () => {
87
+ const restored = restoreInboundChunk(
88
+ runFinished({
89
+ usage: [{ inputTokens: 10, outputTokens: 5, totalTokens: 15 }],
90
+ metadata: {
91
+ tanstack: {
92
+ model: 'gpt-5.5',
93
+ finishReason: 'stop',
94
+ usage: { cost: 0.02 },
95
+ },
96
+ },
97
+ }),
98
+ )
99
+
100
+ expect(restored.finishReason).toBe('stop')
101
+ expect(restored.model).toBe('gpt-5.5')
102
+ expect(restored.usage).toEqual({
103
+ promptTokens: 10,
104
+ completionTokens: 5,
105
+ totalTokens: 15,
106
+ cost: 0.02,
107
+ })
108
+ })
109
+
110
+ it('does not overwrite extras already on the chunk', () => {
111
+ const restored = restoreInboundChunk(
112
+ runFinished({
113
+ finishReason: 'length',
114
+ model: 'kept',
115
+ metadata: {
116
+ tanstack: { finishReason: 'stop', model: 'other' },
117
+ },
118
+ }),
119
+ )
120
+
121
+ expect(restored.finishReason).toBe('length')
122
+ expect(restored.model).toBe('kept')
123
+ })
124
+
125
+ it('leaves spec-only chunks unchanged when there is no tanstack bag', () => {
126
+ const chunk = {
127
+ type: EventType.TEXT_MESSAGE_CONTENT,
128
+ messageId: 'm1',
129
+ delta: 'Hi',
130
+ } as const satisfies StreamChunk
131
+ expect(restoreInboundChunk(chunk)).toBe(chunk)
132
+ })
133
+ })
@@ -0,0 +1,72 @@
1
+ import { EventType } from '../types'
2
+ import type { StreamChunk } from '../types'
3
+ import type { AdapterYieldChunk } from './adapter-yield-chunk'
4
+ import { isTanstackUsage, rebuildTokenUsage } from './ag-ui-usage'
5
+ import { tanstackMetadata } from './merge-metadata'
6
+
7
+ /**
8
+ * Rebuild TanStack `TokenUsage` (`promptTokens`) from spec `usage[]` plus
9
+ * leftover fields in `metadata.tanstack.usage`. Restore in-process aliases
10
+ * that the wire does not keep (`toolName`, `TOOL_CALL_END.input`). Mutates
11
+ * in place so WeakMap run-id stamps stay attached.
12
+ */
13
+ export function restorePublicUsage(chunk: StreamChunk): StreamChunk {
14
+ if (
15
+ (chunk.type === EventType.RUN_FINISHED ||
16
+ chunk.type === EventType.RUN_ERROR) &&
17
+ (Array.isArray(chunk.usage) || isTanstackUsage(chunk.usage))
18
+ ) {
19
+ const rebuilt = rebuildTokenUsage(
20
+ chunk.usage,
21
+ tanstackMetadata(chunk)?.usage,
22
+ )
23
+ if (rebuilt !== undefined) {
24
+ chunk.usage = rebuilt
25
+ }
26
+ }
27
+
28
+ if (
29
+ chunk.type === EventType.TOOL_CALL_START &&
30
+ chunk.toolName === undefined &&
31
+ chunk.toolCallName
32
+ ) {
33
+ chunk.toolName = chunk.toolCallName
34
+ }
35
+
36
+ if (chunk.type === EventType.TOOL_CALL_END && chunk.input === undefined) {
37
+ const input = tanstackMetadata(chunk)?.input
38
+ if (input !== undefined) {
39
+ chunk.input = input
40
+ }
41
+ }
42
+
43
+ return chunk
44
+ }
45
+
46
+ /**
47
+ * Rebuild the pre-wire chunk shape after SSE/HTTP/WS ingest.
48
+ * Copies `metadata.tanstack` extras back to top-level fields and rebuilds
49
+ * TanStack `TokenUsage` from spec `usage[]` when present.
50
+ */
51
+ export function restoreInboundChunk(chunk: StreamChunk): AdapterYieldChunk {
52
+ restorePublicUsage(chunk)
53
+ const tanstack = tanstackMetadata(chunk)
54
+ const next = chunk as AdapterYieldChunk & Record<string, unknown>
55
+
56
+ if (tanstack == null) {
57
+ return next
58
+ }
59
+
60
+ for (const [key, value] of Object.entries(tanstack)) {
61
+ if (key === 'usage' || key === 'interruptErrors') continue
62
+ if (next[key] === undefined && value !== undefined) {
63
+ next[key] = value
64
+ }
65
+ }
66
+
67
+ if (tanstack.interruptErrors !== undefined) {
68
+ next['tanstack:interruptErrors'] = tanstack.interruptErrors
69
+ }
70
+
71
+ return next
72
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { EventType } from '../types'
3
+ import { specKeysFor, isSpecTopLevelKey } from './spec-event-keys'
4
+
5
+ describe('specKeysFor', () => {
6
+ it('allows metadata on every event and usage on RUN_FINISHED / RUN_ERROR', () => {
7
+ expect(isSpecTopLevelKey(EventType.RUN_FINISHED, 'usage')).toBe(true)
8
+ expect(isSpecTopLevelKey(EventType.RUN_ERROR, 'usage')).toBe(true)
9
+ expect(isSpecTopLevelKey(EventType.RUN_STARTED, 'metadata')).toBe(true)
10
+ expect(isSpecTopLevelKey(EventType.CUSTOM, 'name')).toBe(true)
11
+ })
12
+
13
+ it('rejects TanStack extras', () => {
14
+ expect(isSpecTopLevelKey(EventType.RUN_FINISHED, 'finishReason')).toBe(
15
+ false,
16
+ )
17
+ expect(isSpecTopLevelKey(EventType.RUN_FINISHED, 'model')).toBe(false)
18
+ expect(isSpecTopLevelKey(EventType.TOOL_CALL_START, 'toolName')).toBe(false)
19
+ expect(isSpecTopLevelKey(EventType.TOOL_CALL_START, 'index')).toBe(false)
20
+ expect(isSpecTopLevelKey(EventType.TEXT_MESSAGE_CONTENT, 'content')).toBe(
21
+ false,
22
+ )
23
+ expect(isSpecTopLevelKey(EventType.TOOL_CALL_ARGS, 'args')).toBe(false)
24
+ expect(isSpecTopLevelKey(EventType.TOOL_CALL_END, 'input')).toBe(false)
25
+ expect(isSpecTopLevelKey(EventType.TOOL_CALL_END, 'result')).toBe(false)
26
+ expect(isSpecTopLevelKey(EventType.RUN_ERROR, 'error')).toBe(false)
27
+ expect(
28
+ isSpecTopLevelKey(EventType.RUN_ERROR, 'tanstack:interruptErrors'),
29
+ ).toBe(false)
30
+ expect(isSpecTopLevelKey(EventType.STATE_SNAPSHOT, 'state')).toBe(false)
31
+ expect(isSpecTopLevelKey(EventType.CUSTOM, 'threadId')).toBe(false)
32
+ expect(specKeysFor(EventType.RUN_ERROR).has('threadId')).toBe(false)
33
+ })
34
+ })
@@ -0,0 +1,74 @@
1
+ import { EventType } from '../types'
2
+
3
+ const SHARED: ReadonlySet<string> = new Set([
4
+ 'type',
5
+ 'timestamp',
6
+ 'rawEvent',
7
+ 'metadata',
8
+ ])
9
+
10
+ function keys(...fields: Array<string>): ReadonlySet<string> {
11
+ return new Set([...SHARED, ...fields])
12
+ }
13
+
14
+ const SPEC_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
15
+ [EventType.TEXT_MESSAGE_START, keys('messageId', 'role', 'name')],
16
+ [EventType.TEXT_MESSAGE_CONTENT, keys('messageId', 'delta')],
17
+ [EventType.TEXT_MESSAGE_END, keys('messageId')],
18
+ [EventType.TEXT_MESSAGE_CHUNK, keys('messageId', 'role', 'delta', 'name')],
19
+ [
20
+ EventType.TOOL_CALL_START,
21
+ keys('toolCallId', 'toolCallName', 'parentMessageId'),
22
+ ],
23
+ [EventType.TOOL_CALL_ARGS, keys('toolCallId', 'delta')],
24
+ [EventType.TOOL_CALL_END, keys('toolCallId')],
25
+ [
26
+ EventType.TOOL_CALL_CHUNK,
27
+ keys('toolCallId', 'toolCallName', 'parentMessageId', 'delta'),
28
+ ],
29
+ [
30
+ EventType.TOOL_CALL_RESULT,
31
+ keys('messageId', 'toolCallId', 'content', 'role'),
32
+ ],
33
+ [EventType.THINKING_START, keys('title')],
34
+ [EventType.THINKING_END, SHARED],
35
+ [EventType.THINKING_TEXT_MESSAGE_START, SHARED],
36
+ [EventType.THINKING_TEXT_MESSAGE_CONTENT, keys('delta')],
37
+ [EventType.THINKING_TEXT_MESSAGE_END, SHARED],
38
+ [EventType.STATE_SNAPSHOT, keys('snapshot')],
39
+ [EventType.STATE_DELTA, keys('delta')],
40
+ [EventType.MESSAGES_SNAPSHOT, keys('messages')],
41
+ [
42
+ EventType.ACTIVITY_SNAPSHOT,
43
+ keys('messageId', 'activityType', 'content', 'replace'),
44
+ ],
45
+ [EventType.ACTIVITY_DELTA, keys('messageId', 'activityType', 'patch')],
46
+ [EventType.RAW, keys('event', 'source')],
47
+ [EventType.CUSTOM, keys('name', 'value')],
48
+ [EventType.RUN_STARTED, keys('threadId', 'runId', 'parentRunId', 'input')],
49
+ [
50
+ EventType.RUN_FINISHED,
51
+ keys('threadId', 'runId', 'result', 'outcome', 'usage'),
52
+ ],
53
+ [EventType.RUN_ERROR, keys('message', 'code', 'usage')],
54
+ [EventType.STEP_STARTED, keys('stepName')],
55
+ [EventType.STEP_FINISHED, keys('stepName')],
56
+ [EventType.REASONING_START, keys('messageId')],
57
+ [EventType.REASONING_MESSAGE_START, keys('messageId', 'role')],
58
+ [EventType.REASONING_MESSAGE_CONTENT, keys('messageId', 'delta')],
59
+ [EventType.REASONING_MESSAGE_END, keys('messageId')],
60
+ [EventType.REASONING_MESSAGE_CHUNK, keys('messageId', 'delta')],
61
+ [EventType.REASONING_END, keys('messageId')],
62
+ [
63
+ EventType.REASONING_ENCRYPTED_VALUE,
64
+ keys('subtype', 'entityId', 'encryptedValue'),
65
+ ],
66
+ ])
67
+
68
+ export function specKeysFor(type: string): ReadonlySet<string> {
69
+ return SPEC_KEYS.get(type) ?? SHARED
70
+ }
71
+
72
+ export function isSpecTopLevelKey(type: string, key: string): boolean {
73
+ return specKeysFor(type).has(key)
74
+ }
@@ -1,5 +1,5 @@
1
1
  import { EventType } from '../types'
2
- import type { StreamChunk } from '../types'
2
+ import type { AdapterYieldChunk } from './adapter-yield-chunk'
3
3
 
4
4
  export function structuredOutputStartChunk(args: {
5
5
  messageId: string
@@ -7,7 +7,7 @@ export function structuredOutputStartChunk(args: {
7
7
  threadId: string
8
8
  runId: string
9
9
  timestamp?: number
10
- }): StreamChunk {
10
+ }): AdapterYieldChunk {
11
11
  return {
12
12
  type: EventType.CUSTOM,
13
13
  name: 'structured-output.start',
@@ -27,7 +27,7 @@ export function structuredOutputCompleteChunk(args: {
27
27
  object: unknown
28
28
  raw: string
29
29
  timestamp?: number
30
- }): StreamChunk {
30
+ }): AdapterYieldChunk {
31
31
  return {
32
32
  type: EventType.CUSTOM,
33
33
  name: 'structured-output.complete',