@tanstack/ai-client 0.11.8 → 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.
Files changed (39) hide show
  1. package/dist/esm/chat-client.d.ts +17 -2
  2. package/dist/esm/chat-client.js +223 -37
  3. package/dist/esm/chat-client.js.map +1 -1
  4. package/dist/esm/connection-adapters.d.ts +20 -2
  5. package/dist/esm/connection-adapters.js +154 -48
  6. package/dist/esm/connection-adapters.js.map +1 -1
  7. package/dist/esm/devtools-noop.d.ts +60 -0
  8. package/dist/esm/devtools-noop.js +135 -0
  9. package/dist/esm/devtools-noop.js.map +1 -0
  10. package/dist/esm/devtools.d.ts +276 -0
  11. package/dist/esm/devtools.js +1178 -0
  12. package/dist/esm/devtools.js.map +1 -0
  13. package/dist/esm/events.d.ts +50 -7
  14. package/dist/esm/events.js +65 -20
  15. package/dist/esm/events.js.map +1 -1
  16. package/dist/esm/generation-client.d.ts +14 -0
  17. package/dist/esm/generation-client.js +135 -5
  18. package/dist/esm/generation-client.js.map +1 -1
  19. package/dist/esm/generation-types.d.ts +15 -1
  20. package/dist/esm/generation-types.js.map +1 -1
  21. package/dist/esm/index.d.ts +3 -2
  22. package/dist/esm/index.js +4 -1
  23. package/dist/esm/index.js.map +1 -1
  24. package/dist/esm/types.d.ts +78 -10
  25. package/dist/esm/types.js.map +1 -1
  26. package/dist/esm/video-generation-client.d.ts +15 -0
  27. package/dist/esm/video-generation-client.js +147 -8
  28. package/dist/esm/video-generation-client.js.map +1 -1
  29. package/package.json +8 -4
  30. package/src/chat-client.ts +285 -39
  31. package/src/connection-adapters.ts +226 -61
  32. package/src/devtools-noop.ts +205 -0
  33. package/src/devtools.ts +1866 -0
  34. package/src/events.ts +142 -11
  35. package/src/generation-client.ts +176 -9
  36. package/src/generation-types.ts +23 -4
  37. package/src/index.ts +15 -1
  38. package/src/types.ts +98 -12
  39. package/src/video-generation-client.ts +186 -11
