@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.
- package/dist/attachSubagentTranscripts.d.ts +19 -12
- package/dist/attachSubagentTranscripts.d.ts.map +1 -1
- package/dist/attachSubagentTranscripts.js +8 -4
- package/dist/attachSubagentTranscripts.js.map +1 -1
- package/dist/convertMessages.d.ts +8 -11
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +53 -15
- package/dist/convertMessages.js.map +1 -1
- package/dist/converter.d.ts +137 -138
- package/dist/converter.d.ts.map +1 -1
- package/dist/converter.js +2 -1
- package/dist/converter.js.map +1 -1
- package/dist/findForkCheckpointInHistory.d.ts +17 -19
- package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
- package/dist/hooks.d.ts +19 -22
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/resolveForkCheckpoint.d.ts +5 -7
- package/dist/resolveForkCheckpoint.d.ts.map +1 -1
- package/dist/runtimeExtras.d.ts +2 -5
- package/dist/runtimeExtras.d.ts.map +1 -1
- package/dist/sdkIdentity.d.ts +2 -5
- package/dist/sdkIdentity.d.ts.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/streamingTiming.d.ts +4 -7
- package/dist/streamingTiming.d.ts.map +1 -1
- package/dist/subagentMessagesProjection.d.ts +17 -0
- package/dist/subagentMessagesProjection.d.ts.map +1 -0
- package/dist/subagentMessagesProjection.js +40 -0
- package/dist/subagentMessagesProjection.js.map +1 -0
- package/dist/types.d.ts +106 -109
- package/dist/types.d.ts.map +1 -1
- package/dist/uiMessages.d.ts +37 -10
- package/dist/uiMessages.d.ts.map +1 -1
- package/dist/uiMessages.js +55 -5
- package/dist/uiMessages.js.map +1 -1
- package/dist/useStreamRuntime.d.ts +9 -12
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +102 -28
- package/dist/useStreamRuntime.js.map +1 -1
- package/dist/useSubagentTranscripts.d.ts +5 -8
- package/dist/useSubagentTranscripts.d.ts.map +1 -1
- package/dist/useSubagentTranscripts.js +129 -34
- package/dist/useSubagentTranscripts.js.map +1 -1
- package/package.json +16 -11
- package/src/attachSubagentTranscripts.test.ts +13 -5
- package/src/attachSubagentTranscripts.ts +26 -7
- package/src/convertMessages.test.ts +69 -0
- package/src/convertMessages.ts +46 -8
- package/src/converter.ts +6 -0
- package/src/subagentMessagesProjection.test.ts +175 -0
- package/src/subagentMessagesProjection.ts +50 -0
- package/src/uiMessages.test.ts +149 -0
- package/src/uiMessages.ts +79 -4
- package/src/useStreamRuntime.test.tsx +212 -0
- package/src/useStreamRuntime.ts +202 -50
- package/src/useStreamRuntime.voice.test.tsx +712 -0
- package/src/useSubagentTranscripts.test.tsx +795 -52
- 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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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.
|
|
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
|
-
|
|
58
|
-
|
|
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.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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.
|
|
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
|
|
128
|
-
|
|
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.
|
|
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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-langchain",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
44
|
-
"@assistant-ui/store": "^0.3.
|
|
43
|
+
"@assistant-ui/core": "^0.3.21",
|
|
44
|
+
"@assistant-ui/store": "^0.3.15",
|
|
45
45
|
"assistant-cloud": "*",
|
|
46
|
-
"assistant-stream": "^0.3.
|
|
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.
|
|
67
|
-
"@langchain/core": "^1.2.
|
|
68
|
-
"@langchain/langgraph-sdk": "^1.
|
|
69
|
-
"@langchain/react": "^1.0
|
|
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.
|
|
75
|
+
"@types/react": "^19.3.0",
|
|
72
76
|
"jsdom": "^30.0.1",
|
|
73
|
-
"react": "^19.
|
|
74
|
-
"vitest": "^5.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 = (
|
|
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
|
|
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,
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 },
|