@assistant-ui/react-langchain 0.0.31 → 0.0.33

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 (60) hide show
  1. package/dist/attachSubagentTranscripts.d.ts +19 -12
  2. package/dist/attachSubagentTranscripts.d.ts.map +1 -1
  3. package/dist/attachSubagentTranscripts.js +8 -4
  4. package/dist/attachSubagentTranscripts.js.map +1 -1
  5. package/dist/convertMessages.d.ts +8 -11
  6. package/dist/convertMessages.d.ts.map +1 -1
  7. package/dist/convertMessages.js +53 -15
  8. package/dist/convertMessages.js.map +1 -1
  9. package/dist/converter.d.ts +137 -138
  10. package/dist/converter.d.ts.map +1 -1
  11. package/dist/converter.js +2 -1
  12. package/dist/converter.js.map +1 -1
  13. package/dist/findForkCheckpointInHistory.d.ts +17 -19
  14. package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
  15. package/dist/hooks.d.ts +19 -22
  16. package/dist/hooks.d.ts.map +1 -1
  17. package/dist/index.d.ts +7 -8
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/resolveForkCheckpoint.d.ts +5 -7
  20. package/dist/resolveForkCheckpoint.d.ts.map +1 -1
  21. package/dist/runtimeExtras.d.ts +2 -5
  22. package/dist/runtimeExtras.d.ts.map +1 -1
  23. package/dist/sdkIdentity.d.ts +2 -5
  24. package/dist/sdkIdentity.d.ts.map +1 -1
  25. package/dist/sdkIdentity.js +1 -1
  26. package/dist/streamingTiming.d.ts +4 -7
  27. package/dist/streamingTiming.d.ts.map +1 -1
  28. package/dist/subagentMessagesProjection.d.ts +17 -0
  29. package/dist/subagentMessagesProjection.d.ts.map +1 -0
  30. package/dist/subagentMessagesProjection.js +40 -0
  31. package/dist/subagentMessagesProjection.js.map +1 -0
  32. package/dist/types.d.ts +106 -109
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/uiMessages.d.ts +37 -10
  35. package/dist/uiMessages.d.ts.map +1 -1
  36. package/dist/uiMessages.js +55 -5
  37. package/dist/uiMessages.js.map +1 -1
  38. package/dist/useStreamRuntime.d.ts +9 -12
  39. package/dist/useStreamRuntime.d.ts.map +1 -1
  40. package/dist/useStreamRuntime.js +102 -28
  41. package/dist/useStreamRuntime.js.map +1 -1
  42. package/dist/useSubagentTranscripts.d.ts +5 -8
  43. package/dist/useSubagentTranscripts.d.ts.map +1 -1
  44. package/dist/useSubagentTranscripts.js +129 -34
  45. package/dist/useSubagentTranscripts.js.map +1 -1
  46. package/package.json +16 -11
  47. package/src/attachSubagentTranscripts.test.ts +13 -5
  48. package/src/attachSubagentTranscripts.ts +26 -7
  49. package/src/convertMessages.test.ts +69 -0
  50. package/src/convertMessages.ts +46 -8
  51. package/src/converter.ts +6 -0
  52. package/src/subagentMessagesProjection.test.ts +175 -0
  53. package/src/subagentMessagesProjection.ts +50 -0
  54. package/src/uiMessages.test.ts +149 -0
  55. package/src/uiMessages.ts +79 -4
  56. package/src/useStreamRuntime.test.tsx +212 -0
  57. package/src/useStreamRuntime.ts +202 -50
  58. package/src/useStreamRuntime.voice.test.tsx +712 -0
  59. package/src/useSubagentTranscripts.test.tsx +795 -52
  60. package/src/useSubagentTranscripts.ts +269 -50
@@ -1,14 +1,38 @@
1
1
  "use client";
2
2
  import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTranscripts.js";
3
+ import { groupUIMessagesByParent } from "./converter.js";
3
4
  import { convertLangChainBaseMessage } from "./convertMessages.js";
5
+ import { langChainStreamingTimingAccessors } from "./streamingTiming.js";
6
+ import { subagentMessagesProjection } from "./subagentMessagesProjection.js";
7
+ import { UI_CUSTOM_CHANNELS, createUIFoldMemo, foldUIUpdates, mergeUIMessages } from "./uiMessages.js";
8
+ import { stepStreamingTiming } from "@assistant-ui/core";
4
9
  import { convertExternalMessages, createExternalMessageConversionCache } from "@assistant-ui/core/react";
5
10
  import { useEffect, useRef, useSyncExternalStore } from "react";
6
11
  import { STREAM_CONTROLLER } from "@langchain/react";
7
- import { messagesProjection } from "@langchain/langgraph-sdk/stream";
12
+ import { channelProjection } from "@langchain/langgraph-sdk/stream";
8
13
  //#region src/useSubagentTranscripts.ts
9
14
  const MAX_SUBAGENT_DEPTH = 16;
15
+ const ROOT_UI_CHANNEL_DEPTH = 1;
10
16
  const TRANSCRIPT_METADATA = {};
17
+ const NO_UI_MESSAGES = [];
18
+ const NO_MESSAGE_TIMING = {};
11
19
  const sameNamespace = (a, b) => a.length === b.length && a.every((segment, index) => segment === b[index]);
20
+ const needsNamespaceResolution = (snapshot) => snapshot.namespace.length === 1 && snapshot.namespace[0] === `tools:${snapshot.id}`;
21
+ const requestSubagentNamespace = (source, controller, request) => {
22
+ request.attempts += 1;
23
+ request.pending = true;
24
+ request.retryQueued = false;
25
+ controller.resolveSubagentNamespace(request.id).catch(() => {}).finally(() => {
26
+ if (source.controller !== controller || source.namespaceRequests.get(request.id) !== request) return;
27
+ request.pending = false;
28
+ if (!request.retryQueued && request.attempts === 1 && request.status !== "running") request.retryQueued = true;
29
+ if (!request.retryQueued || request.attempts >= 2) {
30
+ request.retryQueued = false;
31
+ return;
32
+ }
33
+ requestSubagentNamespace(source, controller, request);
34
+ });
35
+ };
12
36
  const sameTranscriptEntries = (a, b) => a?.size === b.size && [...b].every(([id, transcript]) => a.get(id) === transcript);