@@ -1,6 +1,6 @@
1
1
  import { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai';
2
2
  import { ConnectionAdapter } from './connection-adapters.js';
3
- import { ChatClientOptions, ChatClientState, ConnectionStatus, MultimodalContent, UIMessage } from './types.js';
3
+ import { ChatClientOptions, ChatClientState, ChatFetcher, ConnectionStatus, MultimodalContent, UIMessage } from './types.js';
4
4
  export declare class ChatClient {
5
5
  private readonly processor;
6
6
  private connection;
@@ -15,8 +15,15 @@ export declare class ChatClient {
15
15
  private status;
16
16
  private connectionStatus;
17
17
  private abortController;
18
- private readonly events;
19
18
  private readonly clientToolsRef;
19
+ private readonly devtoolsBridge;
20
+ /**
21
+ * Alias for `this.events`. The bridge installs an
22
+ * emitter that auto-attaches run/thread context and auto-emits a
23
+ * snapshot after every event, so chat-client only ever calls
24
+ * `this.events.X(...)` exactly like it did before devtools landed.
25
+ */
26
+ private readonly events;
20
27
  private currentStreamId;
21
28
  private currentMessageId;
22
29
  private readonly postStreamActions;
@@ -30,8 +37,10 @@ export declare class ChatClient {
30
37
  private draining;
31
38
  private sessionGenerating;
32
39
  private readonly activeRunIds;
40
+ private devtoolsMounted;
33
41
  private readonly callbacksRef;
34
42
  constructor(options: ChatClientOptions);
43
+ mountDevtools(): void;
35
44
  private generateUniqueId;
36
45
  private setIsLoading;
37
46
  private setStatus;
@@ -40,6 +49,9 @@ export declare class ChatClient {
40
49
  private setSessionGenerating;
41
50
  private resetSessionGenerating;
42
51
  private setError;
52
+ private buildDevtoolsBridgeOptions;
53
+ private getDevtoolsSnapshot;
54
+ private findMessageIdForToolCall;
43
55
  private abortSubscriptionLoop;
44
56
  private resolveProcessing;
45
57
  private cancelInFlightStream;
@@ -149,6 +161,7 @@ export declare class ChatClient {
149
161
  state?: 'output-available' | 'output-error';
150
162
  errorText?: string;
151
163
  }): Promise<void>;
164
+ private addToolResultInternal;
152
165
  /**
153
166
  * Respond to a tool approval request
154
167
  */
@@ -214,6 +227,7 @@ export declare class ChatClient {
214
227
  */
215
228
  updateOptions(options: {
216
229
  connection?: ConnectionAdapter;
230
+ fetcher?: ChatFetcher;
217
231
  /** @deprecated Use `forwardedProps` instead. */
218
232
  body?: Record<string, any>;
219
233
  forwardedProps?: Record<string, any>;
@@ -229,4 +243,5 @@ export declare class ChatClient {
229
243
  toolCallId?: string;
230
244
  }) => void;
231
245
  }): void;
246
+ dispose(): void;
232
247
  }
@@ -1,6 +1,17 @@
1
1
  import { StreamProcessor, normalizeToUIMessage, generateMessageId, convertSchemaToJsonSchema } from "@tanstack/ai";
2
- import { DefaultChatClientEventEmitter } from "./events.js";
3
- import { normalizeConnectionAdapter } from "./connection-adapters.js";
2
+ import { createNoOpChatDevtoolsBridge } from "./devtools-noop.js";
3
+ import { normalizeConnectionAdapter, fetcherToConnectionAdapter } from "./connection-adapters.js";
4
+ function resolveTransport(transport) {
5
+ const { connection, fetcher } = transport;
6
+ if (connection && fetcher) {
7
+ throw new Error(
8
+ "ChatClient: pass either `connection` or `fetcher`, not both."
9
+ );
10
+ }
11
+ if (connection) return connection;
12
+ if (fetcher) return fetcherToConnectionAdapter(fetcher);
13
+ throw new Error("ChatClient: either `connection` or `fetcher` is required.");
14
+ }
4
15
  class ChatClient {
5
16
  processor;
6
17
  connection;
@@ -19,8 +30,15 @@ class ChatClient {
19
30
  status = "ready";
20
31
  connectionStatus = "disconnected";
21
32
  abortController = null;
22
- events;
23
33
  clientToolsRef;
34
+ devtoolsBridge;
35
+ /**
36
+ * Alias for `this.events`. The bridge installs an
37
+ * emitter that auto-attaches run/thread context and auto-emits a
38
+ * snapshot after every event, so chat-client only ever calls
39
+ * `this.events.X(...)` exactly like it did before devtools landed.
40
+ */
41
+ events;
24
42
  currentStreamId = null;
25
43
  currentMessageId = null;
26
44
  postStreamActions = [];
@@ -38,20 +56,22 @@ class ChatClient {
38
56
  draining = false;
39
57
  sessionGenerating = false;
40
58
  activeRunIds = /* @__PURE__ */ new Set();
59
+ devtoolsMounted = false;
41
60
  callbacksRef;
42
61
  constructor(options) {
43
62
  this.uniqueId = options.id || this.generateUniqueId("chat");
44
63
  this.threadId = options.threadId || this.generateUniqueId("thread");
45
64
  this.bodyOption = options.body || {};
46
65
  this.forwardedPropsOption = options.forwardedProps || {};
47
- this.connection = normalizeConnectionAdapter(options.connection);
48
- this.events = new DefaultChatClientEventEmitter(this.uniqueId);
66
+ this.connection = normalizeConnectionAdapter(resolveTransport(options));
49
67
  this.clientToolsRef = { current: /* @__PURE__ */ new Map() };
50
68
  if (options.tools) {
51
69
  for (const tool of options.tools) {
52
70
  this.clientToolsRef.current.set(tool.name, tool);
53
71
  }
54
72
  }
73
+ this.devtoolsBridge = (options.devtoolsBridgeFactory ?? createNoOpChatDevtoolsBridge)(this.buildDevtoolsBridgeOptions(options.devtools));
74
+ this.events = this.devtoolsBridge.events;
55
75
  this.callbacksRef = {
56
76
  current: {
57
77
  onResponse: options.onResponse || (() => {
@@ -123,10 +143,30 @@ class ChatClient {
123
143
  this.events.thinkingUpdated(
124
144
  this.currentStreamId,
125
145
  messageId,
126
- content
146
+ content,
147
+ void 0
127
148
  );
128
149
  }
129
150
  },
151
+ onStructuredOutputChange: (args) => {
152
+ const streamId = this.devtoolsBridge.resolveStreamId();
153
+ const eventName = args.phase === "start" ? "structured-output:started" : args.phase === "complete" ? "structured-output:completed" : args.phase === "error" ? "structured-output:errored" : "structured-output:updated";
154
+ this.currentMessageId = args.messageId;
155
+ this.events.structuredOutputChanged(
156
+ eventName,
157
+ streamId,
158
+ args.messageId,
159
+ {
160
+ status: args.status,
161
+ raw: args.raw,
162
+ ...args.partial !== void 0 ? { partial: args.partial } : {},
163
+ ...args.data !== void 0 ? { data: args.data } : {},
164
+ ...args.reasoning !== void 0 ? { reasoning: args.reasoning } : {},
165
+ ...args.errorMessage !== void 0 ? { errorMessage: args.errorMessage } : {},
166
+ ...args.delta !== void 0 ? { delta: args.delta } : {}
167
+ }
168
+ );
169
+ },
130
170
  onToolCallStateChange: (messageId, toolCallId, state, args) => {
131
171
  const messages = this.processor.getMessages();
132
172
  const message = messages.find((m) => m.id === messageId);
@@ -149,23 +189,30 @@ class ChatClient {
149
189
  const clientTool = this.clientToolsRef.current.get(args.toolName);
150
190
  const executeFunc = clientTool?.execute;
151
191
  if (executeFunc) {
192
+ const runEventContext = this.devtoolsBridge.getCurrentRunEventContext();
152
193
  const executionPromise = (async () => {
153
194
  try {
154
195
  const output = await executeFunc(args.input);
155
- await this.addToolResult({
156
- toolCallId: args.toolCallId,
157
- tool: args.toolName,
158
- output,
159
- state: "output-available"
160
- });
196
+ await this.addToolResultInternal(
197
+ {
198
+ toolCallId: args.toolCallId,
199
+ tool: args.toolName,
200
+ output,
201
+ state: "output-available"
202
+ },
203
+ runEventContext
204
+ );
161
205
  } catch (error) {
162
- await this.addToolResult({
163
- toolCallId: args.toolCallId,
164
- tool: args.toolName,
165
- output: null,
166
- state: "output-error",
167
- errorText: error.message
168
- });
206
+ await this.addToolResultInternal(
207
+ {
208
+ toolCallId: args.toolCallId,
209
+ tool: args.toolName,
210
+ output: null,
211
+ state: "output-error",
212
+ errorText: error.message
213
+ },
214
+ runEventContext
215
+ );
169
216
  } finally {
170
217
  this.pendingToolExecutions.delete(args.toolCallId);
171
218
  }
@@ -174,23 +221,29 @@ class ChatClient {
174
221
  }
175
222
  },
176
223
  onApprovalRequest: (args) => {
177
- if (this.currentStreamId) {
178
- this.events.approvalRequested(
179
- this.currentStreamId,
180
- this.currentMessageId || "",
181
- args.toolCallId,
182
- args.toolName,
183
- args.input,
184
- args.approvalId
185
- );
186
- }
224
+ const streamId = this.devtoolsBridge.resolveStreamId();
225
+ const messageIdForApproval = this.findMessageIdForToolCall(args.toolCallId) ?? this.currentMessageId ?? "";
226
+ this.events.approvalRequested(
227
+ streamId,
228
+ messageIdForApproval,
229
+ args.toolCallId,
230
+ args.toolName,
231
+ args.input,
232
+ args.approvalId
233
+ );
187
234
  },
188
235
  onCustomEvent: (eventType, data, context) => {
189
236
  this.callbacksRef.current.onCustomEvent(eventType, data, context);
190
237
  }
191
238
  }
192
239
  });
193
- this.events.clientCreated(this.processor.getMessages().length);
240
+ }
241
+ mountDevtools() {
242
+ if (this.devtoolsMounted) {
243
+ return;
244
+ }
245
+ this.devtoolsMounted = true;
246
+ this.devtoolsBridge.mountWithTools(this.processor.getMessages().length);
194
247
  }
195
248
  generateUniqueId(prefix) {
196
249
  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
@@ -203,19 +256,23 @@ class ChatClient {
203
256
  setStatus(status) {
204
257
  this.status = status;
205
258
  this.callbacksRef.current.onStatusChange(status);
259
+ this.devtoolsBridge.emitSnapshot();
206
260
  }
207
261
  setIsSubscribed(isSubscribed) {
208
262
  this.isSubscribed = isSubscribed;
209
263
  this.callbacksRef.current.onSubscriptionChange(isSubscribed);
264
+ this.devtoolsBridge.emitSnapshot();
210
265
  }
211
266
  setConnectionStatus(status) {
212
267
  this.connectionStatus = status;
213
268
  this.callbacksRef.current.onConnectionStatusChange(status);
269
+ this.devtoolsBridge.emitSnapshot();
214
270
  }
215
271
  setSessionGenerating(isGenerating) {
216
272
  if (this.sessionGenerating === isGenerating) return;
217
273
  this.sessionGenerating = isGenerating;
218
274
  this.callbacksRef.current.onSessionGeneratingChange(isGenerating);
275
+ this.devtoolsBridge.emitSnapshot();
219
276
  }
220
277
  resetSessionGenerating() {
221
278
  this.activeRunIds.clear();
@@ -226,6 +283,51 @@ class ChatClient {
226
283
  this.callbacksRef.current.onErrorChange(error);
227
284
  this.events.errorChanged(error?.message || null);
228
285
  }
286
+ buildDevtoolsBridgeOptions(devtools) {
287
+ return {
288
+ hookId: this.uniqueId,
289
+ clientId: this.uniqueId,
290
+ threadId: this.threadId,
291
+ metadata: {
292
+ hookName: devtools?.hookName ?? "useChat",
293
+ outputKind: devtools?.outputKind ?? "chat",
294
+ ...devtools?.framework ? { framework: devtools.framework } : {},
295
+ ...devtools?.name ? { name: devtools.name } : {}
296
+ },
297
+ getSnapshot: () => this.getDevtoolsSnapshot(),
298
+ getTools: () => this.clientToolsRef.current.values(),
299
+ getMessages: () => this.processor.getMessages(),
300
+ setMessages: (messages) => {
301
+ this.processor.setMessages(messages);
302
+ },
303
+ addToolResult: (toolCallId, output, errorText) => {
304
+ this.processor.addToolResult(toolCallId, output, errorText);
305
+ },
306
+ generateId: (prefix) => this.generateUniqueId(prefix)
307
+ };
308
+ }
309
+ getDevtoolsSnapshot() {
310
+ return {
311
+ messages: this.processor.getMessages(),
312
+ status: this.status,
313
+ isLoading: this.isLoading,
314
+ isSubscribed: this.isSubscribed,
315
+ connectionStatus: this.connectionStatus,
316
+ sessionGenerating: this.sessionGenerating,
317
+ activeRunIds: Array.from(this.activeRunIds),
318
+ ...this.error ? { error: this.error.message } : {}
319
+ };
320
+ }
321
+ findMessageIdForToolCall(toolCallId) {
322
+ const messages = this.processor.getMessages();
323
+ for (const message of messages) {
324
+ const match = message.parts.find(
325
+ (part) => part.type === "tool-call" && part.id === toolCallId
326
+ );
327
+ if (match) return message.id;
328
+ }
329
+ return void 0;
330
+ }
229
331
  abortSubscriptionLoop() {
230
332
  this.subscriptionAbortController?.abort();
231
333
  this.subscriptionAbortController = null;
@@ -295,13 +397,14 @@ class ChatClient {
295
397
  this.setConnectionStatus("connected");
296
398
  }
297
399
  this.callbacksRef.current.onChunk(chunk);
298
- this.processor.processChunk(chunk);
299
400
  if (chunk.type === "RUN_STARTED") {
300
401
  this.activeRunIds.add(chunk.runId);
301
402
  this.setSessionGenerating(true);
302
403
  }
404
+ this.devtoolsBridge.observeChunk(chunk);
405
+ this.processor.processChunk(chunk);
303
406
  if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
304
- const runId = chunk.type === "RUN_FINISHED" ? chunk.runId : chunk.runId;
407
+ const runId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
305
408
  if (runId) {
306
409
  this.activeRunIds.delete(runId);
307
410
  } else if (chunk.type === "RUN_ERROR") {
@@ -375,6 +478,7 @@ class ChatClient {
375
478
  * ```
376
479
  */
377
480
  async sendMessage(content, body) {
481
+ this.mountDevtools();
378
482
  const emptyMessage = typeof content === "string" && !content.trim();
379
483
  if (emptyMessage || this.isLoading) {
380
484
  return;
@@ -402,6 +506,7 @@ class ChatClient {
402
506
  * Append a message and stream the response
403
507
  */
404
508
  async append(message) {
509
+ this.mountDevtools();
405
510
  const normalizedMessage = normalizeToUIMessage(message, generateMessageId);
406
511
  if (normalizedMessage.role === "system") {
407
512
  return;
@@ -410,6 +515,7 @@ class ChatClient {
410
515
  this.events.messageAppended(uiMessage);
411
516
  const messages = this.processor.getMessages();
412
517
  this.processor.setMessages([...messages, uiMessage]);
518
+ this.devtoolsBridge.emitSnapshot();
413
519
  if (this.isLoading) {
414
520
  this.queuePostStreamAction(async () => {
415
521
  await this.streamResponse();
@@ -435,6 +541,8 @@ class ChatClient {
435
541
  const signal = this.abortController.signal;
436
542
  this.pendingToolExecutions.clear();
437
543
  let streamCompletedSuccessfully = false;
544
+ let activeDevtoolsRunId = null;
545
+ let runTerminalEventEmitted = false;
438
546
  try {
439
547
  const messages = this.processor.getMessages();
440
548
  await this.callbacksRef.current.onResponse();
@@ -448,6 +556,7 @@ class ChatClient {
448
556
  };
449
557
  this.pendingMessageBody = void 0;
450
558
  this.currentStreamId = this.generateUniqueId("stream");
559
+ this.devtoolsBridge.setCurrentStreamId(this.currentStreamId);
451
560
  this.currentMessageId = null;
452
561
  this.processor.prepareAssistantMessage();
453
562
  this.ensureSubscription();
@@ -464,12 +573,34 @@ class ChatClient {
464
573
  ),
465
574
  forwardedProps: { ...mergedBody }
466
575
  };
576
+ this.devtoolsBridge.beginRun(runContext.runId, this.threadId);
577
+ activeDevtoolsRunId = runContext.runId;
578
+ this.devtoolsBridge.emitRunLifecycle(
579
+ "run:created",
580
+ runContext.runId,
581
+ "created"
582
+ );
583
+ this.devtoolsBridge.emitRunLifecycle(
584
+ "run:started",
585
+ runContext.runId,
586
+ "started"
587
+ );
588
+ this.devtoolsBridge.emitSnapshot();
467
589
  await this.connection.send(messages, mergedBody, signal, runContext);
468
590
  await processingComplete;
469
591
  if (generation !== this.streamGeneration) {
470
592
  return false;
471
593
  }
472
594
  if (this.status === "error") {
595
+ if (activeDevtoolsRunId) {
596
+ this.devtoolsBridge.emitRunLifecycle(
597
+ "run:errored",
598
+ activeDevtoolsRunId,
599
+ "errored",
600
+ this.error ? { error: this.error.message } : {}
601
+ );
602
+ runTerminalEventEmitted = true;
603
+ }
473
604
  return false;
474
605
  }
475
606
  if (this.pendingToolExecutions.size > 0) {
@@ -480,19 +611,52 @@ class ChatClient {
480
611
  } catch (err) {
481
612
  if (err instanceof Error) {
482
613
  if (err.name === "AbortError") {
614
+ if (activeDevtoolsRunId) {
615
+ this.devtoolsBridge.emitRunLifecycle(
616
+ "run:cancelled",
617
+ activeDevtoolsRunId,
618
+ "cancelled"
619
+ );
620
+ runTerminalEventEmitted = true;
621
+ }
483
622
  return false;
484
623
  }
485
624
  if (generation === this.streamGeneration) {
486
625
  this.reportStreamError(err);
626
+ if (activeDevtoolsRunId) {
627
+ this.devtoolsBridge.emitRunLifecycle(
628
+ "run:errored",
629
+ activeDevtoolsRunId,
630
+ "errored",
631
+ { error: err.message }
632
+ );
633
+ runTerminalEventEmitted = true;
634
+ }
487
635
  }
488
636
  }
489
637
  } finally {
490
638
  if (generation === this.streamGeneration) {
491
639
  this.currentStreamId = null;
640
+ this.devtoolsBridge.setCurrentStreamId(null);
492
641
  this.currentMessageId = null;
493
642
  this.abortController = null;
494
643
  this.setIsLoading(false);
495
644
  this.pendingMessageBody = void 0;
645
+ if (activeDevtoolsRunId && !runTerminalEventEmitted) {
646
+ if (streamCompletedSuccessfully) {
647
+ this.devtoolsBridge.emitRunLifecycle(
648
+ "run:completed",
649
+ activeDevtoolsRunId,
650
+ "completed"
651
+ );
652
+ } else if (signal.aborted) {
653
+ this.devtoolsBridge.emitRunLifecycle(
654
+ "run:cancelled",
655
+ activeDevtoolsRunId,
656
+ "cancelled"
657
+ );
658
+ }
659
+ }
496
660
  await this.drainPostStreamActions();
497
661
  if (streamCompletedSuccessfully) {
498
662
  const messages = this.processor.getMessages();
@@ -554,6 +718,7 @@ class ChatClient {
554
718
  }
555
719
  this.events.reloaded(lastUserMessageIndex);
556
720
  this.processor.removeMessagesAfter(lastUserMessageIndex);
721
+ this.devtoolsBridge.emitSnapshot();
557
722
  await this.streamResponse();
558
723
  }
559
724
  /**
@@ -575,11 +740,15 @@ class ChatClient {
575
740
  * Add the result of a client-side tool execution
576
741
  */
577
742
  async addToolResult(result) {
743
+ await this.addToolResultInternal(result);
744
+ }
745
+ async addToolResultInternal(result, context) {
578
746
  this.events.toolResultAdded(
579
747
  result.toolCallId,
580
748
  result.tool,
581
749
  result.output,
582
- result.state || "output-available"
750
+ result.state || "output-available",
751
+ context
583
752
  );
584
753
  this.processor.addToolResult(
585
754
  result.toolCallId,
@@ -615,6 +784,7 @@ class ChatClient {
615
784
  );
616
785
  }
617
786
  this.processor.addToolApprovalResponse(response.id, response.approved);
787
+ this.devtoolsBridge.emitSnapshot();
618
788
  if (this.isLoading) {
619
789
  this.queuePostStreamAction(() => this.checkForContinuation());
620
790
  return;
@@ -672,9 +842,13 @@ class ChatClient {
672
842
  */
673
843
  shouldAutoSend() {
674
844
  const messages = this.processor.getMessages();
675
- const lastAssistant = messages.findLast((m) => m.role === "assistant");
845
+ const lastAssistant = messages.findLast(
846
+ (m) => m.role === "assistant"
847
+ );
676
848
  if (!lastAssistant) return false;
677
- const hasToolCalls = lastAssistant.parts.some((p) => p.type === "tool-call");
849
+ const hasToolCalls = lastAssistant.parts.some(
850
+ (p) => p.type === "tool-call"
851
+ );
678
852
  if (!hasToolCalls) return false;
679
853
  return this.processor.areAllToolsComplete();
680
854
  }
@@ -728,12 +902,13 @@ class ChatClient {
728
902
  */
729
903
  setMessagesManually(messages) {
730
904
  this.processor.setMessages(messages);
905
+ this.devtoolsBridge.emitSnapshot();
731
906
  }
732
907
  /**
733
908
  * Update options refs (for use in React hooks to avoid recreating client)
734
909
  */
735
910
  updateOptions(options) {
736
- if (options.connection !== void 0) {
911
+ if (options.connection !== void 0 || options.fetcher !== void 0) {
737
912
  const wasSubscribed = this.isSubscribed;
738
913
  if (this.isLoading) {
739
914
  this.cancelInFlightStream({
@@ -746,7 +921,12 @@ class ChatClient {
746
921
  this.resetSessionGenerating();
747
922
  this.setIsSubscribed(false);
748
923
  this.setConnectionStatus("disconnected");
749
- this.connection = normalizeConnectionAdapter(options.connection);
924
+ this.connection = normalizeConnectionAdapter(
925
+ resolveTransport({
926
+ connection: options.connection,
927
+ fetcher: options.fetcher
928
+ })
929
+ );
750
930
  if (wasSubscribed) {
751
931
  this.subscribe();
752
932
  }
@@ -762,6 +942,7 @@ class ChatClient {
762
942
  for (const tool of options.tools) {
763
943
  this.clientToolsRef.current.set(tool.name, tool);
764
944
  }
945
+ this.devtoolsBridge.notifyToolsChanged();
765
946
  }
766
947
  if (options.onResponse !== void 0) {
767
948
  this.callbacksRef.current.onResponse = options.onResponse;
@@ -788,6 +969,11 @@ class ChatClient {
788
969
  this.callbacksRef.current.onCustomEvent = options.onCustomEvent;
789
970
  }
790
971
  }
972
+ dispose() {
973
+ this.unsubscribe();
974
+ this.devtoolsBridge.dispose();
975
+ this.devtoolsMounted = false;
976
+ }
791
977
  }
792
978
  export {
793
979
  ChatClient