@assistant-ui/react-langchain 0.0.32 → 0.0.34

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 (59) hide show
  1. package/LICENSE +1 -1
  2. package/dist/attachSubagentTranscripts.d.ts +13 -14
  3. package/dist/attachSubagentTranscripts.d.ts.map +1 -1
  4. package/dist/convertMessages.d.ts +6 -9
  5. package/dist/convertMessages.d.ts.map +1 -1
  6. package/dist/convertMessages.js +57 -15
  7. package/dist/convertMessages.js.map +1 -1
  8. package/dist/converter.d.ts +128 -128
  9. package/dist/converter.d.ts.map +1 -1
  10. package/dist/converter.js +2 -1
  11. package/dist/converter.js.map +1 -1
  12. package/dist/findForkCheckpointInHistory.d.ts +15 -16
  13. package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
  14. package/dist/hooks.d.ts +5 -7
  15. package/dist/hooks.d.ts.map +1 -1
  16. package/dist/index.d.ts +7 -8
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/resolveForkCheckpoint.d.ts +4 -5
  19. package/dist/resolveForkCheckpoint.d.ts.map +1 -1
  20. package/dist/runtimeExtras.d.ts +1 -3
  21. package/dist/runtimeExtras.d.ts.map +1 -1
  22. package/dist/sdkIdentity.d.ts +1 -3
  23. package/dist/sdkIdentity.d.ts.map +1 -1
  24. package/dist/sdkIdentity.js +1 -1
  25. package/dist/streamingTiming.d.ts +2 -4
  26. package/dist/streamingTiming.d.ts.map +1 -1
  27. package/dist/subagentMessagesProjection.d.ts +17 -0
  28. package/dist/subagentMessagesProjection.d.ts.map +1 -0
  29. package/dist/subagentMessagesProjection.js +40 -0
  30. package/dist/subagentMessagesProjection.js.map +1 -0
  31. package/dist/types.d.ts +100 -103
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/uiMessages.d.ts +18 -6
  34. package/dist/uiMessages.d.ts.map +1 -1
  35. package/dist/uiMessages.js +33 -1
  36. package/dist/uiMessages.js.map +1 -1
  37. package/dist/useStreamRuntime.d.ts +7 -10
  38. package/dist/useStreamRuntime.d.ts.map +1 -1
  39. package/dist/useStreamRuntime.js +100 -26
  40. package/dist/useStreamRuntime.js.map +1 -1
  41. package/dist/useSubagentTranscripts.d.ts +3 -5
  42. package/dist/useSubagentTranscripts.d.ts.map +1 -1
  43. package/dist/useSubagentTranscripts.js +5 -3
  44. package/dist/useSubagentTranscripts.js.map +1 -1
  45. package/package.json +16 -9
  46. package/src/convertMessages.test.ts +153 -0
  47. package/src/convertMessages.ts +54 -8
  48. package/src/converter.ts +6 -0
  49. package/src/subagentMessagesProjection.test.ts +175 -0
  50. package/src/subagentMessagesProjection.ts +50 -0
  51. package/src/tests/settleOutsideAct.ts +15 -0
  52. package/src/uiMessages.test.ts +106 -0
  53. package/src/uiMessages.ts +43 -0
  54. package/src/useStreamRuntime.test.tsx +147 -7
  55. package/src/useStreamRuntime.ts +197 -45
  56. package/src/useStreamRuntime.voice.test.tsx +713 -0
  57. package/src/useSubagentTranscripts.fast-refresh.test.tsx +152 -0
  58. package/src/useSubagentTranscripts.test.tsx +2 -0
  59. package/src/useSubagentTranscripts.ts +5 -7
@@ -1,6 +1,5 @@
1
- import { RemoveUIMessage, UIMessage } from "./types.js";
2
- import { Channel } from "@langchain/react";
3
- //#region src/uiMessages.d.ts
1
+ import type { Channel } from "@langchain/react";
2
+ import type { RemoveUIMessage, UIMessage } from "./types.js";
4
3
  /** Channels the generative-UI fold reads, at the root and per subagent. */
5
4
  export declare const UI_CUSTOM_CHANNELS: readonly Channel[];
6
5
  export type UIUpdate = UIMessage | RemoveUIMessage;
