@tanstack/ai-client 0.12.0 → 0.13.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/chat-client.d.ts +15 -1
- package/dist/esm/chat-client.js +196 -30
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +60 -0
- package/dist/esm/devtools-noop.js +135 -0
- package/dist/esm/devtools-noop.js.map +1 -0
- package/dist/esm/devtools.d.ts +276 -0
- package/dist/esm/devtools.js +1178 -0
- package/dist/esm/devtools.js.map +1 -0
- package/dist/esm/events.d.ts +50 -7
- package/dist/esm/events.js +65 -20
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +14 -0
- package/dist/esm/generation-client.js +135 -5
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +15 -1
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.d.ts +21 -3
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +15 -0
- package/dist/esm/video-generation-client.js +147 -8
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +7 -3
- package/src/chat-client.ts +248 -31
- package/src/devtools-noop.ts +205 -0
- package/src/devtools.ts +1866 -0
- package/src/events.ts +142 -11
- package/src/generation-client.ts +176 -9
- package/src/generation-types.ts +23 -4
- package/src/index.ts +9 -0
- package/src/types.ts +29 -2
- package/src/video-generation-client.ts +186 -11
package/src/chat-client.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
generateMessageId,
|
|
5
5
|
normalizeToUIMessage,
|
|
6
6
|
} from '@tanstack/ai'
|
|
7
|
-
import {
|
|
7
|
+
import { createNoOpChatDevtoolsBridge } from './devtools-noop'
|
|
8
8
|
import {
|
|
9
9
|
fetcherToConnectionAdapter,
|
|
10
10
|
normalizeConnectionAdapter,
|
|
@@ -19,7 +19,15 @@ import type {
|
|
|
19
19
|
ConnectionAdapter,
|
|
20
20
|
SubscribeConnectionAdapter,
|
|
21
21
|
} from './connection-adapters'
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
ChatClientEventEmitter,
|
|
24
|
+
ChatClientRunEventContext,
|
|
25
|
+
} from './events'
|
|
26
|
+
import type {
|
|
27
|
+
AIDevtoolsChatSnapshot,
|
|
28
|
+
ChatDevtoolsBridge,
|
|
29
|
+
ChatDevtoolsBridgeOptions,
|
|
30
|
+
} from './devtools'
|
|
23
31
|
import type {
|
|
24
32
|
ChatClientOptions,
|
|
25
33
|
ChatClientState,
|
|
@@ -64,8 +72,15 @@ export class ChatClient {
|
|
|
64
72
|
private status: ChatClientState = 'ready'
|
|
65
73
|
private connectionStatus: ConnectionStatus = 'disconnected'
|
|
66
74
|
private abortController: AbortController | null = null
|
|
67
|
-
private readonly events: ChatClientEventEmitter
|
|
68
75
|
private readonly clientToolsRef: { current: Map<string, AnyClientTool> }
|
|
76
|
+
private readonly devtoolsBridge: ChatDevtoolsBridge
|
|
77
|
+
/**
|
|
78
|
+
* Alias for `this.events`. The bridge installs an
|
|
79
|
+
* emitter that auto-attaches run/thread context and auto-emits a
|
|
80
|
+
* snapshot after every event, so chat-client only ever calls
|
|
81
|
+
* `this.events.X(...)` exactly like it did before devtools landed.
|
|
82
|
+
*/
|
|
83
|
+
private readonly events: ChatClientEventEmitter
|
|
69
84
|
private currentStreamId: string | null = null
|
|
70
85
|
private currentMessageId: string | null = null
|
|
71
86
|
private readonly postStreamActions: Array<() => Promise<void>> = []
|
|
@@ -83,6 +98,7 @@ export class ChatClient {
|
|
|
83
98
|
private draining = false
|
|
84
99
|
private sessionGenerating = false
|
|
85
100
|
private readonly activeRunIds = new Set<string>()
|
|
101
|
+
private devtoolsMounted = false
|
|
86
102
|
|
|
87
103
|
private readonly callbacksRef: {
|
|
88
104
|
current: {
|
|
@@ -116,7 +132,6 @@ export class ChatClient {
|
|
|
116
132
|
this.bodyOption = options.body || {}
|
|
117
133
|
this.forwardedPropsOption = options.forwardedProps || {}
|
|
118
134
|
this.connection = normalizeConnectionAdapter(resolveTransport(options))
|
|
119
|
-
this.events = new DefaultChatClientEventEmitter(this.uniqueId)
|
|
120
135
|
|
|
121
136
|
// Build client tools map
|
|
122
137
|
this.clientToolsRef = { current: new Map() }
|
|
@@ -126,6 +141,11 @@ export class ChatClient {
|
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
143
|
|
|
144
|
+
this.devtoolsBridge = (
|
|
145
|
+
options.devtoolsBridgeFactory ?? createNoOpChatDevtoolsBridge
|
|
146
|
+
)(this.buildDevtoolsBridgeOptions(options.devtools))
|
|
147
|
+
this.events = this.devtoolsBridge.events
|
|
148
|
+
|
|
129
149
|
this.callbacksRef = {
|
|
130
150
|
current: {
|
|
131
151
|
onResponse: options.onResponse || (() => {}),
|
|
@@ -200,9 +220,41 @@ export class ChatClient {
|
|
|
200
220
|
this.currentStreamId,
|
|
201
221
|
messageId,
|
|
202
222
|
content,
|
|
223
|
+
undefined,
|
|
203
224
|
)
|
|
204
225
|
}
|
|
205
226
|
},
|
|
227
|
+
onStructuredOutputChange: (args) => {
|
|
228
|
+
const streamId = this.devtoolsBridge.resolveStreamId()
|
|
229
|
+
const eventName =
|
|
230
|
+
args.phase === 'start'
|
|
231
|
+
? 'structured-output:started'
|
|
232
|
+
: args.phase === 'complete'
|
|
233
|
+
? 'structured-output:completed'
|
|
234
|
+
: args.phase === 'error'
|
|
235
|
+
? 'structured-output:errored'
|
|
236
|
+
: 'structured-output:updated'
|
|
237
|
+
|
|
238
|
+
this.currentMessageId = args.messageId
|
|
239
|
+
this.events.structuredOutputChanged(
|
|
240
|
+
eventName,
|
|
241
|
+
streamId,
|
|
242
|
+
args.messageId,
|
|
243
|
+
{
|
|
244
|
+
status: args.status,
|
|
245
|
+
raw: args.raw,
|
|
246
|
+
...(args.partial !== undefined ? { partial: args.partial } : {}),
|
|
247
|
+
...(args.data !== undefined ? { data: args.data } : {}),
|
|
248
|
+
...(args.reasoning !== undefined
|
|
249
|
+
? { reasoning: args.reasoning }
|
|
250
|
+
: {}),
|
|
251
|
+
...(args.errorMessage !== undefined
|
|
252
|
+
? { errorMessage: args.errorMessage }
|
|
253
|
+
: {}),
|
|
254
|
+
...(args.delta !== undefined ? { delta: args.delta } : {}),
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
},
|
|
206
258
|
onToolCallStateChange: (
|
|
207
259
|
messageId: string,
|
|
208
260
|
toolCallId: string,
|
|
@@ -239,24 +291,36 @@ export class ChatClient {
|
|
|
239
291
|
const clientTool = this.clientToolsRef.current.get(args.toolName)
|
|
240
292
|
const executeFunc = clientTool?.execute
|
|
241
293
|
if (executeFunc) {
|
|
294
|
+
// Capture the run context at execution-start so a tool whose
|
|
295
|
+
// result lands AFTER the originating run finishes still reports
|
|
296
|
+
// back against the originating run, not whatever run is
|
|
297
|
+
// current when the result emits.
|
|
298
|
+
const runEventContext =
|
|
299
|
+
this.devtoolsBridge.getCurrentRunEventContext()
|
|
242
300
|
// Create and track the execution promise
|
|
243
301
|
const executionPromise = (async () => {
|
|
244
302
|
try {
|
|
245
303
|
const output = await executeFunc(args.input)
|
|
246
|
-
await this.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
304
|
+
await this.addToolResultInternal(
|
|
305
|
+
{
|
|
306
|
+
toolCallId: args.toolCallId,
|
|
307
|
+
tool: args.toolName,
|
|
308
|
+
output,
|
|
309
|
+
state: 'output-available',
|
|
310
|
+
},
|
|
311
|
+
runEventContext,
|
|
312
|
+
)
|
|
252
313
|
} catch (error: any) {
|
|
253
|
-
await this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
314
|
+
await this.addToolResultInternal(
|
|
315
|
+
{
|
|
316
|
+
toolCallId: args.toolCallId,
|
|
317
|
+
tool: args.toolName,
|
|
318
|
+
output: null,
|
|
319
|
+
state: 'output-error',
|
|
320
|
+
errorText: error.message,
|
|
321
|
+
},
|
|
322
|
+
runEventContext,
|
|
323
|
+
)
|
|
260
324
|
} finally {
|
|
261
325
|
// Remove from pending when complete
|
|
262
326
|
this.pendingToolExecutions.delete(args.toolCallId)
|
|
@@ -273,16 +337,20 @@ export class ChatClient {
|
|
|
273
337
|
input: any
|
|
274
338
|
approvalId: string
|
|
275
339
|
}) => {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
340
|
+
const streamId = this.devtoolsBridge.resolveStreamId()
|
|
341
|
+
const messageIdForApproval =
|
|
342
|
+
this.findMessageIdForToolCall(args.toolCallId) ??
|
|
343
|
+
this.currentMessageId ??
|
|
344
|
+
''
|
|
345
|
+
|
|
346
|
+
this.events.approvalRequested(
|
|
347
|
+
streamId,
|
|
348
|
+
messageIdForApproval,
|
|
349
|
+
args.toolCallId,
|
|
350
|
+
args.toolName,
|
|
351
|
+
args.input,
|
|
352
|
+
args.approvalId,
|
|
353
|
+
)
|
|
286
354
|
},
|
|
287
355
|
onCustomEvent: (
|
|
288
356
|
eventType: string,
|
|
@@ -293,8 +361,15 @@ export class ChatClient {
|
|
|
293
361
|
},
|
|
294
362
|
},
|
|
295
363
|
})
|
|
364
|
+
}
|
|
296
365
|
|
|
297
|
-
|
|
366
|
+
mountDevtools(): void {
|
|
367
|
+
if (this.devtoolsMounted) {
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.devtoolsMounted = true
|
|
372
|
+
this.devtoolsBridge.mountWithTools(this.processor.getMessages().length)
|
|
298
373
|
}
|
|
299
374
|
|
|
300
375
|
private generateUniqueId(prefix: string): string {
|
|
@@ -310,22 +385,26 @@ export class ChatClient {
|
|
|
310
385
|
private setStatus(status: ChatClientState): void {
|
|
311
386
|
this.status = status
|
|
312
387
|
this.callbacksRef.current.onStatusChange(status)
|
|
388
|
+
this.devtoolsBridge.emitSnapshot()
|
|
313
389
|
}
|
|
314
390
|
|
|
315
391
|
private setIsSubscribed(isSubscribed: boolean): void {
|
|
316
392
|
this.isSubscribed = isSubscribed
|
|
317
393
|
this.callbacksRef.current.onSubscriptionChange(isSubscribed)
|
|
394
|
+
this.devtoolsBridge.emitSnapshot()
|
|
318
395
|
}
|
|
319
396
|
|
|
320
397
|
private setConnectionStatus(status: ConnectionStatus): void {
|
|
321
398
|
this.connectionStatus = status
|
|
322
399
|
this.callbacksRef.current.onConnectionStatusChange(status)
|
|
400
|
+
this.devtoolsBridge.emitSnapshot()
|
|
323
401
|
}
|
|
324
402
|
|
|
325
403
|
private setSessionGenerating(isGenerating: boolean): void {
|
|
326
404
|
if (this.sessionGenerating === isGenerating) return
|
|
327
405
|
this.sessionGenerating = isGenerating
|
|
328
406
|
this.callbacksRef.current.onSessionGeneratingChange(isGenerating)
|
|
407
|
+
this.devtoolsBridge.emitSnapshot()
|
|
329
408
|
}
|
|
330
409
|
|
|
331
410
|
private resetSessionGenerating(): void {
|
|
@@ -339,6 +418,57 @@ export class ChatClient {
|
|
|
339
418
|
this.events.errorChanged(error?.message || null)
|
|
340
419
|
}
|
|
341
420
|
|
|
421
|
+
private buildDevtoolsBridgeOptions(
|
|
422
|
+
devtools: ChatClientOptions['devtools'],
|
|
423
|
+
): ChatDevtoolsBridgeOptions {
|
|
424
|
+
return {
|
|
425
|
+
hookId: this.uniqueId,
|
|
426
|
+
clientId: this.uniqueId,
|
|
427
|
+
threadId: this.threadId,
|
|
428
|
+
metadata: {
|
|
429
|
+
hookName: devtools?.hookName ?? 'useChat',
|
|
430
|
+
outputKind: devtools?.outputKind ?? 'chat',
|
|
431
|
+
...(devtools?.framework ? { framework: devtools.framework } : {}),
|
|
432
|
+
...(devtools?.name ? { name: devtools.name } : {}),
|
|
433
|
+
},
|
|
434
|
+
getSnapshot: () => this.getDevtoolsSnapshot(),
|
|
435
|
+
getTools: () => this.clientToolsRef.current.values(),
|
|
436
|
+
getMessages: () => this.processor.getMessages(),
|
|
437
|
+
setMessages: (messages: Array<UIMessage>) => {
|
|
438
|
+
this.processor.setMessages(messages)
|
|
439
|
+
},
|
|
440
|
+
addToolResult: (toolCallId, output, errorText) => {
|
|
441
|
+
this.processor.addToolResult(toolCallId, output, errorText)
|
|
442
|
+
},
|
|
443
|
+
generateId: (prefix) => this.generateUniqueId(prefix),
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private getDevtoolsSnapshot(): AIDevtoolsChatSnapshot {
|
|
448
|
+
return {
|
|
449
|
+
messages: this.processor.getMessages(),
|
|
450
|
+
status: this.status,
|
|
451
|
+
isLoading: this.isLoading,
|
|
452
|
+
isSubscribed: this.isSubscribed,
|
|
453
|
+
connectionStatus: this.connectionStatus,
|
|
454
|
+
sessionGenerating: this.sessionGenerating,
|
|
455
|
+
activeRunIds: Array.from(this.activeRunIds),
|
|
456
|
+
...(this.error ? { error: this.error.message } : {}),
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private findMessageIdForToolCall(toolCallId: string): string | undefined {
|
|
461
|
+
const messages = this.processor.getMessages()
|
|
462
|
+
for (const message of messages) {
|
|
463
|
+
const match = message.parts.find(
|
|
464
|
+
(part: MessagePart): part is ToolCallPart =>
|
|
465
|
+
part.type === 'tool-call' && part.id === toolCallId,
|
|
466
|
+
)
|
|
467
|
+
if (match) return message.id
|
|
468
|
+
}
|
|
469
|
+
return undefined
|
|
470
|
+
}
|
|
471
|
+
|
|
342
472
|
private abortSubscriptionLoop(): void {
|
|
343
473
|
this.subscriptionAbortController?.abort()
|
|
344
474
|
this.subscriptionAbortController = null
|
|
@@ -428,11 +558,12 @@ export class ChatClient {
|
|
|
428
558
|
this.setConnectionStatus('connected')
|
|
429
559
|
}
|
|
430
560
|
this.callbacksRef.current.onChunk(chunk)
|
|
431
|
-
this.processor.processChunk(chunk)
|
|
432
561
|
if (chunk.type === 'RUN_STARTED') {
|
|
433
562
|
this.activeRunIds.add(chunk.runId)
|
|
434
563
|
this.setSessionGenerating(true)
|
|
435
564
|
}
|
|
565
|
+
this.devtoolsBridge.observeChunk(chunk)
|
|
566
|
+
this.processor.processChunk(chunk)
|
|
436
567
|
// RUN_FINISHED / RUN_ERROR signal run completion — resolve processing
|
|
437
568
|
// (redundant if onStreamEnd already resolved it, harmless)
|
|
438
569
|
if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {
|
|
@@ -529,6 +660,7 @@ export class ChatClient {
|
|
|
529
660
|
content: string | MultimodalContent,
|
|
530
661
|
body?: Record<string, any>,
|
|
531
662
|
): Promise<void> {
|
|
663
|
+
this.mountDevtools()
|
|
532
664
|
const emptyMessage = typeof content === 'string' && !content.trim()
|
|
533
665
|
if (emptyMessage || this.isLoading) {
|
|
534
666
|
return
|
|
@@ -567,6 +699,7 @@ export class ChatClient {
|
|
|
567
699
|
* Append a message and stream the response
|
|
568
700
|
*/
|
|
569
701
|
async append(message: UIMessage | ModelMessage): Promise<void> {
|
|
702
|
+
this.mountDevtools()
|
|
570
703
|
// Normalize the message to ensure it has id and createdAt
|
|
571
704
|
const normalizedMessage = normalizeToUIMessage(message, generateMessageId)
|
|
572
705
|
|
|
@@ -584,6 +717,7 @@ export class ChatClient {
|
|
|
584
717
|
// Add to messages
|
|
585
718
|
const messages = this.processor.getMessages()
|
|
586
719
|
this.processor.setMessages([...messages, uiMessage])
|
|
720
|
+
this.devtoolsBridge.emitSnapshot()
|
|
587
721
|
|
|
588
722
|
// If stream is in progress, queue the response for after it ends
|
|
589
723
|
if (this.isLoading) {
|
|
@@ -621,6 +755,8 @@ export class ChatClient {
|
|
|
621
755
|
// Reset pending tool executions for the new stream
|
|
622
756
|
this.pendingToolExecutions.clear()
|
|
623
757
|
let streamCompletedSuccessfully = false
|
|
758
|
+
let activeDevtoolsRunId: string | null = null
|
|
759
|
+
let runTerminalEventEmitted = false
|
|
624
760
|
|
|
625
761
|
try {
|
|
626
762
|
// Get UIMessages with parts (preserves approval state and client tool results)
|
|
@@ -658,6 +794,7 @@ export class ChatClient {
|
|
|
658
794
|
|
|
659
795
|
// Generate stream ID — assistant message will be created by stream events
|
|
660
796
|
this.currentStreamId = this.generateUniqueId('stream')
|
|
797
|
+
this.devtoolsBridge.setCurrentStreamId(this.currentStreamId)
|
|
661
798
|
this.currentMessageId = null
|
|
662
799
|
|
|
663
800
|
// Reset processor stream state for new response — prevents stale
|
|
@@ -693,6 +830,19 @@ export class ChatClient {
|
|
|
693
830
|
),
|
|
694
831
|
forwardedProps: { ...mergedBody },
|
|
695
832
|
}
|
|
833
|
+
this.devtoolsBridge.beginRun(runContext.runId, this.threadId)
|
|
834
|
+
activeDevtoolsRunId = runContext.runId
|
|
835
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
836
|
+
'run:created',
|
|
837
|
+
runContext.runId,
|
|
838
|
+
'created',
|
|
839
|
+
)
|
|
840
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
841
|
+
'run:started',
|
|
842
|
+
runContext.runId,
|
|
843
|
+
'started',
|
|
844
|
+
)
|
|
845
|
+
this.devtoolsBridge.emitSnapshot()
|
|
696
846
|
|
|
697
847
|
// Send through normalized connection (pushes chunks to subscription queue)
|
|
698
848
|
await this.connection.send(messages, mergedBody, signal, runContext)
|
|
@@ -709,6 +859,15 @@ export class ChatClient {
|
|
|
709
859
|
// A RUN_ERROR from the stream transitions status to error.
|
|
710
860
|
// Do not treat this stream as a successful completion.
|
|
711
861
|
if (this.status === 'error') {
|
|
862
|
+
if (activeDevtoolsRunId) {
|
|
863
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
864
|
+
'run:errored',
|
|
865
|
+
activeDevtoolsRunId,
|
|
866
|
+
'errored',
|
|
867
|
+
this.error ? { error: this.error.message } : {},
|
|
868
|
+
)
|
|
869
|
+
runTerminalEventEmitted = true
|
|
870
|
+
}
|
|
712
871
|
return false
|
|
713
872
|
}
|
|
714
873
|
|
|
@@ -723,10 +882,27 @@ export class ChatClient {
|
|
|
723
882
|
} catch (err) {
|
|
724
883
|
if (err instanceof Error) {
|
|
725
884
|
if (err.name === 'AbortError') {
|
|
885
|
+
if (activeDevtoolsRunId) {
|
|
886
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
887
|
+
'run:cancelled',
|
|
888
|
+
activeDevtoolsRunId,
|
|
889
|
+
'cancelled',
|
|
890
|
+
)
|
|
891
|
+
runTerminalEventEmitted = true
|
|
892
|
+
}
|
|
726
893
|
return false
|
|
727
894
|
}
|
|
728
895
|
if (generation === this.streamGeneration) {
|
|
729
896
|
this.reportStreamError(err)
|
|
897
|
+
if (activeDevtoolsRunId) {
|
|
898
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
899
|
+
'run:errored',
|
|
900
|
+
activeDevtoolsRunId,
|
|
901
|
+
'errored',
|
|
902
|
+
{ error: err.message },
|
|
903
|
+
)
|
|
904
|
+
runTerminalEventEmitted = true
|
|
905
|
+
}
|
|
730
906
|
}
|
|
731
907
|
}
|
|
732
908
|
} finally {
|
|
@@ -735,11 +911,28 @@ export class ChatClient {
|
|
|
735
911
|
// clobber the new stream's abortController or isLoading state.
|
|
736
912
|
if (generation === this.streamGeneration) {
|
|
737
913
|
this.currentStreamId = null
|
|
914
|
+
this.devtoolsBridge.setCurrentStreamId(null)
|
|
738
915
|
this.currentMessageId = null
|
|
739
916
|
this.abortController = null
|
|
740
917
|
this.setIsLoading(false)
|
|
741
918
|
this.pendingMessageBody = undefined // Ensure it's cleared even on error
|
|
742
919
|
|
|
920
|
+
if (activeDevtoolsRunId && !runTerminalEventEmitted) {
|
|
921
|
+
if (streamCompletedSuccessfully) {
|
|
922
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
923
|
+
'run:completed',
|
|
924
|
+
activeDevtoolsRunId,
|
|
925
|
+
'completed',
|
|
926
|
+
)
|
|
927
|
+
} else if (signal.aborted) {
|
|
928
|
+
this.devtoolsBridge.emitRunLifecycle(
|
|
929
|
+
'run:cancelled',
|
|
930
|
+
activeDevtoolsRunId,
|
|
931
|
+
'cancelled',
|
|
932
|
+
)
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
743
936
|
// Drain any actions that were queued while the stream was in progress
|
|
744
937
|
await this.drainPostStreamActions()
|
|
745
938
|
|
|
@@ -811,7 +1004,7 @@ export class ChatClient {
|
|
|
811
1004
|
|
|
812
1005
|
// Find the last user message
|
|
813
1006
|
const lastUserMessageIndex = messages.findLastIndex(
|
|
814
|
-
(m
|
|
1007
|
+
(m) => m.role === 'user',
|
|
815
1008
|
)
|
|
816
1009
|
|
|
817
1010
|
if (lastUserMessageIndex === -1) return
|
|
@@ -825,6 +1018,7 @@ export class ChatClient {
|
|
|
825
1018
|
|
|
826
1019
|
// Remove all messages after the last user message
|
|
827
1020
|
this.processor.removeMessagesAfter(lastUserMessageIndex)
|
|
1021
|
+
this.devtoolsBridge.emitSnapshot()
|
|
828
1022
|
|
|
829
1023
|
// Resend
|
|
830
1024
|
await this.streamResponse()
|
|
@@ -857,11 +1051,25 @@ export class ChatClient {
|
|
|
857
1051
|
state?: 'output-available' | 'output-error'
|
|
858
1052
|
errorText?: string
|
|
859
1053
|
}): Promise<void> {
|
|
1054
|
+
await this.addToolResultInternal(result)
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private async addToolResultInternal(
|
|
1058
|
+
result: {
|
|
1059
|
+
toolCallId: string
|
|
1060
|
+
tool: string
|
|
1061
|
+
output: any
|
|
1062
|
+
state?: 'output-available' | 'output-error'
|
|
1063
|
+
errorText?: string
|
|
1064
|
+
},
|
|
1065
|
+
context?: ChatClientRunEventContext,
|
|
1066
|
+
): Promise<void> {
|
|
860
1067
|
this.events.toolResultAdded(
|
|
861
1068
|
result.toolCallId,
|
|
862
1069
|
result.tool,
|
|
863
1070
|
result.output,
|
|
864
1071
|
result.state || 'output-available',
|
|
1072
|
+
context,
|
|
865
1073
|
)
|
|
866
1074
|
|
|
867
1075
|
// Add result via processor
|
|
@@ -912,6 +1120,7 @@ export class ChatClient {
|
|
|
912
1120
|
|
|
913
1121
|
// Add response via processor
|
|
914
1122
|
this.processor.addToolApprovalResponse(response.id, response.approved)
|
|
1123
|
+
this.devtoolsBridge.emitSnapshot()
|
|
915
1124
|
|
|
916
1125
|
// If stream is in progress, queue continuation check for after it ends
|
|
917
1126
|
if (this.isLoading) {
|
|
@@ -1051,6 +1260,7 @@ export class ChatClient {
|
|
|
1051
1260
|
*/
|
|
1052
1261
|
setMessagesManually(messages: Array<UIMessage>): void {
|
|
1053
1262
|
this.processor.setMessages(messages)
|
|
1263
|
+
this.devtoolsBridge.emitSnapshot()
|
|
1054
1264
|
}
|
|
1055
1265
|
|
|
1056
1266
|
/**
|
|
@@ -1104,7 +1314,7 @@ export class ChatClient {
|
|
|
1104
1314
|
}
|
|
1105
1315
|
// Replace each slot independently so callers can update one without
|
|
1106
1316
|
// wiping the other. (Passing `undefined` for either field is a "leave
|
|
1107
|
-
// unchanged" signal
|
|
1317
|
+
// unchanged" signal - to clear a slot, pass an empty object `{}`.)
|
|
1108
1318
|
if (options.body !== undefined) {
|
|
1109
1319
|
this.bodyOption = options.body
|
|
1110
1320
|
}
|
|
@@ -1116,6 +1326,7 @@ export class ChatClient {
|
|
|
1116
1326
|
for (const tool of options.tools) {
|
|
1117
1327
|
this.clientToolsRef.current.set(tool.name, tool)
|
|
1118
1328
|
}
|
|
1329
|
+
this.devtoolsBridge.notifyToolsChanged()
|
|
1119
1330
|
}
|
|
1120
1331
|
if (options.onResponse !== undefined) {
|
|
1121
1332
|
this.callbacksRef.current.onResponse = options.onResponse
|
|
@@ -1145,4 +1356,10 @@ export class ChatClient {
|
|
|
1145
1356
|
this.callbacksRef.current.onCustomEvent = options.onCustomEvent
|
|
1146
1357
|
}
|
|
1147
1358
|
}
|
|
1359
|
+
|
|
1360
|
+
dispose(): void {
|
|
1361
|
+
this.unsubscribe()
|
|
1362
|
+
this.devtoolsBridge.dispose()
|
|
1363
|
+
this.devtoolsMounted = false
|
|
1364
|
+
}
|
|
1148
1365
|
}
|