@tanstack/ai-client 0.33.1 → 0.34.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.
@@ -1,7 +1,7 @@
1
1
  import { AnyClientTool, InterruptDefinition, ModelMessage, RunAgentResumeItem, StreamChunk } from '@tanstack/ai/client';
2
2
  import { ByokClient } from './byok.js';
3
3
  import { ConnectionAdapter } from './connection-adapters.js';
4
- import { BoundInterrupts, ChatClientOptions, ChatClientState, ChatFetcher, ChatInterruptState, ResolvableChatInterrupt, ChatResumeState, ConnectionStatus, MultimodalContent, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from './types.js';
4
+ import { BoundInterrupts, ChatClientOptions, ChatClientState, ChatFetcher, ChatInterruptState, ResolvableChatInterrupt, ChatResumeState, ConnectionStatus, MultimodalContent, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, SubagentHandle, UIMessage, WhenBusy } from './types.js';
5
5
  type ChatClientUpdateOptionsWithoutContext<TTools extends ReadonlyArray<AnyClientTool>, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []> = {
6
6
  connection?: ConnectionAdapter;
7
7
  fetcher?: ChatFetcher;
@@ -51,6 +51,8 @@ export interface NormalizedQueueConfig {
51
51
  export declare function normalizeQueueOption(option: QueueOption | undefined): NormalizedQueueConfig;
52
52
  export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = any, TContext = unknown, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = any> {
53
53
  private readonly processor;
54
+ private readonly subagentHandles;
55
+ private readonly stoppedSubagentIds;
54
56
  private connection;
55
57
  private uniqueId;
56
58
  private threadId;
@@ -484,6 +486,12 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
484
486
  * Get current messages
485
487
  */
486
488
  getMessages(): Array<UIMessage<TTools>>;
489
+ getSubagents(): SubagentHandle[];
490
+ private devtoolsSnapshotQueued;
491
+ /** One devtools snapshot per microtask, however many chunks arrive. */
492
+ private queueDevtoolsSnapshot;
493
+ private syncSubagentHandles;
494
+ private stopSubagent;
487
495
  /**
488
496
  * True when the last hydrate or older-page response said more messages exist.
489
497
  */
@@ -6,7 +6,7 @@ import { fetcherToConnectionAdapter, getChunkRunId as getChunkRunId$1, normalize
6
6
  import { ChatPersistor } from "./client-persistor.js";
7
7
  import { ClearedStreamTracker } from "./cleared-stream-tracker.js";
8
8
  import { InterruptManager } from "./interrupt-manager.js";
9
- import { StreamProcessor, convertSchemaToJsonSchema, generateMessageId, isStandardSchema, mergeMetadata, normalizeToUIMessage, parseWithStandardSchema, restoreInboundChunk, tanstackMetadata } from "@tanstack/ai/client";
9
+ import { EventType, StreamProcessor, convertSchemaToJsonSchema, generateMessageId, isStandardSchema, mergeMetadata, normalizeToUIMessage, parseWithStandardSchema, restoreInboundChunk, tanstackMetadata } from "@tanstack/ai/client";
10
10
  import { ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError } from "@tanstack/ai/byok";
11
11
  //#region src/chat-client.ts
12
12
  function assertUniqueInterruptDefinitions(interrupts) {
@@ -139,11 +139,35 @@ var REJOIN_REBUILD_TRIGGERS = /* @__PURE__ */ new Set([
139
139
  "TEXT_MESSAGE_START",
140
140
  "TEXT_MESSAGE_CONTENT",
141
141
  "TOOL_CALL_START",
142
- "MESSAGES_SNAPSHOT"
142
+ "MESSAGES_SNAPSHOT",
143
+ "SUBAGENT_STARTED"
143
144
  ]);
145
+ /** Every subagent card in the messages, nested cards included. */
146
+ function collectSubagentParts(messages) {
147
+ const cards = [];
148
+ for (const message of messages) for (const part of message.parts) {
149
+ if (part.type !== "subagent") continue;
150
+ cards.push(part);
151
+ cards.push(...collectSubagentParts(part.subagent.messages));
152
+ }
153
+ return cards;
154
+ }
155
+ function readSubagentRunId(chunk) {
156
+ if ("subagentRunId" in chunk && typeof chunk.subagentRunId === "string") return chunk.subagentRunId;
157
+ }
144
158
  var MAX_RUN_LINEAGE_DEPTH = 64;
159
+ function copyForDevtools(value) {
160
+ if (Array.isArray(value)) return value.map(copyForDevtools);
161
+ if (value instanceof Date) return new Date(value.getTime());
162
+ if (value === null || typeof value !== "object") return value;
163
+ const out = {};
164
+ for (const [key, item] of Object.entries(value)) if (typeof item !== "function") out[key] = copyForDevtools(item);
165
+ return out;
166
+ }
145
167
  var ChatClient = class {
146
168
  processor;
169
+ subagentHandles = /* @__PURE__ */ new Map();
170
+ stoppedSubagentIds = /* @__PURE__ */ new Set();
147
171
  connection;
148
172
  uniqueId;
149
173
  threadId;
@@ -321,8 +345,10 @@ var ChatClient = class {
321
345
  ...initialMessages ? { initialMessages } : {},
322
346
  events: {
323
347
  onMessagesChange: (messages) => {
348
+ this.syncSubagentHandles();
324
349
  this.persistor?.notifyMessagesChanged(messages);
325
350
  this.callbacksRef.current.onMessagesChange(messages);
351
+ this.queueDevtoolsSnapshot();
326
352
  },
327
353
  onStreamStart: () => {
328
354
  this.setStatus("streaming");
@@ -414,6 +440,7 @@ var ChatClient = class {
414
440
  }
415
441
  }
416
442
  });
443
+ this.syncSubagentHandles();
417
444
  this.persistor?.hydrateAsync(persistedState);
418
445
  this.rejoinRunId = rejoinRunId;
419
446
  this.cachesMessages = cachesMessages;
@@ -882,7 +909,7 @@ var ChatClient = class {
882
909
  }
883
910
  getDevtoolsSnapshot() {
884
911
  return {
885
- messages: this.processor.getMessages(),
912
+ messages: copyForDevtools(this.processor.getMessages()),
886
913
  status: this.status,
887
914
  isLoading: this.isLoading,
888
915
  isSubscribed: this.isSubscribed,
@@ -1061,7 +1088,15 @@ var ChatClient = class {
1061
1088
  }
1062
1089
  this.callbacksRef.current.onChunk(chunk);
1063
1090
  this.devtoolsBridge.observeChunk(chunk);
1091
+ const attributedId = readSubagentRunId(chunk);
1092
+ if (attributedId && chunk.type === EventType.SUBAGENT_STARTED) this.stoppedSubagentIds.delete(attributedId);
1093
+ if (attributedId && this.stoppedSubagentIds.has(attributedId)) {
1094
+ this.updateRunLifecycle(chunk);
1095
+ this.resolveJoinedRun(chunk);
1096
+ return;
1097
+ }
1064
1098
  this.processor.processChunk(chunk);
1099
+ this.syncSubagentHandles();
1065
1100
  this.updateRunLifecycle(chunk);
1066
1101
  this.observeInterruptState(chunk);
1067
1102
  if (options?.defer !== false && (typeof document === "undefined" || !document.hidden)) await new Promise((resolve) => setTimeout(resolve, 0));
@@ -1502,6 +1537,7 @@ var ChatClient = class {
1502
1537
  this.resetHistoryPaging();
1503
1538
  this.discardPendingSends();
1504
1539
  this.persistor?.remove();
1540
+ this.stoppedSubagentIds.clear();
1505
1541
  this.lastResume = null;
1506
1542
  this.interruptManager.reset();
1507
1543
  this.pendingResumeThreadId = null;
@@ -1639,6 +1675,59 @@ var ChatClient = class {
1639
1675
  getMessages() {
1640
1676
  return this.processor.getMessages();
1641
1677
  }
1678
+ getSubagents() {
1679
+ return [...this.subagentHandles.values()];
1680
+ }
1681
+ devtoolsSnapshotQueued = false;
1682
+ /** One devtools snapshot per microtask, however many chunks arrive. */
1683
+ queueDevtoolsSnapshot() {
1684
+ if (this.devtoolsSnapshotQueued) return;
1685
+ this.devtoolsSnapshotQueued = true;
1686
+ queueMicrotask(() => {
1687
+ this.devtoolsSnapshotQueued = false;
1688
+ this.devtoolsBridge.emitSnapshot();
1689
+ });
1690
+ }
1691
+ syncSubagentHandles() {
1692
+ const cards = collectSubagentParts(this.processor.getMessages());
1693
+ const present = new Set(cards.map((part) => part.subagent.id));
1694
+ for (const id of this.subagentHandles.keys()) if (!present.has(id)) this.subagentHandles.delete(id);
1695
+ for (const part of cards) {
1696
+ const id = part.subagent.id;
1697
+ const existing = this.subagentHandles.get(id);
1698
+ if (existing) {
1699
+ if (part.subagent === existing) continue;
1700
+ const { stop } = existing;
1701
+ for (const key of Object.keys(existing)) Reflect.deleteProperty(existing, key);
1702
+ Object.assign(existing, part.subagent, { stop });
1703
+ part.subagent = existing;
1704
+ continue;
1705
+ }
1706
+ const handle = {
1707
+ ...part.subagent,
1708
+ stop: () => {
1709
+ this.stopSubagent(id);
1710
+ }
1711
+ };
1712
+ this.subagentHandles.set(id, handle);
1713
+ part.subagent = handle;
1714
+ }
1715
+ }
1716
+ stopSubagent(id) {
1717
+ const handle = this.subagentHandles.get(id);
1718
+ if (!handle || handle.status !== "running") return;
1719
+ this.stoppedSubagentIds.add(id);
1720
+ this.processor.processChunk({
1721
+ type: EventType.SUBAGENT_ERROR,
1722
+ subagentRunId: id,
1723
+ message: "Stopped",
1724
+ timestamp: Date.now()
1725
+ });
1726
+ this.syncSubagentHandles();
1727
+ handle.status = "error";
1728
+ handle.error = { message: "Stopped" };
1729
+ this.abortController?.abort();
1730
+ }
1642
1731
  /**
1643
1732
  * True when the last hydrate or older-page response said more messages exist.
1644
1733
  */