@@ -13,8 +12,8 @@ export declare const applyUIUpdate: (list: readonly UIMessage[], update: UIUpdat
13
12
  */
14
13
  export declare const extractUIUpdate: (event: unknown) => UIUpdate | readonly UIUpdate[] | undefined;
15
14
  export type UIFoldMemo = {
16
- events: readonly unknown[];
17
- messages: UIMessage[];
15
+ events: readonly unknown[];
16
+ messages: UIMessage[];
18
17
  };
19
18
  export declare const createUIFoldMemo: () => UIFoldMemo;
20
19
  /**
@@ -26,6 +25,20 @@ export declare const createUIFoldMemo: () => UIFoldMemo;
26
25
  * and is folded from scratch.
27
26
  */
28
27
  export declare const foldUIUpdates: (events: readonly unknown[], memo?: UIFoldMemo) => UIMessage[];
28
+ export type UISnapshotMemo = {
29
+ snapshot: unknown;
30
+ entries: readonly UIMessage[];
31
+ };
32
+ export declare const createUISnapshotMemo: () => UISnapshotMemo;
33
+ /**
34
+ * Recovers entry identity across `values` events. The SDK rebuilds the
35
+ * `values` object from every snapshot and reconciles only the messages slot by
36
+ * id, so an unchanged UI list arrives as a new array of new objects on every
37
+ * superstep. An entry structurally equal to the previous entry with its id is
38
+ * replaced by that previous object, and a list whose entries all survive in
39
+ * place is replaced by the previous list.
40
+ */
41
+ export declare const reconcileUISnapshot: (snapshot: unknown, memo: UISnapshotMemo) => readonly UIMessage[];
29
42
  /**
30
43
  * Merges live-streamed UI with the state snapshot. The snapshot is
31
44
  * authoritative by id: once a UI lands in graph state it supersedes its live
@@ -34,5 +47,4 @@ export declare const foldUIUpdates: (events: readonly unknown[], memo?: UIFoldMe
34
47
  * the snapshot catches up.
35
48
  */
36
49
  export declare const mergeUIMessages: (live: readonly UIMessage[], snapshot: unknown) => UIMessage[];
37
- //#endregion
38
50
  //# sourceMappingURL=uiMessages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uiMessages.d.ts","names":[],"sources":["../src/uiMessages.ts"],"mappings":";;;;qBAIa,6BAA6B;YAE9B,WAAW,YAAY;qBAEtB,aACX,mBACC,SAAS,oBAAoB;qBAmBnB,gBACX,eAAe,aACf,QAAQ,oBAAoB,eAC3B;;;;;;qBA0BU,kBACX,mBACC,oBAAoB;YAQX;EACV;EACA,UAAU;;qBAGC,wBAAuB;;;;;;;;;qBAavB,gBACX,4BACA,OAAM,eACL;;;;;;;;qBAuBU,kBACX,eAAe,aACf,sBACC"}
1
+ {"version":3,"file":"uiMessages.d.ts","sourceRoot":"","sources":["../src/uiMessages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAgB;AAE1D,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAAe,CAAC;AAEjE,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAC;AAEnD,eAAO,MAAM,UAAU,GACrB,OAAO,OAAO,KACb,KAAK,IAAI,QAAQ,GAAG,SAAS,QAAQ,EAiBvC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,SAAS,SAAS,EAAE,EAC1B,QAAQ,QAAQ,GAAG,SAAS,QAAQ,EAAE,KACrC,SAAS,EAmBX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,OAAO,KACb,QAAQ,GAAG,SAAS,QAAQ,EAAE,GAAG,SAMnC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,SAAS,OAAO,EAAE,EAC1B,OAAM,UAA+B,KACpC,SAAS,EAcX,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,cAGtC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,OAAO,EACjB,MAAM,cAAc,KACnB,SAAS,SAAS,EAmBpB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,SAAS,SAAS,EAAE,EAC1B,UAAU,OAAO,KAChB,SAAS,EAOX,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { isJSONValueEqual } from "@assistant-ui/core/internal";
1
2
  //#region src/uiMessages.ts
2
3
  /** Channels the generative-UI fold reads, at the root and per subagent. */
3
4
  const UI_CUSTOM_CHANNELS = ["custom"];
@@ -67,6 +68,37 @@ const foldUIUpdates = (events, memo = createUIFoldMemo()) => {
67
68
  memo.messages = acc;
68
69
  return acc;
69
70
  };
71
+ const createUISnapshotMemo = () => ({
72
+ snapshot: void 0,
73
+ entries: []
74
+ });
75
+ /**
76
+ * Recovers entry identity across `values` events. The SDK rebuilds the
77
+ * `values` object from every snapshot and reconciles only the messages slot by
78
+ * id, so an unchanged UI list arrives as a new array of new objects on every
79
+ * superstep. An entry structurally equal to the previous entry with its id is
80
+ * replaced by that previous object, and a list whose entries all survive in
81
+ * place is replaced by the previous list.
82
+ */
83
+ const reconcileUISnapshot = (snapshot, memo) => {
84
+ if (snapshot === memo.snapshot) return memo.entries;
85
+ memo.snapshot = snapshot;
86
+ if (!Array.isArray(snapshot)) {
87
+ if (memo.entries.length > 0) memo.entries = [];
88
+ return memo.entries;
89
+ }
90
+ const previous = memo.entries;
91
+ const previousById = new Map(previous.map((ui) => [ui.id, ui]));
92
+ let same = snapshot.length === previous.length;
93
+ const entries = snapshot.map((ui, index) => {
94
+ const before = previousById.get(ui.id);
95
+ const entry = before !== void 0 && isJSONValueEqual(before, ui) ? before : ui;
96
+ if (entry !== previous[index]) same = false;
97
+ return entry;
98
+ });
99
+ if (!same) memo.entries = entries;
100
+ return memo.entries;
101
+ };
70
102
  /**
71
103
  * Merges live-streamed UI with the state snapshot. The snapshot is
72
104
  * authoritative by id: once a UI lands in graph state it supersedes its live
@@ -81,6 +113,6 @@ const mergeUIMessages = (live, snapshot) => {
81
113
  return [...byId.values()];
82
114
  };
83
115
  //#endregion
84
- export { UI_CUSTOM_CHANNELS, applyUIUpdate, createUIFoldMemo, extractUIUpdate, foldUIUpdates, isUIUpdate, mergeUIMessages };
116
+ export { UI_CUSTOM_CHANNELS, applyUIUpdate, createUIFoldMemo, createUISnapshotMemo, extractUIUpdate, foldUIUpdates, isUIUpdate, mergeUIMessages, reconcileUISnapshot };
85
117
 
86
118
  //# sourceMappingURL=uiMessages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"uiMessages.js","names":[],"sources":["../src/uiMessages.ts"],"sourcesContent":["import type { Channel } from \"@langchain/react\";\nimport type { RemoveUIMessage, UIMessage } from \"./types\";\n\n/** Channels the generative-UI fold reads, at the root and per subagent. */\nexport const UI_CUSTOM_CHANNELS: readonly Channel[] = [\"custom\"];\n\nexport type UIUpdate = UIMessage | RemoveUIMessage;\n\nexport const isUIUpdate = (\n value: unknown,\n): value is UIUpdate | readonly UIUpdate[] => {\n if (Array.isArray(value)) return value.length > 0 && value.every(isUIUpdate);\n if (value == null || typeof value !== \"object\") return false;\n const v = value as {\n type?: unknown;\n id?: unknown;\n name?: unknown;\n props?: unknown;\n };\n if (typeof v.id !== \"string\") return false;\n if (v.type === \"remove-ui\") return true;\n return (\n v.type === \"ui\" &&\n typeof v.name === \"string\" &&\n typeof v.props === \"object\" &&\n v.props !== null\n );\n};\n\nexport const applyUIUpdate = (\n list: readonly UIMessage[],\n update: UIUpdate | readonly UIUpdate[],\n): UIMessage[] => {\n const events = Array.isArray(update) ? update : [update as UIUpdate];\n let next = list.slice();\n for (const event of events) {\n if (event.type === \"remove-ui\") {\n next = next.filter((ui) => ui.id !== event.id);\n continue;\n }\n const index = next.findIndex((ui) => ui.id === event.id);\n if (index === -1) {\n next.push(event);\n continue;\n }\n next[index] =\n event.metadata?.merge === true\n ? { ...event, props: { ...next[index]!.props, ...event.props } }\n : event;\n }\n return next;\n};\n\n/**\n * Pulls a UI update out of a raw `custom`-channel event. The graph writes the\n * `UIMessage` straight to the channel, so it lands at `params.data`; some\n * transports wrap it one level deeper at `params.data.payload`.\n */\nexport const extractUIUpdate = (\n event: unknown,\n): UIUpdate | readonly UIUpdate[] | undefined => {\n const data = (event as { params?: { data?: unknown } } | null)?.params?.data;\n if (isUIUpdate(data)) return data;\n const payload = (data as { payload?: unknown } | undefined)?.payload;\n if (isUIUpdate(payload)) return payload;\n return undefined;\n};\n\nexport type UIFoldMemo = {\n events: readonly unknown[];\n messages: UIMessage[];\n};\n\nexport const createUIFoldMemo = (): UIFoldMemo => ({\n events: [],\n messages: [],\n});\n\n/**\n * Folds `custom`-channel events into UI messages, continuing the fold held in\n * `memo`. The channel buffer holds each event object once, appends new events\n * and drops its oldest ones once full, so only events after the previously\n * folded last event are applied, and entries no later event touches keep\n * their identity. A buffer that no longer contains that event was replaced\n * and is folded from scratch.\n */\nexport const foldUIUpdates = (\n events: readonly unknown[],\n memo: UIFoldMemo = createUIFoldMemo(),\n): UIMessage[] => {\n const previous = memo.events;\n const resumeAt =\n previous.length === 0\n ? 0\n : events.lastIndexOf(previous.at(-1), previous.length - 1) + 1;\n let acc = previous.length > 0 && resumeAt === 0 ? [] : memo.messages;\n for (let i = resumeAt; i < events.length; i++) {\n const update = extractUIUpdate(events[i]);\n if (update) acc = applyUIUpdate(acc, update);\n }\n memo.events = events;\n memo.messages = acc;\n return acc;\n};\n\n/**\n * Merges live-streamed UI with the state snapshot. The snapshot is\n * authoritative by id: once a UI lands in graph state it supersedes its live\n * copy. A consequence is that a live `remove-ui` is overridden while the\n * snapshot still contains that id; the removal only takes visible effect once\n * the snapshot catches up.\n */\nexport const mergeUIMessages = (\n live: readonly UIMessage[],\n snapshot: unknown,\n): UIMessage[] => {\n const byId = new Map<string, UIMessage>();\n for (const ui of live) byId.set(ui.id, ui);\n if (Array.isArray(snapshot)) {\n for (const ui of snapshot as UIMessage[]) byId.set(ui.id, ui);\n }\n return [...byId.values()];\n};\n"],"mappings":";;AAIA,MAAa,qBAAyC,CAAC,QAAQ;AAI/D,MAAa,cACX,UAC4C;CAC5C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,SAAS,KAAK,MAAM,MAAM,UAAU;CAC3E,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU,OAAO;CACvD,MAAM,IAAI;CAMV,IAAI,OAAO,EAAE,OAAO,UAAU,OAAO;CACrC,IAAI,EAAE,SAAS,aAAa,OAAO;CACnC,OACE,EAAE,SAAS,QACX,OAAO,EAAE,SAAS,YAClB,OAAO,EAAE,UAAU,YACnB,EAAE,UAAU;AAEhB;AAEA,MAAa,iBACX,MACA,WACgB;CAChB,MAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAkB;CACnE,IAAI,OAAO,KAAK,MAAM;CACtB,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,SAAS,aAAa;GAC9B,OAAO,KAAK,QAAQ,OAAO,GAAG,OAAO,MAAM,EAAE;GAC7C;EACF;EACA,MAAM,QAAQ,KAAK,WAAW,OAAO,GAAG,OAAO,MAAM,EAAE;EACvD,IAAI,UAAU,IAAI;GAChB,KAAK,KAAK,KAAK;GACf;EACF;EACA,KAAK,SACH,MAAM,UAAU,UAAU,OACtB;GAAE,GAAG;GAAO,OAAO;IAAE,GAAG,KAAK,MAAM,CAAE;IAAO,GAAG,MAAM;GAAM;EAAE,IAC7D;CACR;CACA,OAAO;AACT;;;;;;AAOA,MAAa,mBACX,UAC+C;CAC/C,MAAM,OAAQ,OAAkD,QAAQ;CACxE,IAAI,WAAW,IAAI,GAAG,OAAO;CAC7B,MAAM,UAAW,MAA4C;CAC7D,IAAI,WAAW,OAAO,GAAG,OAAO;AAElC;AAOA,MAAa,0BAAsC;CACjD,QAAQ,CAAC;CACT,UAAU,CAAC;AACb;;;;;;;;;AAUA,MAAa,iBACX,QACA,OAAmB,iBAAiB,MACpB;CAChB,MAAM,WAAW,KAAK;CACtB,MAAM,WACJ,SAAS,WAAW,IAChB,IACA,OAAO,YAAY,SAAS,GAAG,EAAE,GAAG,SAAS,SAAS,CAAC,IAAI;CACjE,IAAI,MAAM,SAAS,SAAS,KAAK,aAAa,IAAI,CAAC,IAAI,KAAK;CAC5D,KAAK,IAAI,IAAI,UAAU,IAAI,OAAO,QAAQ,KAAK;EAC7C,MAAM,SAAS,gBAAgB,OAAO,EAAE;EACxC,IAAI,QAAQ,MAAM,cAAc,KAAK,MAAM;CAC7C;CACA,KAAK,SAAS;CACd,KAAK,WAAW;CAChB,OAAO;AACT;;;;;;;;AASA,MAAa,mBACX,MACA,aACgB;CAChB,MAAM,uBAAO,IAAI,IAAuB;CACxC,KAAK,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE;CACzC,IAAI,MAAM,QAAQ,QAAQ,GACxB,KAAK,MAAM,MAAM,UAAyB,KAAK,IAAI,GAAG,IAAI,EAAE;CAE9D,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B"}
1
+ {"version":3,"file":"uiMessages.js","names":[],"sources":["../src/uiMessages.ts"],"sourcesContent":["import type { Channel } from \"@langchain/react\";\nimport { isJSONValueEqual } from \"@assistant-ui/core/internal\";\nimport type { RemoveUIMessage, UIMessage } from \"./types\";\n\n/** Channels the generative-UI fold reads, at the root and per subagent. */\nexport const UI_CUSTOM_CHANNELS: readonly Channel[] = [\"custom\"];\n\nexport type UIUpdate = UIMessage | RemoveUIMessage;\n\nexport const isUIUpdate = (\n value: unknown,\n): value is UIUpdate | readonly UIUpdate[] => {\n if (Array.isArray(value)) return value.length > 0 && value.every(isUIUpdate);\n if (value == null || typeof value !== \"object\") return false;\n const v = value as {\n type?: unknown;\n id?: unknown;\n name?: unknown;\n props?: unknown;\n };\n if (typeof v.id !== \"string\") return false;\n if (v.type === \"remove-ui\") return true;\n return (\n v.type === \"ui\" &&\n typeof v.name === \"string\" &&\n typeof v.props === \"object\" &&\n v.props !== null\n );\n};\n\nexport const applyUIUpdate = (\n list: readonly UIMessage[],\n update: UIUpdate | readonly UIUpdate[],\n): UIMessage[] => {\n const events = Array.isArray(update) ? update : [update as UIUpdate];\n let next = list.slice();\n for (const event of events) {\n if (event.type === \"remove-ui\") {\n next = next.filter((ui) => ui.id !== event.id);\n continue;\n }\n const index = next.findIndex((ui) => ui.id === event.id);\n if (index === -1) {\n next.push(event);\n continue;\n }\n next[index] =\n event.metadata?.merge === true\n ? { ...event, props: { ...next[index]!.props, ...event.props } }\n : event;\n }\n return next;\n};\n\n/**\n * Pulls a UI update out of a raw `custom`-channel event. The graph writes the\n * `UIMessage` straight to the channel, so it lands at `params.data`; some\n * transports wrap it one level deeper at `params.data.payload`.\n */\nexport const extractUIUpdate = (\n event: unknown,\n): UIUpdate | readonly UIUpdate[] | undefined => {\n const data = (event as { params?: { data?: unknown } } | null)?.params?.data;\n if (isUIUpdate(data)) return data;\n const payload = (data as { payload?: unknown } | undefined)?.payload;\n if (isUIUpdate(payload)) return payload;\n return undefined;\n};\n\nexport type UIFoldMemo = {\n events: readonly unknown[];\n messages: UIMessage[];\n};\n\nexport const createUIFoldMemo = (): UIFoldMemo => ({\n events: [],\n messages: [],\n});\n\n/**\n * Folds `custom`-channel events into UI messages, continuing the fold held in\n * `memo`. The channel buffer holds each event object once, appends new events\n * and drops its oldest ones once full, so only events after the previously\n * folded last event are applied, and entries no later event touches keep\n * their identity. A buffer that no longer contains that event was replaced\n * and is folded from scratch.\n */\nexport const foldUIUpdates = (\n events: readonly unknown[],\n memo: UIFoldMemo = createUIFoldMemo(),\n): UIMessage[] => {\n const previous = memo.events;\n const resumeAt =\n previous.length === 0\n ? 0\n : events.lastIndexOf(previous.at(-1), previous.length - 1) + 1;\n let acc = previous.length > 0 && resumeAt === 0 ? [] : memo.messages;\n for (let i = resumeAt; i < events.length; i++) {\n const update = extractUIUpdate(events[i]);\n if (update) acc = applyUIUpdate(acc, update);\n }\n memo.events = events;\n memo.messages = acc;\n return acc;\n};\n\nexport type UISnapshotMemo = {\n snapshot: unknown;\n entries: readonly UIMessage[];\n};\n\nexport const createUISnapshotMemo = (): UISnapshotMemo => ({\n snapshot: undefined,\n entries: [],\n});\n\n/**\n * Recovers entry identity across `values` events. The SDK rebuilds the\n * `values` object from every snapshot and reconciles only the messages slot by\n * id, so an unchanged UI list arrives as a new array of new objects on every\n * superstep. An entry structurally equal to the previous entry with its id is\n * replaced by that previous object, and a list whose entries all survive in\n * place is replaced by the previous list.\n */\nexport const reconcileUISnapshot = (\n snapshot: unknown,\n memo: UISnapshotMemo,\n): readonly UIMessage[] => {\n if (snapshot === memo.snapshot) return memo.entries;\n memo.snapshot = snapshot;\n if (!Array.isArray(snapshot)) {\n if (memo.entries.length > 0) memo.entries = [];\n return memo.entries;\n }\n const previous = memo.entries;\n const previousById = new Map(previous.map((ui) => [ui.id, ui]));\n let same = snapshot.length === previous.length;\n const entries = (snapshot as UIMessage[]).map((ui, index) => {\n const before = previousById.get(ui.id);\n const entry =\n before !== undefined && isJSONValueEqual(before, ui) ? before : ui;\n if (entry !== previous[index]) same = false;\n return entry;\n });\n if (!same) memo.entries = entries;\n return memo.entries;\n};\n\n/**\n * Merges live-streamed UI with the state snapshot. The snapshot is\n * authoritative by id: once a UI lands in graph state it supersedes its live\n * copy. A consequence is that a live `remove-ui` is overridden while the\n * snapshot still contains that id; the removal only takes visible effect once\n * the snapshot catches up.\n */\nexport const mergeUIMessages = (\n live: readonly UIMessage[],\n snapshot: unknown,\n): UIMessage[] => {\n const byId = new Map<string, UIMessage>();\n for (const ui of live) byId.set(ui.id, ui);\n if (Array.isArray(snapshot)) {\n for (const ui of snapshot as UIMessage[]) byId.set(ui.id, ui);\n }\n return [...byId.values()];\n};\n"],"mappings":"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"}
@@ -1,12 +1,11 @@
1
- /// <reference types="@assistant-ui/core/store" />
2
- import { groupUIMessagesByParent } from "./converter.js";
3
- import { UseStreamRuntimeOptions } from "./types.js";
4
- import { AppendMessage } from "@assistant-ui/core";
5
- //#region src/useStreamRuntime.d.ts
1
+ /// <reference types="@assistant-ui/core/store" preserve="true" />
2
+ import type { AppendMessage } from "@assistant-ui/core";
3
+ import type { UseStreamRuntimeOptions } from "./types.js";
4
+ export { groupUIMessagesByParent } from "./converter.js";
6
5
  export declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runConfig"]) => {
7
- config: {
8
- configurable: Record<string, unknown>;
9
- };
6
+ config: {
7
+ configurable: Record<string, unknown>;
8
+ };
10
9
  } | undefined;
11
10
  /**
12
11
  * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.
@@ -33,6 +32,4 @@ export declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runCon
33
32
  * ```
34
33
  */
35
34
  export declare const useStreamRuntime: (rawOptions: UseStreamRuntimeOptions) => import("@assistant-ui/core").AssistantRuntime;
36
- //#endregion
37
- export { groupUIMessagesByParent };
38
35
  //# sourceMappingURL=useStreamRuntime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;qBA4Da,2BACX,WAAW;;kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAukBA,mBAAoB,YAAY,yDAAuB"}
1
+ {"version":3,"file":"useStreamRuntime.d.ts","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":";AAWA,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAqB7E,OAAO,KAAK,EAIV,uBAAuB,EACxB,mBAAgB;AAEjB,OAAO,EAAE,uBAAuB,EAAE,uBAAoB;AAwBtD,eAAO,MAAM,wBAAwB,GACnC,WAAW,aAAa,CAAC,WAAW,CAAC;;;;aAIxB,CAAC;AAgsBhB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,GAAI,YAAY,uBAAuB,kDA8BnE,CAAC"}
@@ -1,15 +1,15 @@
1
1
  "use client";
2
2
  import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTranscripts.js";
3
- import { getMessageContent, groupUIMessagesByParent } from "./converter.js";
3
+ import { getMessageContent, getMessageModality, groupUIMessagesByParent } from "./converter.js";
4
4
  import { convertLangChainBaseMessage, getMessageType } from "./convertMessages.js";
5
5
  import { langChainExtras } from "./runtimeExtras.js";
6
6
  import { useLangChainStreamingTiming } from "./streamingTiming.js";
7
- import { UI_CUSTOM_CHANNELS, createUIFoldMemo, foldUIUpdates, mergeUIMessages } from "./uiMessages.js";
7
+ import { UI_CUSTOM_CHANNELS, createUIFoldMemo, createUISnapshotMemo, foldUIUpdates, mergeUIMessages, reconcileUISnapshot } from "./uiMessages.js";
8
8
  import { useSubagentTranscripts } from "./useSubagentTranscripts.js";
9
9
  import { resolveForkCheckpoint } from "./resolveForkCheckpoint.js";
10
10
  import { LANGCHAIN_SDK } from "./sdkIdentity.js";
11
11
  import { generateId, getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
12
- import { createCloudThreadListAdapterCreateFallback, createToolCallCancellationStub, scanPendingToolCalls } from "@assistant-ui/core/internal";
12
+ import { createCloudThreadListAdapterCreateFallback, createToolCallCancellationStub, getThreadMessageText, scanPendingToolCalls } from "@assistant-ui/core/internal";
13
13
  import { useAui, useAuiState } from "@assistant-ui/store";
14
14
  import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
15
15
  import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react";
@@ -26,6 +26,12 @@ const toStagedHumanMessage = (msg, id = generateId()) => ({
26
26
  _getType: () => "human",
27
27
  content: getMessageContent(msg)
28
28
  });
29
+ const toStagedMessageInput = (message) => ({
30
+ id: message.id,
31
+ type: getMessageType(message) === "ai" ? "ai" : "human",
32
+ content: message.content,
33
+ ...message.additional_kwargs && { additional_kwargs: message.additional_kwargs }
34
+ });
29
35
  const humanContentText = (content) => {
30
36
  if (typeof content === "string") return content;
31
37
  if (!Array.isArray(content)) return "";
@@ -51,7 +57,8 @@ const useStreamThreadRuntime = (options) => {
51
57
  const [toolStatuses, setToolStatuses] = useState({});
52
58
  const hasExecutingTools = Object.values(toolStatuses).some((s) => s?.type === "executing");
53
59
  const effectiveIsRunning = stream.isLoading || hasExecutingTools;
54
- const uiStateValue = stream.values[uiStateKey];
60
+ const [uiSnapshotMemo] = useState(createUISnapshotMemo);
61
+ const uiStateValue = reconcileUISnapshot(stream.values[uiStateKey], uiSnapshotMemo);
55
62
  const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);
56
63
  const [uiFoldMemo] = useState(createUIFoldMemo);
57
64
  const liveUiMessages = useMemo(() => foldUIUpdates(customEvents, uiFoldMemo), [customEvents, uiFoldMemo]);
@@ -156,15 +163,75 @@ const useStreamThreadRuntime = (options) => {
156
163
  setStagedMessages(nextMessages);
157
164
  }, [stream.messages]);
158
165
  const getStagedRun = (parentId) => {
159
- if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;
166
+ const parent = parentId ? stagedMessagesRef.current.get(parentId) : void 0;
167
+ if (!parent || parent.transcriptStatus === "sent") return null;
160
168
  const staged = [];
161
169
  for (const message of visibleMessagesRef.current) {
162
- if (message.id && stagedMessagesRef.current.has(message.id)) staged.push(stagedMessagesRef.current.get(message.id).message);
170
+ const entry = message.id ? stagedMessagesRef.current.get(message.id) : void 0;
171
+ if (entry && entry.transcriptStatus !== "sent") staged.push(entry.message);
163
172
  if (message.id === parentId) break;
164
173
  }
165
174
  return {
166
175
  messages: staged,
167
- runConfig: stagedMessagesRef.current.get(parentId).runConfig
176
+ runConfig: parent.runConfig
177
+ };
178
+ };
179
+ const appendVoiceTranscript = (message) => {
180
+ const transcript = {
181
+ id: message.id,
182
+ _getType: () => message.role === "assistant" ? "ai" : "human",
183
+ content: getThreadMessageText(message),
184
+ ...message.metadata.modality && { additional_kwargs: { modality: message.metadata.modality } }
185
+ };
186
+ stagedMessagesRef.current.set(transcript.id, {
187
+ message: transcript,
188
+ runConfig: void 0,
189
+ reconcileOnEcho: false,
190
+ baseMessageCount: streamRef.current.messages.length,
191
+ transcriptStatus: "unsent"
192
+ });
193
+ const nextMessages = [...visibleMessagesRef.current, transcript];
194
+ visibleMessagesRef.current = nextMessages;
195
+ setStagedMessages(nextMessages);
196
+ };
197
+ const getUnsentTranscripts = () => visibleMessagesRef.current.filter((message) => message.id !== void 0 && stagedMessagesRef.current.get(message.id)?.transcriptStatus === "unsent");
198
+ const setTranscriptStatus = (messages, status) => {
199
+ for (const message of messages) {
200
+ const staged = message.id ? stagedMessagesRef.current.get(message.id) : void 0;
201
+ if (staged?.transcriptStatus) staged.transcriptStatus = status;
202
+ }
203
+ };
204
+ const submitCarryingTranscripts = async (transcripts, submit) => {
205
+ setTranscriptStatus(transcripts, "sent");
206
+ try {
207
+ await submit();
208
+ } catch (error) {
209
+ setTranscriptStatus(transcripts, "unsent");
210
+ throw error;
211
+ }
212
+ };
213
+ const dropTranscripts = (messages) => {
214
+ for (const message of messages) if (message.id && stagedMessagesRef.current.get(message.id)?.transcriptStatus) removeStagedMessage(message.id);
215
+ };
216
+ const isTranscriptMessage = (message) => {
217
+ if ((message.id ? stagedMessagesRef.current.get(message.id) : void 0)?.transcriptStatus === "unsent") return true;
218
+ const type = getMessageType(message);
219
+ return (type === "human" || type === "ai") && getMessageModality(message.additional_kwargs) !== void 0;
220
+ };
221
+ const planForkTranscripts = (parentId) => {
222
+ const visible = visibleMessagesRef.current;
223
+ const parentIndex = parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);
224
+ if (parentId != null && parentIndex === -1) return {
225
+ forkParentId: parentId,
226
+ transcripts: [],
227
+ truncated: []
228
+ };
229
+ let start = parentIndex + 1;
230
+ while (start > 0 && isTranscriptMessage(visible[start - 1])) start--;
231
+ return {
232
+ forkParentId: start > 0 ? visible[start - 1].id ?? null : null,
233
+ transcripts: visible.slice(start, parentIndex + 1),
234
+ truncated: visible.slice(parentIndex + 1)
168
235
  };
169
236
  };
170
237
  const stageUserMessage = (msg, reconcileOnEcho = false) => {
@@ -227,22 +294,28 @@ const useStreamThreadRuntime = (options) => {
227
294
  const stagedMessageId = stageUserMessage(msg, true).id;
228
295
  setActiveRunConfig(msg.runConfig);
229
296
  const content = getMessageContent(msg);
230
- const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map(createToolCallCancellationStub) : [];
297
+ const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).filter((toolCall) => toolCall.id).map(createToolCallCancellationStub) : [];
231
298
  try {
232
299
  const { externalId } = await aui.threadListItem.initialize();
233
- await streamRef.current.submit({ [messagesKey]: [...cancellations, {
234
- id: stagedMessageId,
235
- type: "human",
236
- content
237
- }] }, {
300
+ const transcripts = getUnsentTranscripts();
301
+ await submitCarryingTranscripts(transcripts, () => streamRef.current.submit({ [messagesKey]: [
302
+ ...cancellations,
303
+ ...transcripts.map(toStagedMessageInput),
304
+ {
305
+ id: stagedMessageId,
306
+ type: "human",
307
+ content
308
+ }
309
+ ] }, {
238
310
  ...runConfigToSubmitOptions(msg.runConfig),
239
311
  ...externalId != null ? { threadId: externalId } : {}
240
- });
312
+ }));
241
313
  } catch (error) {
242
314
  removeStagedMessage(stagedMessageId);
243
315
  throw error;
244
316
  }
245
317
  },
318
+ onVoiceTranscript: appendVoiceTranscript,
246
319
  onAddToolResult: async ({ messageId, toolCallId, toolName, result, isError, artifact }) => {
247
320
  const runConfig = runConfigByMessageIdRef.current.has(messageId) ? runConfigByMessageIdRef.current.get(messageId) : activeRunConfigRef.current;
248
321
  await stream.submit({ [messagesKey]: [{
@@ -257,9 +330,10 @@ const useStreamThreadRuntime = (options) => {
257
330
  onReload: async (parentId, config) => {
258
331
  const stagedRun = getStagedRun(parentId);
259
332
  if (stagedRun) {
333
+ if (config.sourceId && stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus) removeStagedMessage(config.sourceId);
260
334
  const promotedIds = /* @__PURE__ */ new Set();
261
335
  for (const message of stagedRun.messages) {
262
- if (!message.id) continue;
336
+ if (!message.id || stagedMessagesRef.current.get(message.id)?.transcriptStatus) continue;
263
337
  promotedIds.add(message.id);
264
338
  stagedMessagesRef.current.delete(message.id);
265
339
  }
@@ -271,23 +345,21 @@ const useStreamThreadRuntime = (options) => {
271
345
  } else setStagedMessages(null);
272
346
  const runConfig = config.runConfig ?? stagedRun.runConfig;
273
347
  setActiveRunConfig(runConfig);
274
- await stream.submit({ [messagesKey]: stagedRun.messages.map((message) => ({
275
- id: message.id,
276
- type: "human",
277
- content: message.content
278
- })) }, runConfigToSubmitOptions(runConfig));
348
+ await submitCarryingTranscripts(stagedRun.messages, () => stream.submit({ [messagesKey]: stagedRun.messages.map(toStagedMessageInput) }, runConfigToSubmitOptions(runConfig)));
279
349
  return;
280
350
  }
281
351
  const threadId = externalId;
282
352
  if (!threadId || parentId == null) return;
283
353
  const s = streamRef.current;
284
- const checkpointId = await resolveForkCheckpoint(s.client, threadId, s.messages, parentId, config.sourceId, s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(), messagesKey);
354
+ const fork = planForkTranscripts(parentId);
355
+ const checkpointId = await resolveForkCheckpoint(s.client, threadId, s.messages, fork.forkParentId, config.sourceId, s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(), messagesKey);
285
356
  if (!checkpointId) return;
357
+ dropTranscripts(fork.truncated);
286
358
  setActiveRunConfig(config.runConfig);
287
- await s.submit(null, {
359
+ await submitCarryingTranscripts(fork.transcripts, () => s.submit(fork.transcripts.length > 0 ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) } : null, {
288
360
  forkFrom: checkpointId,
289
361
  ...runConfigToSubmitOptions(config.runConfig)
290
- });
362
+ }));
291
363
  },
292
364
  onEdit: async (message) => {
293
365
  if (!(message.startRun ?? message.role === "user")) {
@@ -308,17 +380,19 @@ const useStreamThreadRuntime = (options) => {
308
380
  const threadId = externalId;
309
381
  if (!threadId) return;
310
382
  const s = streamRef.current;
311
- const checkpointId = await resolveForkCheckpoint(s.client, threadId, s.messages, message.parentId, message.sourceId, s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(), messagesKey);
383
+ const fork = planForkTranscripts(message.parentId);
384
+ const checkpointId = await resolveForkCheckpoint(s.client, threadId, s.messages, fork.forkParentId, message.sourceId, s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(), messagesKey);
312
385
  if (!checkpointId) return;
386
+ dropTranscripts(fork.truncated);
313
387
  const content = getMessageContent(message);
314
388
  setActiveRunConfig(message.runConfig);
315
- await s.submit({ [messagesKey]: [{
389
+ await submitCarryingTranscripts(fork.transcripts, () => s.submit({ [messagesKey]: [...fork.transcripts.map(toStagedMessageInput), {
316
390
  type: "human",
317
391
  content
318
392
  }] }, {
319
393
  forkFrom: checkpointId,
320
394
  ...runConfigToSubmitOptions(message.runConfig)
321
- });
395
+ }));
322
396
  },
323
397
  onCancel: unstable_allowCancellation !== false ? async () => {
324
398
  activeRunConfigRef.current = void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n foldUIUpdates,\n mergeUIMessages,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") return { toolCalls: message.tool_calls ?? [] };\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst humanContentText = (content: LangChainBaseMessage[\"content\"]) => {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .filter(\n (part): part is { type: \"text\"; text: string } =>\n typeof part === \"object\" &&\n part !== null &&\n part.type === \"text\" &&\n typeof part.text === \"string\",\n )\n .map((part) => part.text)\n .join(\"\");\n};\n\nconst hasSameMessageContent = (\n a: LangChainBaseMessage,\n b: LangChainBaseMessage,\n) => humanContentText(a.content) === humanContentText(b.content);\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [stagedMessages, setStagedMessages] = useState<\n LangChainBaseMessage[] | null\n >(null);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const uiStateValue = stream.values[uiStateKey];\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const convertWithUI = useMemo<\n useExternalMessageConverter.Callback<LangChainBaseMessage>\n >(\n () => (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertWithUI,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: LangChainBaseMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n reconcileOnEcho: boolean;\n baseMessageCount: number;\n }\n >(),\n );\n const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);\n useEffect(() => {\n if (stagedMessagesRef.current.size === 0) return;\n\n // Staged edits must keep their truncated base while stream updates arrive before promotion.\n const baseMessages =\n stagedBaseMessagesRef.current ??\n (stream.messages as LangChainBaseMessage[]);\n const remainingStagedMessages: LangChainBaseMessage[] = [];\n const matchedBaseMessageIndexes = new Set<number>();\n const visibleStagedIds = new Set(\n visibleMessagesRef.current.flatMap((m) => (m.id ? [m.id] : [])),\n );\n for (const [id, staged] of stagedMessagesRef.current) {\n if (!visibleStagedIds.has(id)) continue;\n const echoed = baseMessages.some((message, index) => {\n if (matchedBaseMessageIndexes.has(index)) return false;\n if (message.id === id) {\n matchedBaseMessageIndexes.add(index);\n return true;\n }\n if (\n !staged.reconcileOnEcho ||\n index < staged.baseMessageCount ||\n getMessageType(message) !== \"human\" ||\n !hasSameMessageContent(message, staged.message)\n ) {\n return false;\n }\n matchedBaseMessageIndexes.add(index);\n return true;\n });\n if (echoed) stagedMessagesRef.current.delete(id);\n else remainingStagedMessages.push(staged.message);\n }\n\n if (remainingStagedMessages.length === 0) {\n stagedBaseMessagesRef.current = null;\n visibleMessagesRef.current = baseMessages;\n // Reconciling against the upstream stream mutates the staged refs above,\n // which cannot happen during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setStagedMessages(null);\n return;\n }\n\n const nextMessages = [...baseMessages, ...remainingStagedMessages];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n }, [stream.messages]);\n\n const getStagedRun = (parentId: string | null) => {\n if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n if (message.id && stagedMessagesRef.current.has(message.id)) {\n staged.push(stagedMessagesRef.current.get(message.id)!.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n });\n const nextMessages = [...visibleMessagesRef.current, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!stagedMessagesRef.current.delete(id)) return;\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => message.id !== id,\n );\n visibleMessagesRef.current = nextMessages;\n if (stagedMessagesRef.current.size === 0) {\n stagedBaseMessagesRef.current = null;\n setStagedMessages(null);\n } else {\n setStagedMessages(nextMessages);\n }\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n ).map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n await streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (!message.id) continue;\n promotedIds.add(message.id);\n stagedMessagesRef.current.delete(message.id);\n }\n stagedBaseMessagesRef.current = null;\n if (stagedMessagesRef.current.size > 0) {\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => !message.id || !promotedIds.has(message.id),\n );\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n } else {\n setStagedMessages(null);\n }\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await stream.submit(\n {\n [messagesKey]: stagedRun.messages.map((message) => ({\n id: message.id,\n type: \"human\",\n content: message.content,\n })),\n },\n runConfigToSubmitOptions(runConfig),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n parentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n setActiveRunConfig(config.runConfig);\n await s.submit(null, {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n });\n },\n onEdit: async (message) => {\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n });\n stagedBaseMessagesRef.current = truncated;\n const nextMessages = [...truncated, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n message.parentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await s.submit(\n { [messagesKey]: [{ type: \"human\", content }] },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n );\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA4DA,MAAa,4BACX,cAEA,WAAW,SACP,EAAE,QAAQ,EAAE,cAAc,UAAU,OAAO,EAAE,IAC7C,KAAA;AAMN,MAAM,uBACJ,aAEA,qBACE,WACC,YAAY;CACX,MAAM,OAAO,eAAe,OAAO;CACnC,IAAI,SAAS,MAAM,OAAO,EAAE,WAAW,QAAQ,cAAc,CAAC,EAAE;CAChE,IAAI,SAAS,UAAU,QAAQ,cAC7B,OAAO,EAAE,YAAY,QAAQ,aAAa;AAG9C,IACC,aAAa,SAAS,EACzB;AAEF,MAAM,wBACJ,KACA,KAAK,WAAW,OAC2B;CAC3C;CACA,gBAAgB;CAChB,SAAS,kBAAkB,GAAG;AAChC;AAEA,MAAM,oBAAoB,YAA6C;CACrE,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,OAAO,QACJ,QACE,SACC,OAAO,SAAS,YAChB,SAAS,QACT,KAAK,SAAS,UACd,OAAO,KAAK,SAAS,QACzB,CAAC,CACA,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AACZ;AAEA,MAAM,yBACJ,GACA,MACG,iBAAiB,EAAE,OAAO,MAAM,iBAAiB,EAAE,OAAO;AAE/D,MAAM,iCACJ,gBACA,aAC2B;CAC3B,IAAI,aAAa,MAAM,OAAO,CAAC;CAC/B,MAAM,cAAc,eAAe,WAAW,MAAM,EAAE,OAAO,QAAQ;CACrE,IAAI,gBAAgB,IAAI,OAAO,CAAC;CAChC,MAAM,YAAoC,CAAC;CAC3C,KAAK,IAAI,IAAI,GAAG,KAAK,eAAe,IAAI,eAAe,QAAQ,KAC7D,UAAU,KACR,GAAG,yBAA+C,eAAe,EAAG,CACtE;CAEF,OAAO;AACT;AAMA,MAAM,0BACJ,YAIG;CACH,MAAM,EAAE,UAAU,4BAA4B,+BAC5C;CACF,MAAM,MAAM,OAAO;CACnB,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,aAAa,QAAQ,cAAc;CAEzC,MAAM,aAAa,aAAa,MAAM,EAAE,eAAe,UAAU;CAKjE,MAAM,SAAS,UACb,OAAO,OAAO,CAAC,GAAG,SAAS,EAAE,UAAU,WAAW,CAAC,CACrD;CACA,MAAM,CAAC,gBAAgB,qBAAqB,SAE1C,IAAI;CAEN,MAAM,CAAC,cAAc,mBAAmB,SAEtC,CAAC,CAAC;CACJ,MAAM,oBAAoB,OAAO,OAAO,YAAY,CAAC,CAAC,MACnD,MAAM,GAAG,SAAS,WACrB;CACA,MAAM,qBAAqB,OAAO,aAAa;CAE/C,MAAM,eAAe,OAAO,OAAO;CAEnC,MAAM,eAAe,WAAW,QAAQ,kBAAkB;CAC1D,MAAM,CAAC,cAAc,SAAS,gBAAgB;CAC9C,MAAM,iBAAiB,cACf,cAAc,cAAc,UAAU,GAC5C,CAAC,cAAc,UAAU,CAC3B;CAEA,MAAM,mBAAmB,cACjB,gBAAgB,gBAAgB,YAAY,GAClD,CAAC,gBAAgB,YAAY,CAC/B;CAEA,MAAM,qBAAqB,cACnB,wBAAmC,gBAAgB,GACzD,CAAC,gBAAgB,CACnB;CAEA,MAAM,kBACJ,kBAAmB,OAAO;CAE5B,MAAM,gBAAgB,4BACpB,iBACA,kBACF;CAEA,MAAM,sBAAsB,uBAC1B,QACA,kBACF;CAEA,MAAM,gBAAgB,eAGb,SAAS,aACd,4BAA4B,SAAS;EACnC,GAAG;EACH;EACA;CACF,CAAC,GACH,CAAC,oBAAoB,aAAa,CACpC;CAEA,MAAM,iBAAiB,4BAA4B;EACjD,UAAU;EACV,UAAU;EACV,WAAW;CACb,CAAC;CACD,MAAM,CAAC,QAAQ,SAAS,gBAAgB;CACxC,MAAM,0BAA0B,cACxB,0BAA0B,gBAAgB,qBAAqB,IAAI,GACzE;EAAC;EAAgB;EAAqB;CAAI,CAC5C;CAEA,MAAM,YAAY,OAAO,MAAM;CAC/B,yBAAyB;EACvB,UAAU,UAAU;CACtB,GAAG,CAAC,MAAM,CAAC;CAEX,MAAM,qBAAqB,OAEzB,KAAA,CAAS;CACX,MAAM,0BAA0B,uBAC9B,IAAI,IAA8D,CACpE;CACA,MAAM,oBAAoB,OAAO,UAAU;CAC3C,MAAM,qBAAqB,aACxB,cAA0C;EACzC,mBAAmB,UAAU,yBAAyB,SAAS,CAAC,EAAE;CACpE,GACA,CAAC,CACH;CACA,MAAM,sBAAsB,aACzB,kBAA4C;EAC3C,IAAI,iBAAiB,YAAY,eAAe,OAAO;EACvD,IAAI,mBAAmB,YAAY,KAAA,GAAW,OAAO;EACrD,OAAO;GAAE,GAAG;GAAe,QAAQ,mBAAmB;EAAQ;CAChE,GACA,CAAC,CACH;CAEA,gBAAgB;EACd,IACE,kBAAkB,YAAY,QAC9B,kBAAkB,YAAY,YAC9B;GACA,mBAAmB,UAAU,KAAA;GAC7B,wBAAwB,QAAQ,MAAM;EACxC;EACA,kBAAkB,UAAU;CAC9B,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,MAAM,WAAW,OAAO;EACxB,MAAM,QAAQ,wBAAwB;EACtC,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;GAC7C,MAAM,UAAU,SAAS,GAAG,CAAC;GAC7B,IACE,CAAC,SAAS,MACV,eAAe,OAAO,MAAM,QAC5B,CAAC,QAAQ,YAAY,QAErB;GAEF,IAAI,MAAM,IAAI,QAAQ,EAAE,GAAG;GAC3B;EACF;EACA,KAAK,MAAM,WAAW,UAAU;GAC9B,IACE,CAAC,QAAQ,MACT,eAAe,OAAO,MAAM,QAC5B,CAAC,QAAQ,YAAY,UACrB,MAAM,IAAI,QAAQ,EAAE,GAEpB;GAEF,MAAM,IAAI,QAAQ,IAAI,mBAAmB,OAAO;EAClD;CACF,GAAG,CAAC,OAAO,QAAQ,CAAC;CAEpB,MAAM,qBAAqB,OAAO,eAAe;CACjD,yBAAyB;EACvB,mBAAmB,UAAU;CAC/B,GAAG,CAAC,eAAe,CAAC;CAEpB,MAAM,oBAAoB,OAAO,uBAAuB;CACxD,yBAAyB;EACvB,kBAAkB,UAAU;CAC9B,GAAG,CAAC,uBAAuB,CAAC;CAE5B,MAAM,oBAAoB,uBACxB,IAAI,IAQF,CACJ;CACA,MAAM,wBAAwB,OAAsC,IAAI;CACxE,gBAAgB;EACd,IAAI,kBAAkB,QAAQ,SAAS,GAAG;EAG1C,MAAM,eACJ,sBAAsB,WACrB,OAAO;EACV,MAAM,0BAAkD,CAAC;EACzD,MAAM,4CAA4B,IAAI,IAAY;EAClD,MAAM,mBAAmB,IAAI,IAC3B,mBAAmB,QAAQ,SAAS,MAAO,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAE,CAChE;EACA,KAAK,MAAM,CAAC,IAAI,WAAW,kBAAkB,SAAS;GACpD,IAAI,CAAC,iBAAiB,IAAI,EAAE,GAAG;GAkB/B,IAjBe,aAAa,MAAM,SAAS,UAAU;IACnD,IAAI,0BAA0B,IAAI,KAAK,GAAG,OAAO;IACjD,IAAI,QAAQ,OAAO,IAAI;KACrB,0BAA0B,IAAI,KAAK;KACnC,OAAO;IACT;IACA,IACE,CAAC,OAAO,mBACR,QAAQ,OAAO,oBACf,eAAe,OAAO,MAAM,WAC5B,CAAC,sBAAsB,SAAS,OAAO,OAAO,GAE9C,OAAO;IAET,0BAA0B,IAAI,KAAK;IACnC,OAAO;GACT,CACS,GAAG,kBAAkB,QAAQ,OAAO,EAAE;QAC1C,wBAAwB,KAAK,OAAO,OAAO;EAClD;EAEA,IAAI,wBAAwB,WAAW,GAAG;GACxC,sBAAsB,UAAU;GAChC,mBAAmB,UAAU;GAI7B,kBAAkB,IAAI;GACtB;EACF;EAEA,MAAM,eAAe,CAAC,GAAG,cAAc,GAAG,uBAAuB;EACjE,mBAAmB,UAAU;EAC7B,kBAAkB,YAAY;CAChC,GAAG,CAAC,OAAO,QAAQ,CAAC;CAEpB,MAAM,gBAAgB,aAA4B;EAChD,IAAI,CAAC,YAAY,CAAC,kBAAkB,QAAQ,IAAI,QAAQ,GAAG,OAAO;EAElE,MAAM,SAAiC,CAAC;EACxC,KAAK,MAAM,WAAW,mBAAmB,SAAS;GAChD,IAAI,QAAQ,MAAM,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,GACxD,OAAO,KAAK,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,CAAC,CAAE,OAAO;GAEhE,IAAI,QAAQ,OAAO,UAAU;EAC/B;EAEA,OAAO;GACL,UAAU;GACV,WAAW,kBAAkB,QAAQ,IAAI,QAAQ,CAAC,CAAE;EACtD;CACF;CAEA,MAAM,oBAAoB,KAAoB,kBAAkB,UAAU;EACxE,MAAM,gBAAgB,qBAAqB,GAAG;EAC9C,kBAAkB,QAAQ,IAAI,cAAc,IAAI;GAC9C,SAAS;GACT,WAAW,IAAI;GACf;GACA,kBAAkB,UAAU,QAAQ,SAAS;EAC/C,CAAC;EACD,MAAM,eAAe,CAAC,GAAG,mBAAmB,SAAS,aAAa;EAClE,mBAAmB,UAAU;EAC7B,kBAAkB,YAAY;EAC9B,OAAO;CACT;CAEA,MAAM,uBAAuB,OAAe;EAC1C,IAAI,CAAC,kBAAkB,QAAQ,OAAO,EAAE,GAAG;EAC3C,MAAM,eAAe,mBAAmB,QAAQ,QAC7C,YAAY,QAAQ,OAAO,EAC9B;EACA,mBAAmB,UAAU;EAC7B,IAAI,kBAAkB,QAAQ,SAAS,GAAG;GACxC,sBAAsB,UAAU;GAChC,kBAAkB,IAAI;EACxB,OACE,kBAAkB,YAAY;CAElC;CAEA,MAAM,SAAS,cAEX,gBAAgB,QAAQ;EACtB,WAAW,OAAO;EAClB,YAAY,OAAO;EACnB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB;EACA,OAAO,OAAO;EACd,SAAS,QAAQ,kBAAkB;GACjC,MAAM,WAAW,UAAU,QAAQ,eAAe,WAAW;GAC7D,OAAO,OAAO,OACZ,QACA,WAAW,oBAAoB,aAAa,IAAI,aAClD;EACF;EACA,UAAU,UAAU,mBAClB,OAAO,QAAQ,UAAU,oBAAoB,cAAc,CAAC;EAC9D,aAAa,eAAe,mBAC1B,OAAO,WAAW,eAAe,oBAAoB,cAAc,CAAC;EACtE,QAAQ,OAAO;EACf;CACF,CAAC,GACH;EAAC;EAAQ;EAAa;CAAmB,CAC3C;CA0LA,OAxLgB,wBAAwB;EACtC,GAAG,+BAA+B,OAAO;EACzC,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,UAAU;EACV;EACA;EACA,gCAAgC;EAChC;EACA,OAAO,OAAO,QAAQ;GACpB,IAAI,EAAE,IAAI,YAAY,IAAI,SAAS,SAAS;IAC1C,iBAAiB,GAAG;IACpB;GACF;GAGA,MAAM,kBADgB,iBAAiB,KAAK,IACR,CAAC,CAAC;GACtC,mBAAmB,IAAI,SAAS;GAChC,MAAM,UAAU,kBAAkB,GAAG;GACrC,MAAM,gBACJ,+BAA+B,QAC3B,oBACE,UAAU,QAAQ,QACpB,CAAC,CAAC,IAAI,8BAA8B,IACpC,CAAC;GAKP,IAAI;IACF,MAAM,EAAE,eAAe,MAAM,IAAI,eAAe,WAAW;IAC3D,MAAM,UAAU,QAAQ,OACtB,GACG,cAAc,CACb,GAAG,eACH;KACE,IAAI;KACJ,MAAM;KACN;IACF,CACF,EACF,GACA;KACE,GAAG,yBAAyB,IAAI,SAAS;KACzC,GAAI,cAAc,OAAO,EAAE,UAAU,WAAW,IAAI,CAAC;IACvD,CACF;GACF,SAAS,OAAO;IACd,oBAAoB,eAAe;IACnC,MAAM;GACR;EACF;EACA,iBAAiB,OAAO,EACtB,WACA,YACA,UACA,QACA,SACA,eACI;GACJ,MAAM,YAAY,wBAAwB,QAAQ,IAAI,SAAS,IAC3D,wBAAwB,QAAQ,IAAI,SAAS,IAC7C,mBAAmB;GACvB,MAAM,OAAO,OACX,GACG,cAAc,CACb;IACE,MAAM;IACN,MAAM;IACN,cAAc;IACd,SAAS,KAAK,UAAU,MAAM;IAC9B,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IACzC,QAAQ,UAAU,UAAU;GAC9B,CACF,EACF,GACA,cAAc,KAAA,IAAY,KAAA,IAAY,EAAE,QAAQ,UAAU,CAC5D;EACF;EACA,UAAU,OAAO,UAAU,WAAW;GACpC,MAAM,YAAY,aAAa,QAAQ;GACvC,IAAI,WAAW;IACb,MAAM,8BAAc,IAAI,IAAY;IACpC,KAAK,MAAM,WAAW,UAAU,UAAU;KACxC,IAAI,CAAC,QAAQ,IAAI;KACjB,YAAY,IAAI,QAAQ,EAAE;KAC1B,kBAAkB,QAAQ,OAAO,QAAQ,EAAE;IAC7C;IACA,sBAAsB,UAAU;IAChC,IAAI,kBAAkB,QAAQ,OAAO,GAAG;KACtC,MAAM,eAAe,mBAAmB,QAAQ,QAC7C,YAAY,CAAC,QAAQ,MAAM,CAAC,YAAY,IAAI,QAAQ,EAAE,CACzD;KACA,mBAAmB,UAAU;KAC7B,kBAAkB,YAAY;IAChC,OACE,kBAAkB,IAAI;IAExB,MAAM,YAAY,OAAO,aAAa,UAAU;IAChD,mBAAmB,SAAS;IAC5B,MAAM,OAAO,OACX,GACG,cAAc,UAAU,SAAS,KAAK,aAAa;KAClD,IAAI,QAAQ;KACZ,MAAM;KACN,SAAS,QAAQ;IACnB,EAAE,EACJ,GACA,yBAAyB,SAAS,CACpC;IACA;GACF;GAEA,MAAM,WAAW;GACjB,IAAI,CAAC,YAAY,YAAY,MAAM;GACnC,MAAM,IAAI,UAAU;GACpB,MAAM,eAAe,MAAM,sBACzB,EAAE,QACF,UACA,EAAE,UACF,UACA,OAAO,UACP,EAAE,kBAAkB,EAAE,sBAAsB,cAAc,GAC1D,WACF;GACA,IAAI,CAAC,cAAc;GACnB,mBAAmB,OAAO,SAAS;GACnC,MAAM,EAAE,OAAO,MAAM;IACnB,UAAU;IACV,GAAG,yBAAyB,OAAO,SAAS;GAC9C,CAAC;EACH;EACA,QAAQ,OAAO,YAAY;GACzB,IAAI,EAAE,QAAQ,YAAY,QAAQ,SAAS,SAAS;IAClD,MAAM,YAAY,8BAChB,kBAAkB,SAClB,QAAQ,QACV;IACA,MAAM,gBAAgB,qBAAqB,OAAO;IAClD,kBAAkB,QAAQ,IAAI,cAAc,IAAI;KAC9C,SAAS;KACT,WAAW,QAAQ;KACnB,iBAAiB;KACjB,kBAAkB;IACpB,CAAC;IACD,sBAAsB,UAAU;IAChC,MAAM,eAAe,CAAC,GAAG,WAAW,aAAa;IACjD,mBAAmB,UAAU;IAC7B,kBAAkB,YAAY;IAC9B;GACF;GAEA,MAAM,WAAW;GACjB,IAAI,CAAC,UAAU;GACf,MAAM,IAAI,UAAU;GACpB,MAAM,eAAe,MAAM,sBACzB,EAAE,QACF,UACA,EAAE,UACF,QAAQ,UACR,QAAQ,UACR,EAAE,kBAAkB,EAAE,sBAAsB,cAAc,GAC1D,WACF;GACA,IAAI,CAAC,cAAc;GACnB,MAAM,UAAU,kBAAkB,OAAO;GACzC,mBAAmB,QAAQ,SAAS;GACpC,MAAM,EAAE,OACN,GAAG,cAAc,CAAC;IAAE,MAAM;IAAS;GAAQ,CAAC,EAAE,GAC9C;IACE,UAAU;IACV,GAAG,yBAAyB,QAAQ,SAAS;GAC/C,CACF;EACF;EACA,UACE,+BAA+B,QAC3B,YAAY;GACV,mBAAmB,UAAU,KAAA;GAC7B,MAAM,OAAO,KAAK;EACpB,IACA,KAAA;CACR,CAEa;AACf;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,oBAAoB,eAAwC;CACvE,MAAM,EACJ,OACA,4BACA,QACA,QAAQ,UACR,kBACA,GAAG,YACD;CAEJ,MAAM,MAAM,OAAO;CACnB,MAAM,eAAe,0BAA0B;EAC7C,KAAK;EACL;EACA,QAAQ,2CACN,QACA,IAAI,cACN;EACA,QAAQ;CACV,CAAC;CAGD,OAAO,2BAA2B;EAChC,aAAa,SAAS,cAAc;GAClC,OAAO,uBAAuB,OAAO;EACvC;EACA,SANc,8BAA8B;EAO5C,cAAc;EACd;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" preserve=\"true\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n getThreadMessageText,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { getMessageModality, groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n createUISnapshotMemo,\n foldUIUpdates,\n mergeUIMessages,\n reconcileUISnapshot,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") return { toolCalls: message.tool_calls ?? [] };\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst toStagedMessageInput = (message: LangChainBaseMessage) => ({\n id: message.id,\n type: getMessageType(message) === \"ai\" ? (\"ai\" as const) : (\"human\" as const),\n content: message.content,\n ...(message.additional_kwargs && {\n additional_kwargs: message.additional_kwargs,\n }),\n});\n\nconst humanContentText = (content: LangChainBaseMessage[\"content\"]) => {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .filter(\n (part): part is { type: \"text\"; text: string } =>\n typeof part === \"object\" &&\n part !== null &&\n part.type === \"text\" &&\n typeof part.text === \"string\",\n )\n .map((part) => part.text)\n .join(\"\");\n};\n\nconst hasSameMessageContent = (\n a: LangChainBaseMessage,\n b: LangChainBaseMessage,\n) => humanContentText(a.content) === humanContentText(b.content);\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [stagedMessages, setStagedMessages] = useState<\n LangChainBaseMessage[] | null\n >(null);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const [uiSnapshotMemo] = useState(createUISnapshotMemo);\n const uiStateValue = reconcileUISnapshot(\n stream.values[uiStateKey],\n uiSnapshotMemo,\n );\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const convertWithUI = useMemo<\n useExternalMessageConverter.Callback<LangChainBaseMessage>\n >(\n () => (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertWithUI,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: LangChainBaseMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n reconcileOnEcho: boolean;\n baseMessageCount: number;\n transcriptStatus?: \"unsent\" | \"sent\";\n }\n >(),\n );\n const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);\n useEffect(() => {\n if (stagedMessagesRef.current.size === 0) return;\n\n // Staged edits must keep their truncated base while stream updates arrive before promotion.\n const baseMessages =\n stagedBaseMessagesRef.current ??\n (stream.messages as LangChainBaseMessage[]);\n const remainingStagedMessages: LangChainBaseMessage[] = [];\n const matchedBaseMessageIndexes = new Set<number>();\n const visibleStagedIds = new Set(\n visibleMessagesRef.current.flatMap((m) => (m.id ? [m.id] : [])),\n );\n for (const [id, staged] of stagedMessagesRef.current) {\n if (!visibleStagedIds.has(id)) continue;\n const echoed = baseMessages.some((message, index) => {\n if (matchedBaseMessageIndexes.has(index)) return false;\n if (message.id === id) {\n matchedBaseMessageIndexes.add(index);\n return true;\n }\n if (\n !staged.reconcileOnEcho ||\n index < staged.baseMessageCount ||\n getMessageType(message) !== \"human\" ||\n !hasSameMessageContent(message, staged.message)\n ) {\n return false;\n }\n matchedBaseMessageIndexes.add(index);\n return true;\n });\n if (echoed) stagedMessagesRef.current.delete(id);\n else remainingStagedMessages.push(staged.message);\n }\n\n if (remainingStagedMessages.length === 0) {\n stagedBaseMessagesRef.current = null;\n visibleMessagesRef.current = baseMessages;\n // Reconciling against the upstream stream mutates the staged refs above,\n // which cannot happen during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setStagedMessages(null);\n return;\n }\n\n const nextMessages = [...baseMessages, ...remainingStagedMessages];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n }, [stream.messages]);\n\n const getStagedRun = (parentId: string | null) => {\n const parent = parentId\n ? stagedMessagesRef.current.get(parentId)\n : undefined;\n if (!parent || parent.transcriptStatus === \"sent\") return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n const entry = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (entry && entry.transcriptStatus !== \"sent\") {\n staged.push(entry.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: parent.runConfig,\n };\n };\n\n const appendVoiceTranscript = (message: ThreadMessage) => {\n const transcript = {\n id: message.id,\n _getType: () => (message.role === \"assistant\" ? \"ai\" : \"human\"),\n content: getThreadMessageText(message),\n ...(message.metadata.modality && {\n additional_kwargs: { modality: message.metadata.modality },\n }),\n };\n stagedMessagesRef.current.set(transcript.id, {\n message: transcript,\n runConfig: undefined,\n reconcileOnEcho: false,\n baseMessageCount: streamRef.current.messages.length,\n transcriptStatus: \"unsent\",\n });\n const nextMessages = [...visibleMessagesRef.current, transcript];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const getUnsentTranscripts = () =>\n visibleMessagesRef.current.filter(\n (message) =>\n message.id !== undefined &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus ===\n \"unsent\",\n );\n\n const setTranscriptStatus = (\n messages: readonly LangChainBaseMessage[],\n status: \"unsent\" | \"sent\",\n ) => {\n for (const message of messages) {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus) staged.transcriptStatus = status;\n }\n };\n\n // Reserving before the submit keeps an overlapping submit from carrying the\n // same transcript; a failed submit hands it back to the next run.\n const submitCarryingTranscripts = async (\n transcripts: readonly LangChainBaseMessage[],\n submit: () => Promise<void>,\n ) => {\n setTranscriptStatus(transcripts, \"sent\");\n try {\n await submit();\n } catch (error) {\n setTranscriptStatus(transcripts, \"unsent\");\n throw error;\n }\n };\n\n const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {\n for (const message of messages) {\n if (\n message.id &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n removeStagedMessage(message.id);\n }\n };\n\n const isTranscriptMessage = (message: LangChainBaseMessage) => {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus === \"unsent\") return true;\n const type = getMessageType(message);\n return (\n (type === \"human\" || type === \"ai\") &&\n getMessageModality(message.additional_kwargs) !== undefined\n );\n };\n\n // A transcript reaches the graph in the same input as the message after it, so\n // no checkpoint ends at one; a fork starts before the trailing transcripts.\n const planForkTranscripts = (parentId: string | null) => {\n const visible = visibleMessagesRef.current;\n const parentIndex =\n parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);\n if (parentId != null && parentIndex === -1)\n return { forkParentId: parentId, transcripts: [], truncated: [] };\n\n let start = parentIndex + 1;\n while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;\n return {\n forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,\n transcripts: visible.slice(start, parentIndex + 1),\n truncated: visible.slice(parentIndex + 1),\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n });\n const nextMessages = [...visibleMessagesRef.current, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!stagedMessagesRef.current.delete(id)) return;\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => message.id !== id,\n );\n visibleMessagesRef.current = nextMessages;\n if (stagedMessagesRef.current.size === 0) {\n stagedBaseMessagesRef.current = null;\n setStagedMessages(null);\n } else {\n setStagedMessages(nextMessages);\n }\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n )\n // LangChain rejects a tool message without a tool_call_id.\n .filter((toolCall) => toolCall.id)\n .map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n const transcripts = getUnsentTranscripts();\n await submitCarryingTranscripts(transcripts, () =>\n streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n ...transcripts.map(toStagedMessageInput),\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n ),\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onVoiceTranscript: appendVoiceTranscript,\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n if (\n config.sourceId &&\n stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus\n )\n removeStagedMessage(config.sourceId);\n const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (\n !message.id ||\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n continue;\n promotedIds.add(message.id);\n stagedMessagesRef.current.delete(message.id);\n }\n stagedBaseMessagesRef.current = null;\n if (stagedMessagesRef.current.size > 0) {\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => !message.id || !promotedIds.has(message.id),\n );\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n } else {\n setStagedMessages(null);\n }\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await submitCarryingTranscripts(stagedRun.messages, () =>\n stream.submit(\n {\n [messagesKey]: stagedRun.messages.map(toStagedMessageInput),\n },\n runConfigToSubmitOptions(runConfig),\n ),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n setActiveRunConfig(config.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n fork.transcripts.length > 0\n ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }\n : null,\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n },\n ),\n );\n },\n onEdit: async (message) => {\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n });\n stagedBaseMessagesRef.current = truncated;\n const nextMessages = [...truncated, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(message.parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n {\n [messagesKey]: [\n ...fork.transcripts.map(toStagedMessageInput),\n { type: \"human\", content },\n ],\n },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n ),\n );\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
@@ -1,8 +1,6 @@
1
- import { SubagentTranscript } from "./attachSubagentTranscripts.js";
2
- import { UIMessage } from "./types.js";
3
- import { AnyStream } from "@langchain/react";
4
- //#region src/useSubagentTranscripts.d.ts
1
+ import { type AnyStream } from "@langchain/react";
2
+ import { type SubagentTranscript } from "./attachSubagentTranscripts.js";
3
+ import type { UIMessage } from "./types.js";
5
4
  export declare const MAX_SUBAGENT_DEPTH = 16;
6
5
  export declare const useSubagentTranscripts: (stream: AnyStream, uiMessagesByParent: Map<string, UIMessage[]>) => ReadonlyMap<string, SubagentTranscript>;
7
- //#endregion
8
6
  //# sourceMappingURL=useSubagentTranscripts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSubagentTranscripts.d.ts","names":[],"sources":["../src/useSubagentTranscripts.ts"],"mappings":";;;;qBA0Ca;qBA6cA,yBACX,QAAQ,WACR,oBAAoB,YAAY,iBAC/B,oBAAoB"}
1
+ {"version":3,"file":"useSubagentTranscripts.d.ts","sourceRoot":"","sources":["../src/useSubagentTranscripts.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIrE,OAAO,EAIL,KAAK,kBAAkB,EACxB,uCAAoC;AAKrC,OAAO,KAAK,EAAwB,SAAS,EAAE,mBAAgB;AAS/D,eAAO,MAAM,kBAAkB,KAAK,CAAC;AA6crC,eAAO,MAAM,sBAAsB,GACjC,QAAQ,SAAS,EACjB,oBAAoB,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,KAC3C,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAmBxC,CAAC"}
@@ -3,12 +3,14 @@ import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTra
3
3
  import { groupUIMessagesByParent } from "./converter.js";
4
4
  import { convertLangChainBaseMessage } from "./convertMessages.js";
5
5
  import { langChainStreamingTimingAccessors } from "./streamingTiming.js";
6
+ import { subagentMessagesProjection } from "./subagentMessagesProjection.js";
6
7
  import { UI_CUSTOM_CHANNELS, createUIFoldMemo, foldUIUpdates, mergeUIMessages } from "./uiMessages.js";
7
8
  import { stepStreamingTiming } from "@assistant-ui/core";
8
9
  import { convertExternalMessages, createExternalMessageConversionCache } from "@assistant-ui/core/react";
9
10
  import { useEffect, useRef, useSyncExternalStore } from "react";
10
11
  import { STREAM_CONTROLLER } from "@langchain/react";
11
- import { channelProjection, messagesProjection } from "@langchain/langgraph-sdk/stream";
12
+ import { useReplaySafeEffect } from "@assistant-ui/store/internal";
13
+ import { channelProjection } from "@langchain/langgraph-sdk/stream";
12
14
  //#region src/useSubagentTranscripts.ts
13
15
  const MAX_SUBAGENT_DEPTH = 16;
14
16
  const ROOT_UI_CHANNEL_DEPTH = 1;
@@ -127,7 +129,7 @@ const createSubagentTranscriptSource = () => {
127
129
  }
128
130
  }
129
131
  if (source.resources.has(snapshot.id)) continue;
130
- const acquired = controller.registry.acquire(messagesProjection(snapshot.namespace));
132
+ const acquired = controller.registry.acquire(subagentMessagesProjection(snapshot.namespace));
131
133
  const acquiredUI = snapshot.namespace.length > ROOT_UI_CHANNEL_DEPTH ? controller.registry.acquire(channelProjection(UI_CUSTOM_CHANNELS, snapshot.namespace)) : void 0;
132
134
  const resource = {
133
135
  snapshot,
@@ -256,7 +258,7 @@ const useSubagentTranscripts = (stream, uiMessagesByParent) => {
256
258
  stream.subagents,
257
259
  uiMessagesByParent
258
260
  ]);
259
- useEffect(() => () => source.dispose(), [source]);
261
+ useReplaySafeEffect(() => () => source.dispose(), [source]);
260
262
  return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
261
263
  };
262
264
  //#endregion