13
37
  const collectUIMessages = (messages, uiMessagesByParent) => {
14
38
  const collected = [];
@@ -20,20 +44,39 @@ const collectUIMessages = (messages, uiMessagesByParent) => {
20
44
  return collected;
21
45
  };
22
46
  const sameUIMessages = (a, b) => a.length === b.length && a.every((ui, index) => ui === b[index]);
23
- const convertWithUIMessages = (uiMessagesByParent) => (message, metadata) => convertLangChainBaseMessage(message, {
47
+ const foldLocalUIMessages = (resource) => {
48
+ const events = resource.uiStore?.getSnapshot();
49
+ if (events === void 0) return NO_UI_MESSAGES;
50
+ return foldUIUpdates(events, resource.uiFoldMemo);
51
+ };
52
+ const mergeLocalUIMessages = (uiMessagesByParent, localUiMessages) => {
53
+ const merged = new Map(uiMessagesByParent);
54
+ for (const [parentId, messages] of groupUIMessagesByParent(localUiMessages)) merged.set(parentId, mergeUIMessages(messages, merged.get(parentId)));
55
+ return merged;
56
+ };
57
+ const createConverter = (uiMessagesByParent, messageTiming) => (message, metadata) => convertLangChainBaseMessage(message, {
24
58
  ...metadata,
25
- uiMessagesByParent
59
+ uiMessagesByParent,
60
+ messageTiming
26
61
  });
62
+ const updateTiming = (resource) => {
63
+ if (resource.timing === void 0) return;
64
+ const startedAt = resource.snapshot.startedAt.getTime();
65
+ const completedAt = resource.snapshot.completedAt?.getTime();
66
+ if (resource.timing.startedAt === startedAt && resource.timing.completedAt === completedAt) return;
67
+ resource.timing = {
68
+ startedAt,
69
+ ...completedAt !== void 0 && { completedAt }
70
+ };
71
+ };
27
72
  const createSubagentTranscriptSource = () => {
28
- const uiMessagesByParent = /* @__PURE__ */ new Map();
29
73
  const source = {
30
74
  resources: /* @__PURE__ */ new Map(),
31
- requestedNamespaceIds: /* @__PURE__ */ new Set(),
75
+ namespaceRequests: /* @__PURE__ */ new Map(),
32
76
  snapshot: /* @__PURE__ */ new Map(),
33
77
  listeners: /* @__PURE__ */ new Set(),
34
78
  controller: void 0,
35
- uiMessagesByParent,
36
- convert: convertWithUIMessages(uiMessagesByParent),
79
+ uiMessagesByParent: /* @__PURE__ */ new Map(),
37
80
  subscribe(listener) {
38
81
  source.listeners.add(listener);
39
82
  return () => source.listeners.delete(listener);
@@ -42,20 +85,22 @@ const createSubagentTranscriptSource = () => {
42
85
  return source.snapshot;
43
86
  },
44
87
  reconcile(controller, subagents, uiMessagesByParent) {
45
- if (source.uiMessagesByParent !== uiMessagesByParent) {
46
- source.uiMessagesByParent = uiMessagesByParent;
47
- source.convert = convertWithUIMessages(uiMessagesByParent);
48
- }
88
+ source.uiMessagesByParent = uiMessagesByParent;
89
+ const rebound = /* @__PURE__ */ new Map();
49
90
  if (source.controller !== controller) {
50
91
  source.dispose();
51
92
  source.controller = controller;
52
93
  }
53
- for (const id of source.requestedNamespaceIds) if (!subagents.has(id)) source.requestedNamespaceIds.delete(id);
94
+ for (const id of source.namespaceRequests.keys()) if (!subagents.has(id)) source.namespaceRequests.delete(id);
54
95
  for (const [id, resource] of source.resources) {
55
96
  const snapshot = subagents.get(id);
56
97
  if (!snapshot || snapshot.depth > 16 || !sameNamespace(resource.namespace, snapshot.namespace)) {
57
- resource.unsubscribe();
58
- resource.release();
98
+ if (snapshot && snapshot.depth <= 16) rebound.set(id, {
99
+ timing: resource.timing,
100
+ timingState: resource.timingState,
101
+ messageTiming: resource.messageTiming
102
+ });
103
+ resource.dispose();
59
104
  source.resources.delete(id);
60
105
  continue;
61
106
  }
@@ -63,47 +108,75 @@ const createSubagentTranscriptSource = () => {
63
108
  }
64
109
  for (const snapshot of subagents.values()) {
65
110
  if (snapshot.depth > 16) continue;
66
- if (!source.requestedNamespaceIds.has(snapshot.id)) {
67
- source.requestedNamespaceIds.add(snapshot.id);
68
- controller.resolveSubagentNamespace(snapshot.id).catch(() => {
69
- if (source.controller === controller) source.requestedNamespaceIds.delete(snapshot.id);
70
- });
111
+ if (!needsNamespaceResolution(snapshot)) source.namespaceRequests.delete(snapshot.id);
112
+ else {
113
+ const request = source.namespaceRequests.get(snapshot.id);
114
+ if (!request) {
115
+ const nextRequest = {
116
+ id: snapshot.id,
117
+ attempts: 0,
118
+ pending: false,
119
+ retryQueued: false,
120
+ status: snapshot.status
121
+ };
122
+ source.namespaceRequests.set(snapshot.id, nextRequest);
123
+ requestSubagentNamespace(source, controller, nextRequest);
124
+ } else if (request.status !== snapshot.status) {
125
+ request.status = snapshot.status;
126
+ if (request.pending) request.retryQueued = request.attempts < 2;
127
+ else if (request.attempts < 2) requestSubagentNamespace(source, controller, request);
128
+ }
71
129
  }
72
130
  if (source.resources.has(snapshot.id)) continue;
73
- const acquired = controller.registry.acquire(messagesProjection(snapshot.namespace));
131
+ const acquired = controller.registry.acquire(subagentMessagesProjection(snapshot.namespace));
132
+ const acquiredUI = snapshot.namespace.length > ROOT_UI_CHANNEL_DEPTH ? controller.registry.acquire(channelProjection(UI_CUSTOM_CHANNELS, snapshot.namespace)) : void 0;
74
133
  const resource = {
75
134
  snapshot,
76
135
  namespace: snapshot.namespace,
77
136
  store: acquired.store,
78
- unsubscribe: () => {},
79
- release: acquired.release,
137
+ uiStore: acquiredUI?.store,
138
+ dispose: () => {},
80
139
  storeSnapshot: void 0,
81
140
  status: void 0,
141
+ uiFoldMemo: createUIFoldMemo(),
142
+ localUiMessages: NO_UI_MESSAGES,
143
+ rootUiMessagesByParent: source.uiMessagesByParent,
144
+ uiMessagesByParent: source.uiMessagesByParent,
145
+ timingState: rebound.get(snapshot.id)?.timingState ?? null,
146
+ messageTiming: rebound.get(snapshot.id)?.messageTiming ?? NO_MESSAGE_TIMING,
147
+ convertedMessageTiming: void 0,
148
+ convert: createConverter(source.uiMessagesByParent, NO_MESSAGE_TIMING),
82
149
  uiMessages: [],
83
150
  converted: void 0,
84
151
  childTranscripts: void 0,
85
152
  transcript: void 0,
153
+ timing: rebound.has(snapshot.id) ? rebound.get(snapshot.id).timing : snapshot.status === "running" ? { startedAt: snapshot.startedAt.getTime() } : void 0,
154
+ entry: void 0,
86
155
  memo: createAttachMemo(),
87
156
  cache: createExternalMessageConversionCache()
88
157
  };
89
- resource.unsubscribe = resource.store.subscribe(() => rebuild());
158
+ const unsubscribe = resource.store.subscribe(() => rebuild());
159
+ const unsubscribeUI = resource.uiStore?.subscribe(() => rebuild());
160
+ resource.dispose = () => {
161
+ unsubscribe();
162
+ unsubscribeUI?.();
163
+ acquired.release();
164
+ acquiredUI?.release();
165
+ };
90
166
  source.resources.set(snapshot.id, resource);
91
167
  }
92
168
  rebuild();
93
169
  },
94
170
  dispose() {
95
- for (const resource of source.resources.values()) {
96
- resource.unsubscribe();
97
- resource.release();
98
- }
171
+ for (const resource of source.resources.values()) resource.dispose();
99
172
  source.resources.clear();
100
- source.requestedNamespaceIds.clear();
173
+ source.namespaceRequests.clear();
101
174
  source.snapshot = /* @__PURE__ */ new Map();
102
175
  for (const listener of source.listeners) listener();
103
176
  }
104
177
  };
105
178
  const rebuild = () => {
106
- const { convert, uiMessagesByParent } = source;
179
+ const { uiMessagesByParent } = source;
107
180
  const resources = [...source.resources.values()];
108
181
  const childrenByParent = /* @__PURE__ */ new Map();
109
182
  for (const resource of resources) {
@@ -121,16 +194,30 @@ const createSubagentTranscriptSource = () => {
121
194
  built.add(resource.snapshot.id);
122
195
  const children = depth < 16 ? childrenByParent.get(resource.snapshot.id) ?? [] : [];
123
196
  for (const child of children) build(child, depth + 1);
124
- const childTranscripts = new Map(children.flatMap((child) => child.transcript ? [[child.snapshot.id, child.transcript]] : []));
197
+ const childTranscripts = new Map(children.flatMap((child) => child.entry ? [[child.snapshot.id, child.entry]] : []));
125
198
  const storeSnapshot = resource.store.getSnapshot();
126
199
  const status = resource.snapshot.status;
127
- const uiMessages = collectUIMessages(storeSnapshot, uiMessagesByParent);
128
- const conversionChanged = resource.converted === void 0 || resource.storeSnapshot !== storeSnapshot || resource.status !== status || !sameUIMessages(resource.uiMessages, uiMessages);
200
+ const stepped = stepStreamingTiming(resource.timingState, storeSnapshot, status === "running", langChainStreamingTimingAccessors, void 0);
201
+ resource.timingState = stepped.state;
202
+ if (Object.keys(stepped.timings).length > 0) resource.messageTiming = {
203
+ ...resource.messageTiming,
204
+ ...stepped.timings
205
+ };
206
+ const localUiMessages = foldLocalUIMessages(resource);
207
+ if (resource.localUiMessages !== localUiMessages || resource.rootUiMessagesByParent !== uiMessagesByParent || resource.convertedMessageTiming !== resource.messageTiming) {
208
+ resource.localUiMessages = localUiMessages;
209
+ resource.rootUiMessagesByParent = uiMessagesByParent;
210
+ resource.uiMessagesByParent = localUiMessages.length === 0 ? uiMessagesByParent : mergeLocalUIMessages(uiMessagesByParent, localUiMessages);
211
+ resource.convert = createConverter(resource.uiMessagesByParent, resource.messageTiming);
212
+ }
213
+ const uiMessages = collectUIMessages(storeSnapshot, resource.uiMessagesByParent);
214
+ const conversionChanged = resource.converted === void 0 || resource.storeSnapshot !== storeSnapshot || resource.status !== status || resource.convertedMessageTiming !== resource.messageTiming || !sameUIMessages(resource.uiMessages, uiMessages);
129
215
  if (conversionChanged) {
130
- resource.converted = convertExternalMessages(storeSnapshot, convert, status === "running", TRANSCRIPT_METADATA, resource.cache);
216
+ resource.converted = convertExternalMessages(storeSnapshot, resource.convert, status === "running", TRANSCRIPT_METADATA, resource.cache);
131
217
  resource.storeSnapshot = storeSnapshot;
132
218
  resource.status = status;
133
219
  resource.uiMessages = uiMessages;
220
+ resource.convertedMessageTiming = resource.messageTiming;
134
221
  }
135
222
  if (resource.transcript === void 0 || conversionChanged || !sameTranscriptEntries(resource.childTranscripts, childTranscripts)) {
136
223
  const transcript = attachSubagentTranscripts(resource.converted, childTranscripts, resource.memo);
@@ -138,8 +225,16 @@ const createSubagentTranscriptSource = () => {
138
225
  resource.transcript = transcript;
139
226
  resource.childTranscripts = childTranscripts;
140
227
  }
228
+ updateTiming(resource);
229
+ if (resource.entry === void 0 || resource.entry.messages !== resource.transcript || resource.entry.timing !== resource.timing) {
230
+ resource.entry = {
231
+ messages: resource.transcript,
232
+ ...resource.timing && { timing: resource.timing }
233
+ };
234
+ changed = true;
235
+ }
141
236
  if (!source.snapshot.has(resource.snapshot.id)) changed = true;
142
- transcripts.set(resource.snapshot.id, resource.transcript);
237
+ transcripts.set(resource.snapshot.id, resource.entry);
143
238
  };
144
239
  for (const resource of resources) if (resource.snapshot.parentId == null || !source.resources.has(resource.snapshot.parentId)) build(resource, 1);
145
240
  for (const resource of resources) build(resource, 16);
@@ -1 +1 @@
1
- {"version":3,"file":"useSubagentTranscripts.js","names":[],"sources":["../src/useSubagentTranscripts.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n convertExternalMessages,\n createExternalMessageConversionCache,\n type ExternalMessageConversionCache,\n type useExternalMessageConverter,\n} from \"@assistant-ui/core/react\";\nimport { STREAM_CONTROLLER, type AnyStream } from \"@langchain/react\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { messagesProjection } from \"@langchain/langgraph-sdk/stream\";\nimport type { SubagentDiscoverySnapshot } from \"@langchain/react\";\nimport {\n attachSubagentTranscripts,\n type AttachMemo,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { convertLangChainBaseMessage } from \"./convertMessages\";\nimport type { LangChainBaseMessage, UIMessage } from \"./types\";\n\nexport const MAX_SUBAGENT_DEPTH = 16;\n\nconst TRANSCRIPT_METADATA = {};\n\ntype ProjectionStore = {\n getSnapshot(): BaseMessage[];\n subscribe(listener: () => void): () => void;\n};\n\ntype ProjectionResource = {\n snapshot: SubagentDiscoverySnapshot;\n namespace: readonly string[];\n store: ProjectionStore;\n unsubscribe: () => void;\n release: () => void;\n storeSnapshot: BaseMessage[] | undefined;\n status: SubagentDiscoverySnapshot[\"status\"] | undefined;\n uiMessages: readonly UIMessage[];\n converted: readonly ThreadMessage[] | undefined;\n childTranscripts: ReadonlyMap<string, readonly ThreadMessage[]> | undefined;\n transcript: readonly ThreadMessage[] | undefined;\n memo: AttachMemo;\n cache: ExternalMessageConversionCache;\n};\n\ntype SubagentTranscriptSource = {\n resources: Map<string, ProjectionResource>;\n requestedNamespaceIds: Set<string>;\n snapshot: ReadonlyMap<string, readonly ThreadMessage[]>;\n listeners: Set<() => void>;\n controller: AnyStream[typeof STREAM_CONTROLLER] | undefined;\n uiMessagesByParent: Map<string, UIMessage[]>;\n convert: useExternalMessageConverter.Callback<LangChainBaseMessage>;\n subscribe(listener: () => void): () => void;\n getSnapshot(): ReadonlyMap<string, readonly ThreadMessage[]>;\n reconcile(\n controller: AnyStream[typeof STREAM_CONTROLLER],\n subagents: AnyStream[\"subagents\"],\n uiMessagesByParent: Map<string, UIMessage[]>,\n ): void;\n dispose(): void;\n};\n\nconst sameNamespace = (a: readonly string[], b: readonly string[]) =>\n a.length === b.length && a.every((segment, index) => segment === b[index]);\n\nconst sameTranscriptEntries = (\n a: ReadonlyMap<string, readonly ThreadMessage[]> | undefined,\n b: ReadonlyMap<string, readonly ThreadMessage[]>,\n) =>\n a?.size === b.size &&\n [...b].every(([id, transcript]) => a.get(id) === transcript);\n\nconst collectUIMessages = (\n messages: readonly BaseMessage[],\n uiMessagesByParent: Map<string, UIMessage[]>,\n) => {\n const collected: UIMessage[] = [];\n if (uiMessagesByParent.size === 0) return collected;\n for (const message of messages) {\n const uiMessages = message.id && uiMessagesByParent.get(message.id);\n if (uiMessages) collected.push(...uiMessages);\n }\n return collected;\n};\n\nconst sameUIMessages = (a: readonly UIMessage[], b: readonly UIMessage[]) =>\n a.length === b.length && a.every((ui, index) => ui === b[index]);\n\nconst convertWithUIMessages =\n (\n uiMessagesByParent: Map<string, UIMessage[]>,\n ): useExternalMessageConverter.Callback<LangChainBaseMessage> =>\n (message, metadata) =>\n convertLangChainBaseMessage(message, { ...metadata, uiMessagesByParent });\n\nconst createSubagentTranscriptSource = (): SubagentTranscriptSource => {\n const uiMessagesByParent = new Map<string, UIMessage[]>();\n const source: SubagentTranscriptSource = {\n resources: new Map(),\n requestedNamespaceIds: new Set(),\n snapshot: new Map(),\n listeners: new Set(),\n controller: undefined,\n uiMessagesByParent,\n convert: convertWithUIMessages(uiMessagesByParent),\n subscribe(listener) {\n source.listeners.add(listener);\n return () => source.listeners.delete(listener);\n },\n getSnapshot() {\n return source.snapshot;\n },\n reconcile(controller, subagents, uiMessagesByParent) {\n if (source.uiMessagesByParent !== uiMessagesByParent) {\n source.uiMessagesByParent = uiMessagesByParent;\n source.convert = convertWithUIMessages(uiMessagesByParent);\n }\n\n if (source.controller !== controller) {\n source.dispose();\n source.controller = controller;\n }\n\n for (const id of source.requestedNamespaceIds) {\n if (!subagents.has(id)) source.requestedNamespaceIds.delete(id);\n }\n\n for (const [id, resource] of source.resources) {\n const snapshot = subagents.get(id);\n if (\n !snapshot ||\n snapshot.depth > MAX_SUBAGENT_DEPTH ||\n !sameNamespace(resource.namespace, snapshot.namespace)\n ) {\n resource.unsubscribe();\n resource.release();\n source.resources.delete(id);\n continue;\n }\n resource.snapshot = snapshot;\n }\n\n for (const snapshot of subagents.values()) {\n if (snapshot.depth > MAX_SUBAGENT_DEPTH) continue;\n if (!source.requestedNamespaceIds.has(snapshot.id)) {\n source.requestedNamespaceIds.add(snapshot.id);\n void controller.resolveSubagentNamespace(snapshot.id).catch(() => {\n if (source.controller === controller)\n source.requestedNamespaceIds.delete(snapshot.id);\n });\n }\n if (source.resources.has(snapshot.id)) continue;\n const acquired = controller.registry.acquire(\n messagesProjection(snapshot.namespace),\n );\n const resource: ProjectionResource = {\n snapshot,\n namespace: snapshot.namespace,\n store: acquired.store,\n unsubscribe: () => {},\n release: acquired.release,\n storeSnapshot: undefined,\n status: undefined,\n uiMessages: [],\n converted: undefined,\n childTranscripts: undefined,\n transcript: undefined,\n memo: createAttachMemo(),\n cache: createExternalMessageConversionCache(),\n };\n resource.unsubscribe = resource.store.subscribe(() => rebuild());\n source.resources.set(snapshot.id, resource);\n }\n\n rebuild();\n },\n dispose() {\n for (const resource of source.resources.values()) {\n resource.unsubscribe();\n resource.release();\n }\n source.resources.clear();\n source.requestedNamespaceIds.clear();\n source.snapshot = new Map();\n for (const listener of source.listeners) listener();\n },\n };\n\n const rebuild = () => {\n const { convert, uiMessagesByParent } = source;\n const resources = [...source.resources.values()];\n const childrenByParent = new Map<string, ProjectionResource[]>();\n\n for (const resource of resources) {\n const parentId = resource.snapshot.parentId;\n if (parentId == null) continue;\n const children = childrenByParent.get(parentId);\n if (children) children.push(resource);\n else childrenByParent.set(parentId, [resource]);\n }\n\n const transcripts = new Map<string, readonly ThreadMessage[]>();\n let changed = source.snapshot.size !== resources.length;\n const built = new Set<string>();\n\n const build = (resource: ProjectionResource, depth: number) => {\n if (built.has(resource.snapshot.id)) return;\n built.add(resource.snapshot.id);\n const children =\n depth < MAX_SUBAGENT_DEPTH\n ? (childrenByParent.get(resource.snapshot.id) ?? [])\n : [];\n for (const child of children) build(child, depth + 1);\n const childTranscripts = new Map(\n children.flatMap((child) =>\n child.transcript\n ? [[child.snapshot.id, child.transcript] as const]\n : [],\n ),\n );\n const storeSnapshot = resource.store.getSnapshot();\n const status = resource.snapshot.status;\n const uiMessages = collectUIMessages(storeSnapshot, uiMessagesByParent);\n\n const conversionChanged =\n resource.converted === undefined ||\n resource.storeSnapshot !== storeSnapshot ||\n resource.status !== status ||\n !sameUIMessages(resource.uiMessages, uiMessages);\n if (conversionChanged) {\n resource.converted = convertExternalMessages(\n storeSnapshot as LangChainBaseMessage[],\n convert,\n status === \"running\",\n TRANSCRIPT_METADATA,\n resource.cache,\n );\n resource.storeSnapshot = storeSnapshot;\n resource.status = status;\n resource.uiMessages = uiMessages;\n }\n\n if (\n resource.transcript === undefined ||\n conversionChanged ||\n !sameTranscriptEntries(resource.childTranscripts, childTranscripts)\n ) {\n const transcript = attachSubagentTranscripts(\n resource.converted!,\n childTranscripts,\n resource.memo,\n );\n changed ||= resource.transcript !== transcript;\n resource.transcript = transcript;\n resource.childTranscripts = childTranscripts;\n }\n\n if (!source.snapshot.has(resource.snapshot.id)) changed = true;\n transcripts.set(resource.snapshot.id, resource.transcript);\n };\n\n for (const resource of resources) {\n if (\n resource.snapshot.parentId == null ||\n !source.resources.has(resource.snapshot.parentId)\n )\n build(resource, 1);\n }\n for (const resource of resources) build(resource, MAX_SUBAGENT_DEPTH);\n\n if (!changed) return;\n source.snapshot = transcripts;\n for (const listener of source.listeners) listener();\n };\n\n return source;\n};\n\nexport const useSubagentTranscripts = (\n stream: AnyStream,\n uiMessagesByParent: Map<string, UIMessage[]>,\n): ReadonlyMap<string, readonly ThreadMessage[]> => {\n const sourceRef = useRef<SubagentTranscriptSource | undefined>(undefined);\n if (!sourceRef.current) {\n sourceRef.current = createSubagentTranscriptSource();\n }\n const source = sourceRef.current;\n const controller = stream[STREAM_CONTROLLER];\n\n useEffect(() => {\n source.reconcile(controller, stream.subagents, uiMessagesByParent);\n }, [controller, source, stream.subagents, uiMessagesByParent]);\n\n useEffect(() => () => source.dispose(), [source]);\n\n return useSyncExternalStore(\n source.subscribe,\n source.getSnapshot,\n source.getSnapshot,\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSubagentTranscripts.js","names":[],"sources":["../src/useSubagentTranscripts.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport {\n stepStreamingTiming,\n type MessageTiming,\n type StreamingTimingState,\n type ThreadMessage,\n type ToolCallTiming,\n} from \"@assistant-ui/core\";\nimport {\n convertExternalMessages,\n createExternalMessageConversionCache,\n type ExternalMessageConversionCache,\n type useExternalMessageConverter,\n} from \"@assistant-ui/core/react\";\nimport { STREAM_CONTROLLER, type AnyStream } from \"@langchain/react\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { channelProjection, type Event } from \"@langchain/langgraph-sdk/stream\";\nimport type { SubagentDiscoverySnapshot } from \"@langchain/react\";\nimport {\n attachSubagentTranscripts,\n type AttachMemo,\n createAttachMemo,\n type SubagentTranscript,\n} from \"./attachSubagentTranscripts\";\nimport { convertLangChainBaseMessage } from \"./convertMessages\";\nimport { groupUIMessagesByParent } from \"./converter\";\nimport { langChainStreamingTimingAccessors } from \"./streamingTiming\";\nimport { subagentMessagesProjection } from \"./subagentMessagesProjection\";\nimport type { LangChainBaseMessage, UIMessage } from \"./types\";\nimport {\n createUIFoldMemo,\n foldUIUpdates,\n mergeUIMessages,\n UI_CUSTOM_CHANNELS,\n type UIFoldMemo,\n} from \"./uiMessages\";\n\nexport const MAX_SUBAGENT_DEPTH = 16;\n\nconst ROOT_UI_CHANNEL_DEPTH = 1;\n\nconst TRANSCRIPT_METADATA = {};\n\nconst NO_UI_MESSAGES: readonly UIMessage[] = [];\n\nconst NO_MESSAGE_TIMING: Record<string, MessageTiming> = {};\n\ntype ProjectionStore<T> = {\n getSnapshot(): T;\n subscribe(listener: () => void): () => void;\n};\n\ntype ProjectionResource = {\n snapshot: SubagentDiscoverySnapshot;\n namespace: readonly string[];\n store: ProjectionStore<BaseMessage[]>;\n uiStore: ProjectionStore<readonly Event[]> | undefined;\n dispose: () => void;\n storeSnapshot: BaseMessage[] | undefined;\n status: SubagentDiscoverySnapshot[\"status\"] | undefined;\n uiFoldMemo: UIFoldMemo;\n localUiMessages: readonly UIMessage[];\n rootUiMessagesByParent: Map<string, UIMessage[]>;\n uiMessagesByParent: Map<string, UIMessage[]>;\n timingState: StreamingTimingState | null;\n messageTiming: Record<string, MessageTiming>;\n convertedMessageTiming: Record<string, MessageTiming> | undefined;\n convert: useExternalMessageConverter.Callback<LangChainBaseMessage>;\n uiMessages: readonly UIMessage[];\n converted: readonly ThreadMessage[] | undefined;\n childTranscripts: ReadonlyMap<string, SubagentTranscript> | undefined;\n transcript: readonly ThreadMessage[] | undefined;\n timing: ToolCallTiming | undefined;\n entry: SubagentTranscript | undefined;\n memo: AttachMemo;\n cache: ExternalMessageConversionCache;\n};\n\ntype MeasuredTiming = {\n timing: ToolCallTiming | undefined;\n timingState: StreamingTimingState | null;\n messageTiming: Record<string, MessageTiming>;\n};\n\ntype NamespaceRequest = {\n id: string;\n attempts: number;\n pending: boolean;\n retryQueued: boolean;\n status: SubagentDiscoverySnapshot[\"status\"];\n};\n\ntype SubagentTranscriptSource = {\n resources: Map<string, ProjectionResource>;\n namespaceRequests: Map<string, NamespaceRequest>;\n snapshot: ReadonlyMap<string, SubagentTranscript>;\n listeners: Set<() => void>;\n controller: AnyStream[typeof STREAM_CONTROLLER] | undefined;\n uiMessagesByParent: Map<string, UIMessage[]>;\n subscribe(listener: () => void): () => void;\n getSnapshot(): ReadonlyMap<string, SubagentTranscript>;\n reconcile(\n controller: AnyStream[typeof STREAM_CONTROLLER],\n subagents: AnyStream[\"subagents\"],\n uiMessagesByParent: Map<string, UIMessage[]>,\n ): void;\n dispose(): void;\n};\n\nconst sameNamespace = (a: readonly string[], b: readonly string[]) =>\n a.length === b.length && a.every((segment, index) => segment === b[index]);\n\nconst needsNamespaceResolution = (snapshot: SubagentDiscoverySnapshot) =>\n snapshot.namespace.length === 1 &&\n snapshot.namespace[0] === `tools:${snapshot.id}`;\n\nconst requestSubagentNamespace = (\n source: SubagentTranscriptSource,\n controller: AnyStream[typeof STREAM_CONTROLLER],\n request: NamespaceRequest,\n) => {\n request.attempts += 1;\n request.pending = true;\n request.retryQueued = false;\n void controller\n .resolveSubagentNamespace(request.id)\n .catch(() => {})\n .finally(() => {\n if (\n source.controller !== controller ||\n source.namespaceRequests.get(request.id) !== request\n )\n return;\n\n request.pending = false;\n if (\n !request.retryQueued &&\n request.attempts === 1 &&\n request.status !== \"running\"\n ) {\n request.retryQueued = true;\n }\n if (!request.retryQueued || request.attempts >= 2) {\n request.retryQueued = false;\n return;\n }\n requestSubagentNamespace(source, controller, request);\n });\n};\n\nconst sameTranscriptEntries = (\n a: ReadonlyMap<string, SubagentTranscript> | undefined,\n b: ReadonlyMap<string, SubagentTranscript>,\n) =>\n a?.size === b.size &&\n [...b].every(([id, transcript]) => a.get(id) === transcript);\n\nconst collectUIMessages = (\n messages: readonly BaseMessage[],\n uiMessagesByParent: Map<string, UIMessage[]>,\n) => {\n const collected: UIMessage[] = [];\n if (uiMessagesByParent.size === 0) return collected;\n for (const message of messages) {\n const uiMessages = message.id && uiMessagesByParent.get(message.id);\n if (uiMessages) collected.push(...uiMessages);\n }\n return collected;\n};\n\nconst sameUIMessages = (a: readonly UIMessage[], b: readonly UIMessage[]) =>\n a.length === b.length && a.every((ui, index) => ui === b[index]);\n\nconst foldLocalUIMessages = (resource: ProjectionResource) => {\n const events = resource.uiStore?.getSnapshot();\n if (events === undefined) return NO_UI_MESSAGES;\n return foldUIUpdates(events, resource.uiFoldMemo);\n};\n\nconst mergeLocalUIMessages = (\n uiMessagesByParent: Map<string, UIMessage[]>,\n localUiMessages: readonly UIMessage[],\n) => {\n const merged = new Map(uiMessagesByParent);\n for (const [parentId, messages] of groupUIMessagesByParent<UIMessage>(\n localUiMessages,\n )) {\n merged.set(parentId, mergeUIMessages(messages, merged.get(parentId)));\n }\n return merged;\n};\n\nconst createConverter =\n (\n uiMessagesByParent: Map<string, UIMessage[]>,\n messageTiming: Record<string, MessageTiming>,\n ): useExternalMessageConverter.Callback<LangChainBaseMessage> =>\n (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n });\n\nconst updateTiming = (resource: ProjectionResource) => {\n if (resource.timing === undefined) return;\n const startedAt = resource.snapshot.startedAt.getTime();\n const completedAt = resource.snapshot.completedAt?.getTime();\n if (\n resource.timing.startedAt === startedAt &&\n resource.timing.completedAt === completedAt\n )\n return;\n resource.timing = {\n startedAt,\n ...(completedAt !== undefined && { completedAt }),\n };\n};\n\nconst createSubagentTranscriptSource = (): SubagentTranscriptSource => {\n const uiMessagesByParent = new Map<string, UIMessage[]>();\n const source: SubagentTranscriptSource = {\n resources: new Map(),\n namespaceRequests: new Map(),\n snapshot: new Map(),\n listeners: new Set(),\n controller: undefined,\n uiMessagesByParent,\n subscribe(listener) {\n source.listeners.add(listener);\n return () => source.listeners.delete(listener);\n },\n getSnapshot() {\n return source.snapshot;\n },\n reconcile(controller, subagents, uiMessagesByParent) {\n source.uiMessagesByParent = uiMessagesByParent;\n // A resource is rebuilt from scratch when its namespace resolves, which\n // routinely happens after the subagent finished. Its timing was measured\n // while the client watched the task run and cannot be measured again.\n const rebound = new Map<string, MeasuredTiming>();\n\n if (source.controller !== controller) {\n source.dispose();\n source.controller = controller;\n }\n\n for (const id of source.namespaceRequests.keys()) {\n if (!subagents.has(id)) source.namespaceRequests.delete(id);\n }\n\n for (const [id, resource] of source.resources) {\n const snapshot = subagents.get(id);\n if (\n !snapshot ||\n snapshot.depth > MAX_SUBAGENT_DEPTH ||\n !sameNamespace(resource.namespace, snapshot.namespace)\n ) {\n if (snapshot && snapshot.depth <= MAX_SUBAGENT_DEPTH)\n rebound.set(id, {\n timing: resource.timing,\n timingState: resource.timingState,\n messageTiming: resource.messageTiming,\n });\n resource.dispose();\n source.resources.delete(id);\n continue;\n }\n resource.snapshot = snapshot;\n }\n\n for (const snapshot of subagents.values()) {\n if (snapshot.depth > MAX_SUBAGENT_DEPTH) continue;\n if (!needsNamespaceResolution(snapshot)) {\n source.namespaceRequests.delete(snapshot.id);\n } else {\n const request = source.namespaceRequests.get(snapshot.id);\n if (!request) {\n const nextRequest: NamespaceRequest = {\n id: snapshot.id,\n attempts: 0,\n pending: false,\n retryQueued: false,\n status: snapshot.status,\n };\n source.namespaceRequests.set(snapshot.id, nextRequest);\n requestSubagentNamespace(source, controller, nextRequest);\n } else if (request.status !== snapshot.status) {\n request.status = snapshot.status;\n if (request.pending) {\n request.retryQueued = request.attempts < 2;\n } else if (request.attempts < 2) {\n requestSubagentNamespace(source, controller, request);\n }\n }\n }\n if (source.resources.has(snapshot.id)) continue;\n const acquired = controller.registry.acquire(\n subagentMessagesProjection(snapshot.namespace),\n );\n const acquiredUI =\n snapshot.namespace.length > ROOT_UI_CHANNEL_DEPTH\n ? controller.registry.acquire(\n channelProjection(UI_CUSTOM_CHANNELS, snapshot.namespace),\n )\n : undefined;\n const resource: ProjectionResource = {\n snapshot,\n namespace: snapshot.namespace,\n store: acquired.store,\n uiStore: acquiredUI?.store,\n dispose: () => {},\n storeSnapshot: undefined,\n status: undefined,\n uiFoldMemo: createUIFoldMemo(),\n localUiMessages: NO_UI_MESSAGES,\n rootUiMessagesByParent: source.uiMessagesByParent,\n uiMessagesByParent: source.uiMessagesByParent,\n timingState: rebound.get(snapshot.id)?.timingState ?? null,\n messageTiming:\n rebound.get(snapshot.id)?.messageTiming ?? NO_MESSAGE_TIMING,\n convertedMessageTiming: undefined,\n convert: createConverter(\n source.uiMessagesByParent,\n NO_MESSAGE_TIMING,\n ),\n uiMessages: [],\n converted: undefined,\n childTranscripts: undefined,\n transcript: undefined,\n timing: rebound.has(snapshot.id)\n ? rebound.get(snapshot.id)!.timing\n : snapshot.status === \"running\"\n ? { startedAt: snapshot.startedAt.getTime() }\n : undefined,\n entry: undefined,\n memo: createAttachMemo(),\n cache: createExternalMessageConversionCache(),\n };\n const unsubscribe = resource.store.subscribe(() => rebuild());\n const unsubscribeUI = resource.uiStore?.subscribe(() => rebuild());\n resource.dispose = () => {\n unsubscribe();\n unsubscribeUI?.();\n acquired.release();\n acquiredUI?.release();\n };\n source.resources.set(snapshot.id, resource);\n }\n\n rebuild();\n },\n dispose() {\n for (const resource of source.resources.values()) resource.dispose();\n source.resources.clear();\n source.namespaceRequests.clear();\n source.snapshot = new Map();\n for (const listener of source.listeners) listener();\n },\n };\n\n const rebuild = () => {\n const { uiMessagesByParent } = source;\n const resources = [...source.resources.values()];\n const childrenByParent = new Map<string, ProjectionResource[]>();\n\n for (const resource of resources) {\n const parentId = resource.snapshot.parentId;\n if (parentId == null) continue;\n const children = childrenByParent.get(parentId);\n if (children) children.push(resource);\n else childrenByParent.set(parentId, [resource]);\n }\n\n const transcripts = new Map<string, SubagentTranscript>();\n let changed = source.snapshot.size !== resources.length;\n const built = new Set<string>();\n\n const build = (resource: ProjectionResource, depth: number) => {\n if (built.has(resource.snapshot.id)) return;\n built.add(resource.snapshot.id);\n const children =\n depth < MAX_SUBAGENT_DEPTH\n ? (childrenByParent.get(resource.snapshot.id) ?? [])\n : [];\n for (const child of children) build(child, depth + 1);\n const childTranscripts = new Map(\n children.flatMap((child) =>\n child.entry ? [[child.snapshot.id, child.entry] as const] : [],\n ),\n );\n const storeSnapshot = resource.store.getSnapshot();\n const status = resource.snapshot.status;\n const stepped = stepStreamingTiming(\n resource.timingState,\n storeSnapshot as LangChainBaseMessage[],\n status === \"running\",\n langChainStreamingTimingAccessors,\n undefined,\n );\n resource.timingState = stepped.state;\n if (Object.keys(stepped.timings).length > 0) {\n resource.messageTiming = {\n ...resource.messageTiming,\n ...stepped.timings,\n };\n }\n const localUiMessages = foldLocalUIMessages(resource);\n if (\n resource.localUiMessages !== localUiMessages ||\n resource.rootUiMessagesByParent !== uiMessagesByParent ||\n resource.convertedMessageTiming !== resource.messageTiming\n ) {\n resource.localUiMessages = localUiMessages;\n resource.rootUiMessagesByParent = uiMessagesByParent;\n resource.uiMessagesByParent =\n localUiMessages.length === 0\n ? uiMessagesByParent\n : mergeLocalUIMessages(uiMessagesByParent, localUiMessages);\n resource.convert = createConverter(\n resource.uiMessagesByParent,\n resource.messageTiming,\n );\n }\n const uiMessages = collectUIMessages(\n storeSnapshot,\n resource.uiMessagesByParent,\n );\n\n const conversionChanged =\n resource.converted === undefined ||\n resource.storeSnapshot !== storeSnapshot ||\n resource.status !== status ||\n resource.convertedMessageTiming !== resource.messageTiming ||\n !sameUIMessages(resource.uiMessages, uiMessages);\n if (conversionChanged) {\n resource.converted = convertExternalMessages(\n storeSnapshot as LangChainBaseMessage[],\n resource.convert,\n status === \"running\",\n TRANSCRIPT_METADATA,\n resource.cache,\n );\n resource.storeSnapshot = storeSnapshot;\n resource.status = status;\n resource.uiMessages = uiMessages;\n resource.convertedMessageTiming = resource.messageTiming;\n }\n\n if (\n resource.transcript === undefined ||\n conversionChanged ||\n !sameTranscriptEntries(resource.childTranscripts, childTranscripts)\n ) {\n const transcript = attachSubagentTranscripts(\n resource.converted!,\n childTranscripts,\n resource.memo,\n );\n changed ||= resource.transcript !== transcript;\n resource.transcript = transcript;\n resource.childTranscripts = childTranscripts;\n }\n\n updateTiming(resource);\n if (\n resource.entry === undefined ||\n resource.entry.messages !== resource.transcript ||\n resource.entry.timing !== resource.timing\n ) {\n resource.entry = {\n messages: resource.transcript!,\n ...(resource.timing && { timing: resource.timing }),\n };\n changed = true;\n }\n\n if (!source.snapshot.has(resource.snapshot.id)) changed = true;\n transcripts.set(resource.snapshot.id, resource.entry);\n };\n\n for (const resource of resources) {\n if (\n resource.snapshot.parentId == null ||\n !source.resources.has(resource.snapshot.parentId)\n )\n build(resource, 1);\n }\n for (const resource of resources) build(resource, MAX_SUBAGENT_DEPTH);\n\n if (!changed) return;\n source.snapshot = transcripts;\n for (const listener of source.listeners) listener();\n };\n\n return source;\n};\n\nexport const useSubagentTranscripts = (\n stream: AnyStream,\n uiMessagesByParent: Map<string, UIMessage[]>,\n): ReadonlyMap<string, SubagentTranscript> => {\n const sourceRef = useRef<SubagentTranscriptSource | undefined>(undefined);\n if (!sourceRef.current) {\n sourceRef.current = createSubagentTranscriptSource();\n }\n const source = sourceRef.current;\n const controller = stream[STREAM_CONTROLLER];\n\n useEffect(() => {\n source.reconcile(controller, stream.subagents, uiMessagesByParent);\n }, [controller, source, stream.subagents, uiMessagesByParent]);\n\n useEffect(() => () => source.dispose(), [source]);\n\n return useSyncExternalStore(\n source.subscribe,\n source.getSnapshot,\n source.getSnapshot,\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAuCA,MAAa,qBAAqB;AAElC,MAAM,wBAAwB;AAE9B,MAAM,sBAAsB,CAAC;AAE7B,MAAM,iBAAuC,CAAC;AAE9C,MAAM,oBAAmD,CAAC;AAgE1D,MAAM,iBAAiB,GAAsB,MAC3C,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,SAAS,UAAU,YAAY,EAAE,MAAM;AAE3E,MAAM,4BAA4B,aAChC,SAAS,UAAU,WAAW,KAC9B,SAAS,UAAU,OAAO,SAAS,SAAS;AAE9C,MAAM,4BACJ,QACA,YACA,YACG;CACH,QAAQ,YAAY;CACpB,QAAQ,UAAU;CAClB,QAAQ,cAAc;CACtB,WACG,yBAAyB,QAAQ,EAAE,CAAC,CACpC,YAAY,CAAC,CAAC,CAAC,CACf,cAAc;EACb,IACE,OAAO,eAAe,cACtB,OAAO,kBAAkB,IAAI,QAAQ,EAAE,MAAM,SAE7C;EAEF,QAAQ,UAAU;EAClB,IACE,CAAC,QAAQ,eACT,QAAQ,aAAa,KACrB,QAAQ,WAAW,WAEnB,QAAQ,cAAc;EAExB,IAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,GAAG;GACjD,QAAQ,cAAc;GACtB;EACF;EACA,yBAAyB,QAAQ,YAAY,OAAO;CACtD,CAAC;AACL;AAEA,MAAM,yBACJ,GACA,MAEA,GAAG,SAAS,EAAE,QACd,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,gBAAgB,EAAE,IAAI,EAAE,MAAM,UAAU;AAE7D,MAAM,qBACJ,UACA,uBACG;CACH,MAAM,YAAyB,CAAC;CAChC,IAAI,mBAAmB,SAAS,GAAG,OAAO;CAC1C,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,aAAa,QAAQ,MAAM,mBAAmB,IAAI,QAAQ,EAAE;EAClE,IAAI,YAAY,UAAU,KAAK,GAAG,UAAU;CAC9C;CACA,OAAO;AACT;AAEA,MAAM,kBAAkB,GAAyB,MAC/C,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,IAAI,UAAU,OAAO,EAAE,MAAM;AAEjE,MAAM,uBAAuB,aAAiC;CAC5D,MAAM,SAAS,SAAS,SAAS,YAAY;CAC7C,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,OAAO,cAAc,QAAQ,SAAS,UAAU;AAClD;AAEA,MAAM,wBACJ,oBACA,oBACG;CACH,MAAM,SAAS,IAAI,IAAI,kBAAkB;CACzC,KAAK,MAAM,CAAC,UAAU,aAAa,wBACjC,eACF,GACE,OAAO,IAAI,UAAU,gBAAgB,UAAU,OAAO,IAAI,QAAQ,CAAC,CAAC;CAEtE,OAAO;AACT;AAEA,MAAM,mBAEF,oBACA,mBAED,SAAS,aACR,4BAA4B,SAAS;CACnC,GAAG;CACH;CACA;AACF,CAAC;AAEL,MAAM,gBAAgB,aAAiC;CACrD,IAAI,SAAS,WAAW,KAAA,GAAW;CACnC,MAAM,YAAY,SAAS,SAAS,UAAU,QAAQ;CACtD,MAAM,cAAc,SAAS,SAAS,aAAa,QAAQ;CAC3D,IACE,SAAS,OAAO,cAAc,aAC9B,SAAS,OAAO,gBAAgB,aAEhC;CACF,SAAS,SAAS;EAChB;EACA,GAAI,gBAAgB,KAAA,KAAa,EAAE,YAAY;CACjD;AACF;AAEA,MAAM,uCAAiE;CAErE,MAAM,SAAmC;EACvC,2BAAW,IAAI,IAAI;EACnB,mCAAmB,IAAI,IAAI;EAC3B,0BAAU,IAAI,IAAI;EAClB,2BAAW,IAAI,IAAI;EACnB,YAAY,KAAA;EACZ,oCAAA,IAP6B,IAOZ;EACjB,UAAU,UAAU;GAClB,OAAO,UAAU,IAAI,QAAQ;GAC7B,aAAa,OAAO,UAAU,OAAO,QAAQ;EAC/C;EACA,cAAc;GACZ,OAAO,OAAO;EAChB;EACA,UAAU,YAAY,WAAW,oBAAoB;GACnD,OAAO,qBAAqB;GAI5B,MAAM,0BAAU,IAAI,IAA4B;GAEhD,IAAI,OAAO,eAAe,YAAY;IACpC,OAAO,QAAQ;IACf,OAAO,aAAa;GACtB;GAEA,KAAK,MAAM,MAAM,OAAO,kBAAkB,KAAK,GAC7C,IAAI,CAAC,UAAU,IAAI,EAAE,GAAG,OAAO,kBAAkB,OAAO,EAAE;GAG5D,KAAK,MAAM,CAAC,IAAI,aAAa,OAAO,WAAW;IAC7C,MAAM,WAAW,UAAU,IAAI,EAAE;IACjC,IACE,CAAC,YACD,SAAS,QAAA,MACT,CAAC,cAAc,SAAS,WAAW,SAAS,SAAS,GACrD;KACA,IAAI,YAAY,SAAS,SAAA,IACvB,QAAQ,IAAI,IAAI;MACd,QAAQ,SAAS;MACjB,aAAa,SAAS;MACtB,eAAe,SAAS;KAC1B,CAAC;KACH,SAAS,QAAQ;KACjB,OAAO,UAAU,OAAO,EAAE;KAC1B;IACF;IACA,SAAS,WAAW;GACtB;GAEA,KAAK,MAAM,YAAY,UAAU,OAAO,GAAG;IACzC,IAAI,SAAS,QAAA,IAA4B;IACzC,IAAI,CAAC,yBAAyB,QAAQ,GACpC,OAAO,kBAAkB,OAAO,SAAS,EAAE;SACtC;KACL,MAAM,UAAU,OAAO,kBAAkB,IAAI,SAAS,EAAE;KACxD,IAAI,CAAC,SAAS;MACZ,MAAM,cAAgC;OACpC,IAAI,SAAS;OACb,UAAU;OACV,SAAS;OACT,aAAa;OACb,QAAQ,SAAS;MACnB;MACA,OAAO,kBAAkB,IAAI,SAAS,IAAI,WAAW;MACrD,yBAAyB,QAAQ,YAAY,WAAW;KAC1D,OAAO,IAAI,QAAQ,WAAW,SAAS,QAAQ;MAC7C,QAAQ,SAAS,SAAS;MAC1B,IAAI,QAAQ,SACV,QAAQ,cAAc,QAAQ,WAAW;WACpC,IAAI,QAAQ,WAAW,GAC5B,yBAAyB,QAAQ,YAAY,OAAO;KAExD;IACF;IACA,IAAI,OAAO,UAAU,IAAI,SAAS,EAAE,GAAG;IACvC,MAAM,WAAW,WAAW,SAAS,QACnC,2BAA2B,SAAS,SAAS,CAC/C;IACA,MAAM,aACJ,SAAS,UAAU,SAAS,wBACxB,WAAW,SAAS,QAClB,kBAAkB,oBAAoB,SAAS,SAAS,CAC1D,IACA,KAAA;IACN,MAAM,WAA+B;KACnC;KACA,WAAW,SAAS;KACpB,OAAO,SAAS;KAChB,SAAS,YAAY;KACrB,eAAe,CAAC;KAChB,eAAe,KAAA;KACf,QAAQ,KAAA;KACR,YAAY,iBAAiB;KAC7B,iBAAiB;KACjB,wBAAwB,OAAO;KAC/B,oBAAoB,OAAO;KAC3B,aAAa,QAAQ,IAAI,SAAS,EAAE,CAAC,EAAE,eAAe;KACtD,eACE,QAAQ,IAAI,SAAS,EAAE,CAAC,EAAE,iBAAiB;KAC7C,wBAAwB,KAAA;KACxB,SAAS,gBACP,OAAO,oBACP,iBACF;KACA,YAAY,CAAC;KACb,WAAW,KAAA;KACX,kBAAkB,KAAA;KAClB,YAAY,KAAA;KACZ,QAAQ,QAAQ,IAAI,SAAS,EAAE,IAC3B,QAAQ,IAAI,SAAS,EAAE,CAAC,CAAE,SAC1B,SAAS,WAAW,YAClB,EAAE,WAAW,SAAS,UAAU,QAAQ,EAAE,IAC1C,KAAA;KACN,OAAO,KAAA;KACP,MAAM,iBAAiB;KACvB,OAAO,qCAAqC;IAC9C;IACA,MAAM,cAAc,SAAS,MAAM,gBAAgB,QAAQ,CAAC;IAC5D,MAAM,gBAAgB,SAAS,SAAS,gBAAgB,QAAQ,CAAC;IACjE,SAAS,gBAAgB;KACvB,YAAY;KACZ,gBAAgB;KAChB,SAAS,QAAQ;KACjB,YAAY,QAAQ;IACtB;IACA,OAAO,UAAU,IAAI,SAAS,IAAI,QAAQ;GAC5C;GAEA,QAAQ;EACV;EACA,UAAU;GACR,KAAK,MAAM,YAAY,OAAO,UAAU,OAAO,GAAG,SAAS,QAAQ;GACnE,OAAO,UAAU,MAAM;GACvB,OAAO,kBAAkB,MAAM;GAC/B,OAAO,2BAAW,IAAI,IAAI;GAC1B,KAAK,MAAM,YAAY,OAAO,WAAW,SAAS;EACpD;CACF;CAEA,MAAM,gBAAgB;EACpB,MAAM,EAAE,uBAAuB;EAC/B,MAAM,YAAY,CAAC,GAAG,OAAO,UAAU,OAAO,CAAC;EAC/C,MAAM,mCAAmB,IAAI,IAAkC;EAE/D,KAAK,MAAM,YAAY,WAAW;GAChC,MAAM,WAAW,SAAS,SAAS;GACnC,IAAI,YAAY,MAAM;GACtB,MAAM,WAAW,iBAAiB,IAAI,QAAQ;GAC9C,IAAI,UAAU,SAAS,KAAK,QAAQ;QAC/B,iBAAiB,IAAI,UAAU,CAAC,QAAQ,CAAC;EAChD;EAEA,MAAM,8BAAc,IAAI,IAAgC;EACxD,IAAI,UAAU,OAAO,SAAS,SAAS,UAAU;EACjD,MAAM,wBAAQ,IAAI,IAAY;EAE9B,MAAM,SAAS,UAA8B,UAAkB;GAC7D,IAAI,MAAM,IAAI,SAAS,SAAS,EAAE,GAAG;GACrC,MAAM,IAAI,SAAS,SAAS,EAAE;GAC9B,MAAM,WACJ,QAAA,KACK,iBAAiB,IAAI,SAAS,SAAS,EAAE,KAAK,CAAC,IAChD,CAAC;GACP,KAAK,MAAM,SAAS,UAAU,MAAM,OAAO,QAAQ,CAAC;GACpD,MAAM,mBAAmB,IAAI,IAC3B,SAAS,SAAS,UAChB,MAAM,QAAQ,CAAC,CAAC,MAAM,SAAS,IAAI,MAAM,KAAK,CAAU,IAAI,CAAC,CAC/D,CACF;GACA,MAAM,gBAAgB,SAAS,MAAM,YAAY;GACjD,MAAM,SAAS,SAAS,SAAS;GACjC,MAAM,UAAU,oBACd,SAAS,aACT,eACA,WAAW,WACX,mCACA,KAAA,CACF;GACA,SAAS,cAAc,QAAQ;GAC/B,IAAI,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,SAAS,GACxC,SAAS,gBAAgB;IACvB,GAAG,SAAS;IACZ,GAAG,QAAQ;GACb;GAEF,MAAM,kBAAkB,oBAAoB,QAAQ;GACpD,IACE,SAAS,oBAAoB,mBAC7B,SAAS,2BAA2B,sBACpC,SAAS,2BAA2B,SAAS,eAC7C;IACA,SAAS,kBAAkB;IAC3B,SAAS,yBAAyB;IAClC,SAAS,qBACP,gBAAgB,WAAW,IACvB,qBACA,qBAAqB,oBAAoB,eAAe;IAC9D,SAAS,UAAU,gBACjB,SAAS,oBACT,SAAS,aACX;GACF;GACA,MAAM,aAAa,kBACjB,eACA,SAAS,kBACX;GAEA,MAAM,oBACJ,SAAS,cAAc,KAAA,KACvB,SAAS,kBAAkB,iBAC3B,SAAS,WAAW,UACpB,SAAS,2BAA2B,SAAS,iBAC7C,CAAC,eAAe,SAAS,YAAY,UAAU;GACjD,IAAI,mBAAmB;IACrB,SAAS,YAAY,wBACnB,eACA,SAAS,SACT,WAAW,WACX,qBACA,SAAS,KACX;IACA,SAAS,gBAAgB;IACzB,SAAS,SAAS;IAClB,SAAS,aAAa;IACtB,SAAS,yBAAyB,SAAS;GAC7C;GAEA,IACE,SAAS,eAAe,KAAA,KACxB,qBACA,CAAC,sBAAsB,SAAS,kBAAkB,gBAAgB,GAClE;IACA,MAAM,aAAa,0BACjB,SAAS,WACT,kBACA,SAAS,IACX;IACA,YAAY,SAAS,eAAe;IACpC,SAAS,aAAa;IACtB,SAAS,mBAAmB;GAC9B;GAEA,aAAa,QAAQ;GACrB,IACE,SAAS,UAAU,KAAA,KACnB,SAAS,MAAM,aAAa,SAAS,cACrC,SAAS,MAAM,WAAW,SAAS,QACnC;IACA,SAAS,QAAQ;KACf,UAAU,SAAS;KACnB,GAAI,SAAS,UAAU,EAAE,QAAQ,SAAS,OAAO;IACnD;IACA,UAAU;GACZ;GAEA,IAAI,CAAC,OAAO,SAAS,IAAI,SAAS,SAAS,EAAE,GAAG,UAAU;GAC1D,YAAY,IAAI,SAAS,SAAS,IAAI,SAAS,KAAK;EACtD;EAEA,KAAK,MAAM,YAAY,WACrB,IACE,SAAS,SAAS,YAAY,QAC9B,CAAC,OAAO,UAAU,IAAI,SAAS,SAAS,QAAQ,GAEhD,MAAM,UAAU,CAAC;EAErB,KAAK,MAAM,YAAY,WAAW,MAAM,UAAA,EAA4B;EAEpE,IAAI,CAAC,SAAS;EACd,OAAO,WAAW;EAClB,KAAK,MAAM,YAAY,OAAO,WAAW,SAAS;CACpD;CAEA,OAAO;AACT;AAEA,MAAa,0BACX,QACA,uBAC4C;CAC5C,MAAM,YAAY,OAA6C,KAAA,CAAS;CACxE,IAAI,CAAC,UAAU,SACb,UAAU,UAAU,+BAA+B;CAErD,MAAM,SAAS,UAAU;CACzB,MAAM,aAAa,OAAO;CAE1B,gBAAgB;EACd,OAAO,UAAU,YAAY,OAAO,WAAW,kBAAkB;CACnE,GAAG;EAAC;EAAY;EAAQ,OAAO;EAAW;CAAkB,CAAC;CAE7D,sBAAsB,OAAO,QAAQ,GAAG,CAAC,MAAM,CAAC;CAEhD,OAAO,qBACL,OAAO,WACP,OAAO,aACP,OAAO,WACT;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-langchain",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "LangChain useStream adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "langchain",
@@ -40,18 +40,22 @@
40
40
  ],
41
41
  "sideEffects": false,
42
42
  "dependencies": {
43
- "@assistant-ui/core": "^0.3.19",
44
- "@assistant-ui/store": "^0.3.13",
43
+ "@assistant-ui/core": "^0.3.21",
44
+ "@assistant-ui/store": "^0.3.15",
45
45
  "assistant-cloud": "*",
46
- "assistant-stream": "^0.3.43"
46
+ "assistant-stream": "^0.3.45"
47
47
  },
48
48
  "peerDependencies": {
49
+ "@langchain/core": "^1.0.0",
49
50
  "@langchain/langgraph-sdk": "^1.9.20",
50
51
  "@langchain/react": "^1.0.20",
51
52
  "@types/react": "*",
52
53
  "react": "^18 || ^19"
53
54
  },
54
55
  "peerDependenciesMeta": {
56
+ "@langchain/core": {
57
+ "optional": true
58
+ },
55
59
  "@langchain/langgraph-sdk": {
56
60
  "optional": true
57
61
  },
@@ -63,15 +67,15 @@
63
67
  }
64
68
  },
65
69
  "devDependencies": {
66
- "@assistant-ui/x-buildutils": "0.0.27",
67
- "@langchain/core": "^1.2.9",
68
- "@langchain/langgraph-sdk": "^1.10.2",
69
- "@langchain/react": "^1.0.35",
70
+ "@assistant-ui/x-buildutils": "0.0.29",
71
+ "@langchain/core": "^1.2.11",
72
+ "@langchain/langgraph-sdk": "^1.11.0",
73
+ "@langchain/react": "^1.1.0",
70
74
  "@testing-library/react": "^16.3.3",
71
- "@types/react": "^19.2.18",
75
+ "@types/react": "^19.3.0",
72
76
  "jsdom": "^30.0.1",
73
- "react": "^19.2.8",
74
- "vitest": "^5.0.0"
77
+ "react": "^19.3.0",
78
+ "vitest": "^5.0.1"
75
79
  },
76
80
  "publishConfig": {
77
81
  "access": "public",
@@ -89,6 +93,7 @@
89
93
  "scripts": {
90
94
  "build": "aui-build",
91
95
  "test": "vitest run",
96
+ "typecheck": "tsc --noEmit",
92
97
  "test:watch": "vitest"
93
98
  }
94
99
  }
@@ -7,6 +7,7 @@ import { describe, expect, it } from "vitest";
7
7
  import {
8
8
  attachSubagentTranscripts,
9
9
  createAttachMemo,
10
+ type SubagentTranscript,
10
11
  } from "./attachSubagentTranscripts";
11
12
 
12
13
  const assistantMessage = (id: string, toolCallId?: string): ThreadMessage =>
@@ -35,7 +36,13 @@ const assistantMessage = (id: string, toolCallId?: string): ThreadMessage =>
35
36
  },
36
37
  }) as ThreadMessage;
37
38
 
38
- const transcript = (id: string) => [assistantMessage(id)];
39
+ const transcript = (
40
+ id: string,
41
+ timing: SubagentTranscript["timing"] = {
42
+ startedAt: 1_000,
43
+ completedAt: 3_500,
44
+ },
45
+ ): SubagentTranscript => ({ messages: [assistantMessage(id)], timing });
39
46
 
40
47
  describe("attachSubagentTranscripts", () => {
41
48
  it("returns the input array when no tool call has a transcript", () => {
@@ -68,7 +75,8 @@ describe("attachSubagentTranscripts", () => {
68
75
  const part = attached[0]!.content[0]!;
69
76
  expect(part.type).toBe("tool-call");
70
77
  if (part.type !== "tool-call") throw new Error("expected a tool call");
71
- expect(part.messages).toBe(childTranscript);
78
+ expect(part.messages).toBe(childTranscript.messages);
79
+ expect(part.timing).toBe(childTranscript.timing);
72
80
  });
73
81
 
74
82
  it("reuses the attached array when transcript identities are unchanged", () => {
@@ -141,10 +149,10 @@ describe("attachSubagentTranscripts", () => {
141
149
  memo,
142
150
  );
143
151
 
144
- expect(before?.content[0]).toMatchObject({ messages: nested });
152
+ expect(before?.content[0]).toMatchObject({ messages: nested.messages });
145
153
  expect(after).not.toBe(before);
146
154
  expect(after?.content[0]).toBe(first);
147
- expect(after?.content[1]).toMatchObject({ messages: nested });
155
+ expect(after?.content[1]).toMatchObject({ messages: nested.messages });
148
156
  });
149
157
 
150
158
  it("keeps untouched parts of a partially matched message by reference", () => {
@@ -164,7 +172,7 @@ describe("attachSubagentTranscripts", () => {
164
172
  );
165
173
 
166
174
  expect(attached?.content[0]).toBe(text);
167
- expect(attached?.content[1]).toMatchObject({ messages: nested });
175
+ expect(attached?.content[1]).toMatchObject({ messages: nested.messages });
168
176
  expect(attached?.content[2]).toBe(unmatched);
169
177
  });
170
178
 
@@ -2,9 +2,20 @@ import {
2
2
  bindExternalStoreMessage,
3
3
  getExternalStoreMessages,
4
4
  type ThreadMessage,
5
+ type ToolCallTiming,
5
6
  } from "@assistant-ui/core";
6
7
 
7
- type AttachedTranscript = readonly [string, readonly ThreadMessage[]];
8
+ export type SubagentTranscript = {
9
+ readonly messages: readonly ThreadMessage[];
10
+ /**
11
+ * Only present when the client watched the task run: the discovery
12
+ * timestamps are stamped when the task call is first seen, so a thread
13
+ * seeded from a checkpoint would report a duration of zero.
14
+ */
15
+ readonly timing?: ToolCallTiming;
16
+ };
17
+
18
+ type AttachedTranscript = readonly [string, SubagentTranscript];
8
19
 
9
20
  type AttachedMessage = {
10
21
  transcripts: readonly AttachedTranscript[];
@@ -33,7 +44,7 @@ const sameTranscripts = (
33
44
 
34
45
  export const attachSubagentTranscripts = (
35
46
  messages: readonly ThreadMessage[],
36
- transcripts: ReadonlyMap<string, readonly ThreadMessage[]>,
47
+ transcripts: ReadonlyMap<string, SubagentTranscript>,
37
48
  memo: AttachMemo,
38
49
  ): readonly ThreadMessage[] => {
39
50
  let attachedAny = false;
@@ -52,11 +63,19 @@ export const attachSubagentTranscripts = (
52
63
 
53
64
  const attached = {
54
65
  ...message,
55
- content: message.content.map((part) =>
56
- part.type === "tool-call" && transcripts.has(part.toolCallId)
57
- ? { ...part, messages: transcripts.get(part.toolCallId)! }
58
- : part,
59
- ),
66
+ content: message.content.map((part) => {
67
+ const transcript =
68
+ part.type === "tool-call"
69
+ ? transcripts.get(part.toolCallId)
70
+ : undefined;
71
+ return transcript
72
+ ? {
73
+ ...part,
74
+ messages: transcript.messages,
75
+ ...(transcript.timing && { timing: transcript.timing }),
76
+ }
77
+ : part;
78
+ }),
60
79
  } as ThreadMessage;
61
80
  memo.messages.set(message, { transcripts: attachedTranscripts, attached });
62
81
  return attached;
@@ -27,6 +27,27 @@ const contentOf = (result: ReturnType<typeof convertLangChainBaseMessage>) => {
27
27
  return result.content;
28
28
  };
29
29
 
30
+ describe("convertLangChainBaseMessage modality", () => {
31
+ it("lifts voice modality onto human and ai messages and ignores unknown values", () => {
32
+ for (const message of [humanMessage("Question"), aiMessage("Answer")]) {
33
+ const spoken = convertLangChainBaseMessage({
34
+ ...message,
35
+ additional_kwargs: { modality: "voice" },
36
+ });
37
+ const unknown = convertLangChainBaseMessage({
38
+ ...message,
39
+ additional_kwargs: { modality: "video" },
40
+ });
41
+
42
+ expect(spoken).toHaveProperty("metadata", {
43
+ custom: {},
44
+ modality: "voice",
45
+ });
46
+ expect(unknown).toHaveProperty("metadata", { custom: {} });
47
+ }
48
+ });
49
+ });
50
+
30
51
  describe("convertLangChainBaseMessage file content parts", () => {
31
52
  it("converts a base64 file block", () => {
32
53
  const result = convertLangChainBaseMessage(
@@ -1113,6 +1134,54 @@ describe("convertLangChainBaseMessage malformed messages", () => {
1113
1134
  ]);
1114
1135
  });
1115
1136
 
1137
+ it("normalizes missing, string, and array tool-call args to an object", () => {
1138
+ for (const args of [undefined, "not-json", ["x"], null]) {
1139
+ const result = convertLangChainBaseMessage(
1140
+ {
1141
+ ...aiMessage([]),
1142
+ tool_calls: [{ id: "call-1", name: "lookup", args }],
1143
+ } as unknown as LangChainBaseMessage,
1144
+ {},
1145
+ );
1146
+ expect(contentOf(result)).toEqual([
1147
+ {
1148
+ type: "tool-call",
1149
+ toolCallId: "call-1",
1150
+ toolName: "lookup",
1151
+ args: {},
1152
+ argsText: "{}",
1153
+ },
1154
+ ]);
1155
+ }
1156
+ });
1157
+
1158
+ it("normalizes unsafe object tool-call args to an empty object", () => {
1159
+ const cyclic: Record<string, unknown> = {};
1160
+ cyclic.self = cyclic;
1161
+ class CustomArgs {
1162
+ query = "x";
1163
+ }
1164
+
1165
+ for (const args of [new Date(0), new Map(), new CustomArgs(), cyclic]) {
1166
+ const result = convertLangChainBaseMessage(
1167
+ {
1168
+ ...aiMessage([]),
1169
+ tool_calls: [{ id: "call-1", name: "lookup", args }],
1170
+ } as unknown as LangChainBaseMessage,
1171
+ {},
1172
+ );
1173
+ expect(contentOf(result)).toEqual([
1174
+ {
1175
+ type: "tool-call",
1176
+ toolCallId: "call-1",
1177
+ toolName: "lookup",
1178
+ args: {},
1179
+ argsText: "{}",
1180
+ },
1181
+ ]);
1182
+ }
1183
+ });
1184
+
1116
1185
  it("converts a system message with null content to empty text", () => {
1117
1186
  const result = convertLangChainBaseMessage(
1118
1187
  { _getType: () => "system", id: "msg-4", content: null },