@tanstack/ai-client 0.29.0 → 0.29.2
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/chat-client.d.ts +6 -0
- package/dist/esm/chat-client.js +28 -4
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.js +15 -0
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +3 -0
- package/dist/esm/devtools-noop.js +3 -0
- package/dist/esm/devtools-noop.js.map +1 -1
- package/dist/esm/devtools.d.ts +15 -1
- package/dist/esm/devtools.js +149 -10
- package/dist/esm/devtools.js.map +1 -1
- package/package.json +3 -3
- package/src/chat-client.ts +50 -8
- package/src/connection-adapters.ts +23 -0
- package/src/devtools-noop.ts +3 -0
- package/src/devtools.ts +280 -12
package/src/devtools.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { DefaultChatClientEventEmitter } from './events'
|
|
|
8
8
|
import type { AnyClientTool, StreamChunk } from '@tanstack/ai/client'
|
|
9
9
|
import type {
|
|
10
10
|
AIDevtoolsEventVisibility,
|
|
11
|
+
CompactionMessagePreview,
|
|
11
12
|
MemoryScopeLite,
|
|
12
13
|
} from '@tanstack/ai-event-client'
|
|
13
14
|
import type {
|
|
@@ -33,6 +34,11 @@ export interface AIDevtoolsDisplayOptions {
|
|
|
33
34
|
* payload of the `memory:state` CUSTOM chunk. Kept local so `ai-client` doesn't
|
|
34
35
|
* depend on `ai-memory`; the memory middleware is the producer.
|
|
35
36
|
*/
|
|
37
|
+
interface SkillsStateEventValue {
|
|
38
|
+
catalog?: Array<{ name: string; description: string }>
|
|
39
|
+
activated?: Array<string>
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
interface MemoryStateEventValue {
|
|
37
43
|
scope: MemoryScopeLite
|
|
38
44
|
adapter: string
|
|
@@ -55,6 +61,127 @@ interface MemoryStateEventValue {
|
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
function readCompactionBoundaryValue(rawValue: unknown): {
|
|
65
|
+
before?: number
|
|
66
|
+
after?: number
|
|
67
|
+
messagesBefore?: number
|
|
68
|
+
messagesAfter?: number
|
|
69
|
+
reusedCheckpoint?: boolean
|
|
70
|
+
maxTokens?: number
|
|
71
|
+
strategyKey?: string
|
|
72
|
+
durationMs?: number
|
|
73
|
+
} {
|
|
74
|
+
if (!rawValue || typeof rawValue !== 'object') return {}
|
|
75
|
+
return {
|
|
76
|
+
...('before' in rawValue && typeof rawValue.before === 'number'
|
|
77
|
+
? { before: rawValue.before }
|
|
78
|
+
: {}),
|
|
79
|
+
...('after' in rawValue && typeof rawValue.after === 'number'
|
|
80
|
+
? { after: rawValue.after }
|
|
81
|
+
: {}),
|
|
82
|
+
...('messagesBefore' in rawValue &&
|
|
83
|
+
typeof rawValue.messagesBefore === 'number'
|
|
84
|
+
? { messagesBefore: rawValue.messagesBefore }
|
|
85
|
+
: {}),
|
|
86
|
+
...('messagesAfter' in rawValue &&
|
|
87
|
+
typeof rawValue.messagesAfter === 'number'
|
|
88
|
+
? { messagesAfter: rawValue.messagesAfter }
|
|
89
|
+
: {}),
|
|
90
|
+
...('reusedCheckpoint' in rawValue &&
|
|
91
|
+
typeof rawValue.reusedCheckpoint === 'boolean'
|
|
92
|
+
? { reusedCheckpoint: rawValue.reusedCheckpoint }
|
|
93
|
+
: {}),
|
|
94
|
+
...('maxTokens' in rawValue && typeof rawValue.maxTokens === 'number'
|
|
95
|
+
? { maxTokens: rawValue.maxTokens }
|
|
96
|
+
: {}),
|
|
97
|
+
...('strategyKey' in rawValue && typeof rawValue.strategyKey === 'string'
|
|
98
|
+
? { strategyKey: rawValue.strategyKey }
|
|
99
|
+
: {}),
|
|
100
|
+
...('durationMs' in rawValue && typeof rawValue.durationMs === 'number'
|
|
101
|
+
? { durationMs: rawValue.durationMs }
|
|
102
|
+
: {}),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readPreviewList(
|
|
107
|
+
value: unknown,
|
|
108
|
+
): Array<CompactionMessagePreview> | undefined {
|
|
109
|
+
if (!Array.isArray(value)) return undefined
|
|
110
|
+
const previews: Array<CompactionMessagePreview> = []
|
|
111
|
+
for (const item of value) {
|
|
112
|
+
if (!item || typeof item !== 'object') continue
|
|
113
|
+
if (!('role' in item) || !('tokens' in item) || !('text' in item)) continue
|
|
114
|
+
if (
|
|
115
|
+
typeof item.role !== 'string' ||
|
|
116
|
+
typeof item.tokens !== 'number' ||
|
|
117
|
+
typeof item.text !== 'string'
|
|
118
|
+
) {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
previews.push({
|
|
122
|
+
role: item.role,
|
|
123
|
+
tokens: item.tokens,
|
|
124
|
+
text: item.text,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
return previews
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readCompactionStateValue(rawValue: unknown): {
|
|
131
|
+
before: number
|
|
132
|
+
after: number
|
|
133
|
+
messagesBefore: number
|
|
134
|
+
messagesAfter: number
|
|
135
|
+
reusedCheckpoint: boolean
|
|
136
|
+
maxTokens?: number
|
|
137
|
+
strategyKey?: string
|
|
138
|
+
dropped?: Array<CompactionMessagePreview>
|
|
139
|
+
result?: Array<CompactionMessagePreview>
|
|
140
|
+
} | null {
|
|
141
|
+
if (!rawValue || typeof rawValue !== 'object') return null
|
|
142
|
+
if (
|
|
143
|
+
!('before' in rawValue) ||
|
|
144
|
+
!('after' in rawValue) ||
|
|
145
|
+
!('messagesBefore' in rawValue) ||
|
|
146
|
+
!('messagesAfter' in rawValue)
|
|
147
|
+
) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
typeof rawValue.before !== 'number' ||
|
|
152
|
+
typeof rawValue.after !== 'number' ||
|
|
153
|
+
typeof rawValue.messagesBefore !== 'number' ||
|
|
154
|
+
typeof rawValue.messagesAfter !== 'number'
|
|
155
|
+
) {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
const reusedCheckpoint =
|
|
159
|
+
'reusedCheckpoint' in rawValue && rawValue.reusedCheckpoint === true
|
|
160
|
+
const maxTokens =
|
|
161
|
+
'maxTokens' in rawValue && typeof rawValue.maxTokens === 'number'
|
|
162
|
+
? rawValue.maxTokens
|
|
163
|
+
: undefined
|
|
164
|
+
const strategyKey =
|
|
165
|
+
'strategyKey' in rawValue && typeof rawValue.strategyKey === 'string'
|
|
166
|
+
? rawValue.strategyKey
|
|
167
|
+
: undefined
|
|
168
|
+
const dropped =
|
|
169
|
+
'dropped' in rawValue ? readPreviewList(rawValue.dropped) : undefined
|
|
170
|
+
const result =
|
|
171
|
+
'result' in rawValue ? readPreviewList(rawValue.result) : undefined
|
|
172
|
+
return {
|
|
173
|
+
before: rawValue.before,
|
|
174
|
+
after: rawValue.after,
|
|
175
|
+
messagesBefore: rawValue.messagesBefore,
|
|
176
|
+
messagesAfter: rawValue.messagesAfter,
|
|
177
|
+
reusedCheckpoint,
|
|
178
|
+
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
179
|
+
...(strategyKey ? { strategyKey } : {}),
|
|
180
|
+
...(dropped ? { dropped } : {}),
|
|
181
|
+
...(result ? { result } : {}),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
58
185
|
export interface AIDevtoolsClientMetadata extends AIDevtoolsDisplayOptions {
|
|
59
186
|
framework?: string
|
|
60
187
|
hookName: string
|
|
@@ -448,6 +575,49 @@ function getActiveBridgeRegistry(): Map<string, ActiveDevtoolsBridge> {
|
|
|
448
575
|
return registry
|
|
449
576
|
}
|
|
450
577
|
|
|
578
|
+
/**
|
|
579
|
+
* `{...options}` turns `get hookId()` into a data property. Chat/generation
|
|
580
|
+
* clients mint `threadId` after construct (`ensureThreadId` on mount), so a
|
|
581
|
+
* spread would freeze `hookId: ''` and DevTools could never select the hook.
|
|
582
|
+
*/
|
|
583
|
+
function withLiveClientIdentity<TSnapshot extends object>(
|
|
584
|
+
identity: Pick<
|
|
585
|
+
AIDevtoolsBridgeOptions<TSnapshot>,
|
|
586
|
+
| 'hookId'
|
|
587
|
+
| 'clientId'
|
|
588
|
+
| 'threadId'
|
|
589
|
+
| 'metadata'
|
|
590
|
+
| 'getTools'
|
|
591
|
+
| 'applyToolFixture'
|
|
592
|
+
>,
|
|
593
|
+
rest: Pick<AIDevtoolsBridgeOptions<TSnapshot>, 'getSnapshot'> &
|
|
594
|
+
Partial<
|
|
595
|
+
Pick<AIDevtoolsBridgeOptions<TSnapshot>, 'getTools' | 'applyToolFixture'>
|
|
596
|
+
>,
|
|
597
|
+
): AIDevtoolsBridgeOptions<TSnapshot> {
|
|
598
|
+
return {
|
|
599
|
+
get hookId() {
|
|
600
|
+
return identity.hookId
|
|
601
|
+
},
|
|
602
|
+
get clientId() {
|
|
603
|
+
return identity.clientId
|
|
604
|
+
},
|
|
605
|
+
get threadId() {
|
|
606
|
+
return identity.threadId
|
|
607
|
+
},
|
|
608
|
+
metadata: identity.metadata,
|
|
609
|
+
getSnapshot: rest.getSnapshot,
|
|
610
|
+
...(rest.getTools || identity.getTools
|
|
611
|
+
? { getTools: rest.getTools ?? identity.getTools }
|
|
612
|
+
: {}),
|
|
613
|
+
...(rest.applyToolFixture || identity.applyToolFixture
|
|
614
|
+
? {
|
|
615
|
+
applyToolFixture: rest.applyToolFixture ?? identity.applyToolFixture,
|
|
616
|
+
}
|
|
617
|
+
: {}),
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
451
621
|
export class ClientDevtoolsBridge<TSnapshot extends object> {
|
|
452
622
|
protected readonly options: AIDevtoolsBridgeOptions<TSnapshot>
|
|
453
623
|
private readonly unsubscribers: Array<Unsubscribe> = []
|
|
@@ -712,6 +882,10 @@ export class ClientDevtoolsBridge<TSnapshot extends object> {
|
|
|
712
882
|
| 'memory:retrieve:started'
|
|
713
883
|
| 'memory:retrieve:completed'
|
|
714
884
|
| 'memory:snapshot'
|
|
885
|
+
| 'compaction:started'
|
|
886
|
+
| 'compaction:state'
|
|
887
|
+
| 'compaction:ended'
|
|
888
|
+
| 'skills:snapshot'
|
|
715
889
|
| AIDevtoolsRunEventType,
|
|
716
890
|
visibility: AIDevtoolsEventVisibility = 'client-state',
|
|
717
891
|
context: { runId?: string } = {},
|
|
@@ -770,17 +944,29 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
|
|
|
770
944
|
private lastRunEventContext: ChatClientRunEventContext | undefined
|
|
771
945
|
/** Last transported `memory:state` value, replayed when a panel opens. */
|
|
772
946
|
private lastMemoryStateValue: unknown = null
|
|
947
|
+
/** Transported compaction CUSTOM events, replayed when a panel opens. */
|
|
948
|
+
private readonly lastCompactionEvents: Array<{
|
|
949
|
+
eventType: string
|
|
950
|
+
value: unknown
|
|
951
|
+
}> = []
|
|
952
|
+
/** Last transported `skills:state` value, replayed when a panel opens. */
|
|
953
|
+
private lastSkillsStateValue: unknown = null
|
|
773
954
|
|
|
774
955
|
constructor(options: ChatDevtoolsBridgeOptions) {
|
|
775
|
-
super(
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
956
|
+
super(
|
|
957
|
+
withLiveClientIdentity(options, {
|
|
958
|
+
getSnapshot: options.getSnapshot,
|
|
959
|
+
// Thunk defers `this.applyFixture` lookup until after `super` returns.
|
|
960
|
+
applyToolFixture: (fixture) => this.applyFixture(fixture),
|
|
961
|
+
}),
|
|
962
|
+
)
|
|
780
963
|
this.chatOptions = options
|
|
781
964
|
// Auto-attaches run/thread context and auto-emits a snapshot after each
|
|
782
965
|
// event so callers can keep using `this.events.X(...)` with no context arg.
|
|
783
|
-
this.events = new ChatDevtoolsAwareEventEmitter(
|
|
966
|
+
this.events = new ChatDevtoolsAwareEventEmitter(
|
|
967
|
+
() => options.clientId,
|
|
968
|
+
this,
|
|
969
|
+
)
|
|
784
970
|
}
|
|
785
971
|
|
|
786
972
|
// --- Stream / run context API -------------------------------------------
|
|
@@ -874,6 +1060,11 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
|
|
|
874
1060
|
this.emitMemoryState(rawValue)
|
|
875
1061
|
}
|
|
876
1062
|
|
|
1063
|
+
recordSkillsState(rawValue: unknown): void {
|
|
1064
|
+
this.lastSkillsStateValue = rawValue
|
|
1065
|
+
this.emitSkillsState(rawValue)
|
|
1066
|
+
}
|
|
1067
|
+
|
|
877
1068
|
/**
|
|
878
1069
|
* Re-emit the browser-side `memory:*` events from a transported
|
|
879
1070
|
* `memory:state` value. The devtools store consumes these to render the
|
|
@@ -926,10 +1117,78 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
|
|
|
926
1117
|
}
|
|
927
1118
|
}
|
|
928
1119
|
|
|
1120
|
+
/**
|
|
1121
|
+
* Record a transported compaction CUSTOM event. Called from the chat
|
|
1122
|
+
* client's `onCustomEvent` handler so server-side compaction reaches the
|
|
1123
|
+
* browser DevTools panel.
|
|
1124
|
+
*/
|
|
1125
|
+
recordCompactionEvent(eventType: string, rawValue: unknown): void {
|
|
1126
|
+
this.lastCompactionEvents.push({ eventType, value: rawValue })
|
|
1127
|
+
if (this.lastCompactionEvents.length > 60) {
|
|
1128
|
+
this.lastCompactionEvents.splice(0, this.lastCompactionEvents.length - 60)
|
|
1129
|
+
}
|
|
1130
|
+
this.emitCompactionEvent(eventType, rawValue)
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
recordCompactionState(rawValue: unknown): void {
|
|
1134
|
+
this.recordCompactionEvent('compaction:state', rawValue)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
private emitCompactionEvent(eventType: string, rawValue: unknown): void {
|
|
1138
|
+
const runContext = this.currentRunId ? { runId: this.currentRunId } : {}
|
|
1139
|
+
if (eventType === 'compaction:started') {
|
|
1140
|
+
const value = readCompactionBoundaryValue(rawValue)
|
|
1141
|
+
emitAIDevtoolsEvent('compaction:started', {
|
|
1142
|
+
...this.createEnvelope(
|
|
1143
|
+
'compaction:started',
|
|
1144
|
+
'client-state',
|
|
1145
|
+
runContext,
|
|
1146
|
+
),
|
|
1147
|
+
...value,
|
|
1148
|
+
})
|
|
1149
|
+
return
|
|
1150
|
+
}
|
|
1151
|
+
if (eventType === 'compaction:ended') {
|
|
1152
|
+
const value = readCompactionBoundaryValue(rawValue)
|
|
1153
|
+
emitAIDevtoolsEvent('compaction:ended', {
|
|
1154
|
+
...this.createEnvelope('compaction:ended', 'client-state', runContext),
|
|
1155
|
+
...value,
|
|
1156
|
+
})
|
|
1157
|
+
return
|
|
1158
|
+
}
|
|
1159
|
+
if (eventType === 'compaction:state') {
|
|
1160
|
+
const value = readCompactionStateValue(rawValue)
|
|
1161
|
+
if (!value) return
|
|
1162
|
+
emitAIDevtoolsEvent('compaction:state', {
|
|
1163
|
+
...this.createEnvelope('compaction:state', 'client-state', runContext),
|
|
1164
|
+
...value,
|
|
1165
|
+
})
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
private emitSkillsState(rawValue: unknown): void {
|
|
1170
|
+
if (!rawValue || typeof rawValue !== 'object') return
|
|
1171
|
+
const value = rawValue as SkillsStateEventValue
|
|
1172
|
+
const catalog = Array.isArray(value.catalog) ? value.catalog : []
|
|
1173
|
+
const activated = Array.isArray(value.activated) ? value.activated : []
|
|
1174
|
+
const runContext = this.currentRunId ? { runId: this.currentRunId } : {}
|
|
1175
|
+
emitAIDevtoolsEvent('skills:snapshot', {
|
|
1176
|
+
...this.createEnvelope('skills:snapshot', 'client-state', runContext),
|
|
1177
|
+
catalog,
|
|
1178
|
+
activated,
|
|
1179
|
+
})
|
|
1180
|
+
}
|
|
1181
|
+
|
|
929
1182
|
protected override onReplayState(): void {
|
|
930
1183
|
if (this.lastMemoryStateValue != null) {
|
|
931
1184
|
this.emitMemoryState(this.lastMemoryStateValue)
|
|
932
1185
|
}
|
|
1186
|
+
for (const event of this.lastCompactionEvents) {
|
|
1187
|
+
this.emitCompactionEvent(event.eventType, event.value)
|
|
1188
|
+
}
|
|
1189
|
+
if (this.lastSkillsStateValue != null) {
|
|
1190
|
+
this.emitSkillsState(this.lastSkillsStateValue)
|
|
1191
|
+
}
|
|
933
1192
|
}
|
|
934
1193
|
|
|
935
1194
|
getCurrentRunEventContext(): ChatClientRunEventContext | undefined {
|
|
@@ -1410,10 +1669,11 @@ export class GenerationDevtoolsBridge<TOutput> extends ClientDevtoolsBridge<
|
|
|
1410
1669
|
protected readonly getCoreState: () => GenerationDevtoolsCoreState<TOutput>
|
|
1411
1670
|
|
|
1412
1671
|
constructor(options: GenerationDevtoolsBridgeOptions<TOutput>) {
|
|
1413
|
-
super(
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1672
|
+
super(
|
|
1673
|
+
withLiveClientIdentity(options, {
|
|
1674
|
+
getSnapshot: () => this.buildSnapshot(),
|
|
1675
|
+
}),
|
|
1676
|
+
)
|
|
1417
1677
|
this.maxRuns = options.maxRuns ?? 20
|
|
1418
1678
|
this.getCoreState = options.getCoreState
|
|
1419
1679
|
}
|
|
@@ -1759,10 +2019,18 @@ export class VideoDevtoolsBridge<
|
|
|
1759
2019
|
// so resolveStreamId() works without the chat client telling it.
|
|
1760
2020
|
class ChatDevtoolsAwareEventEmitter extends DefaultChatClientEventEmitter {
|
|
1761
2021
|
constructor(
|
|
1762
|
-
|
|
2022
|
+
private readonly getClientId: () => string,
|
|
1763
2023
|
private readonly helper: ChatDevtoolsBridge,
|
|
1764
2024
|
) {
|
|
1765
|
-
super(
|
|
2025
|
+
super(getClientId())
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
protected override emitEvent(
|
|
2029
|
+
eventName: string,
|
|
2030
|
+
data?: Record<string, any>,
|
|
2031
|
+
): void {
|
|
2032
|
+
this.clientId = this.getClientId()
|
|
2033
|
+
super.emitEvent(eventName, data)
|
|
1766
2034
|
}
|
|
1767
2035
|
|
|
1768
2036
|
private afterEmit(streamId?: string): void {
|