@assistant-ui/react-langchain 0.0.15 → 0.0.17

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 (64) hide show
  1. package/dist/convertMessages.d.ts +37 -3
  2. package/dist/convertMessages.d.ts.map +1 -1
  3. package/dist/convertMessages.js +57 -11
  4. package/dist/convertMessages.js.map +1 -1
  5. package/dist/findForkCheckpointInHistory.d.ts +26 -0
  6. package/dist/findForkCheckpointInHistory.d.ts.map +1 -0
  7. package/dist/findForkCheckpointInHistory.js +34 -0
  8. package/dist/findForkCheckpointInHistory.js.map +1 -0
  9. package/dist/hooks.d.ts +88 -0
  10. package/dist/hooks.d.ts.map +1 -0
  11. package/dist/hooks.js +99 -0
  12. package/dist/hooks.js.map +1 -0
  13. package/dist/index.d.ts +6 -3
  14. package/dist/index.js +4 -2
  15. package/dist/resolveForkCheckpoint.d.ts +17 -0
  16. package/dist/resolveForkCheckpoint.d.ts.map +1 -0
  17. package/dist/resolveForkCheckpoint.js +26 -0
  18. package/dist/resolveForkCheckpoint.js.map +1 -0
  19. package/dist/runtimeExtras.d.ts +7 -0
  20. package/dist/runtimeExtras.d.ts.map +1 -0
  21. package/dist/runtimeExtras.js +7 -0
  22. package/dist/runtimeExtras.js.map +1 -0
  23. package/dist/streamingTiming.d.ts +16 -0
  24. package/dist/streamingTiming.d.ts.map +1 -0
  25. package/dist/streamingTiming.js +53 -0
  26. package/dist/streamingTiming.js.map +1 -0
  27. package/dist/types.d.ts +105 -5
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/uiMessages.d.ts +24 -0
  30. package/dist/uiMessages.d.ts.map +1 -0
  31. package/dist/uiMessages.js +68 -0
  32. package/dist/uiMessages.js.map +1 -0
  33. package/dist/useStreamRuntime.d.ts +10 -85
  34. package/dist/useStreamRuntime.d.ts.map +1 -1
  35. package/dist/useStreamRuntime.js +186 -135
  36. package/dist/useStreamRuntime.js.map +1 -1
  37. package/package.json +9 -9
  38. package/src/__tests__/langChainTestUtils.ts +3 -3
  39. package/src/convertMessages.test.ts +160 -3
  40. package/src/convertMessages.ts +104 -11
  41. package/src/findForkCheckpointInHistory.test.ts +263 -0
  42. package/src/findForkCheckpointInHistory.ts +68 -0
  43. package/src/groupUIMessagesByParent.test.ts +61 -0
  44. package/src/hooks.ts +156 -0
  45. package/src/index.ts +18 -3
  46. package/src/resolveForkCheckpoint.test.ts +202 -0
  47. package/src/resolveForkCheckpoint.ts +49 -0
  48. package/src/runtimeExtras.ts +5 -0
  49. package/src/streamingTiming.test.ts +118 -0
  50. package/src/streamingTiming.ts +85 -0
  51. package/src/types.ts +140 -3
  52. package/src/uiMessages.test.ts +190 -0
  53. package/src/uiMessages.ts +91 -0
  54. package/src/useLangChainError.test.tsx +1 -1
  55. package/src/useLangChainInterrupts.test.tsx +48 -0
  56. package/src/useLangChainRespond.test.tsx +49 -0
  57. package/src/useLangChainRespondAll.test.tsx +52 -0
  58. package/src/useLangChainState.test.tsx +1 -1
  59. package/src/useLangChainStream.test.tsx +42 -0
  60. package/src/useLangChainSubagents.test.tsx +48 -0
  61. package/src/useLangChainSubgraphs.test.tsx +48 -0
  62. package/src/useLangChainToolCalls.test.tsx +1 -1
  63. package/src/useStreamRuntime.test.tsx +232 -0
  64. package/src/useStreamRuntime.ts +301 -268
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { getMessageType } from "./convertMessages.js";
3
+ import { useStreamingTiming } from "@assistant-ui/core/react";
4
+ //#region src/streamingTiming.ts
5
+ const findAiMessage = (messages, messageId) => messages.find((m) => getMessageType(m) === "ai" && m.id === messageId);
6
+ const reasoningTextLength = (part) => {
7
+ if (part.summary && part.summary.length > 0) return part.summary.map((s) => s?.text ?? "").join("\n\n\n").length;
8
+ return part.reasoning?.length ?? 0;
9
+ };
10
+ const getTextLength = (messages, messageId) => {
11
+ const m = findAiMessage(messages, messageId);
12
+ if (!m) return 0;
13
+ const content = m.content;
14
+ if (typeof content === "string") return content.length;
15
+ if (!Array.isArray(content)) return 0;
16
+ let len = 0;
17
+ for (const part of content) switch (part.type) {
18
+ case "text":
19
+ case "text_delta":
20
+ if (typeof part.text === "string") len += part.text.length;
21
+ break;
22
+ case "thinking":
23
+ if (typeof part.thinking === "string") len += part.thinking.length;
24
+ break;
25
+ case "reasoning":
26
+ len += reasoningTextLength(part);
27
+ break;
28
+ }
29
+ return len;
30
+ };
31
+ const getToolCallCount = (messages, messageId) => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;
32
+ const getAssistantMessageId = (messages) => {
33
+ for (let i = messages.length - 1; i >= 0; i--) {
34
+ const m = messages[i];
35
+ if (m && getMessageType(m) === "ai" && m.id) return m.id;
36
+ }
37
+ };
38
+ const langChainStreamingTimingAccessors = {
39
+ getAssistantMessageId,
40
+ getTextLength,
41
+ getToolCallCount
42
+ };
43
+ /**
44
+ * Tracks per-message streaming timing for LangChain messages. Delegates to
45
+ * the shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,
46
+ * adapted to the `LangChainBaseMessage` shape (`_getType() -> "ai"`, content
47
+ * blocks including text/thinking/reasoning, `tool_calls`).
48
+ */
49
+ const useLangChainStreamingTiming = (messages, isRunning) => useStreamingTiming(messages, isRunning, langChainStreamingTimingAccessors);
50
+ //#endregion
51
+ export { langChainStreamingTimingAccessors, useLangChainStreamingTiming };
52
+
53
+ //# sourceMappingURL=streamingTiming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamingTiming.js","names":[],"sources":["../src/streamingTiming.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport {\n useStreamingTiming,\n type StreamingTimingAccessors,\n} from \"@assistant-ui/core/react\";\nimport type { LangChainBaseMessage, LangChainContentBlock } from \"./types\";\nimport { getMessageType } from \"./convertMessages\";\n\nconst findAiMessage = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): LangChainBaseMessage | undefined =>\n messages.find((m) => getMessageType(m) === \"ai\" && m.id === messageId);\n\nconst reasoningTextLength = (part: {\n readonly summary?: ReadonlyArray<{ readonly text?: string }>;\n readonly reasoning?: string;\n}): number => {\n if (part.summary && part.summary.length > 0)\n return part.summary.map((s) => s?.text ?? \"\").join(\"\\n\\n\\n\").length;\n return part.reasoning?.length ?? 0;\n};\n\nconst getTextLength = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): number => {\n const m = findAiMessage(messages, messageId);\n if (!m) return 0;\n const content = m.content;\n if (typeof content === \"string\") return content.length;\n if (!Array.isArray(content)) return 0;\n let len = 0;\n for (const part of content as readonly LangChainContentBlock[]) {\n switch (part.type) {\n case \"text\":\n case \"text_delta\":\n if (typeof part.text === \"string\") len += part.text.length;\n break;\n case \"thinking\":\n if (typeof part.thinking === \"string\") len += part.thinking.length;\n break;\n case \"reasoning\":\n len += reasoningTextLength(part);\n break;\n }\n }\n return len;\n};\n\nconst getToolCallCount = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): number => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;\n\nconst getAssistantMessageId = (\n messages: readonly LangChainBaseMessage[],\n): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && getMessageType(m) === \"ai\" && m.id) return m.id;\n }\n return undefined;\n};\n\nexport const langChainStreamingTimingAccessors: StreamingTimingAccessors<LangChainBaseMessage> =\n {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\n\n/**\n * Tracks per-message streaming timing for LangChain messages. Delegates to\n * the shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,\n * adapted to the `LangChainBaseMessage` shape (`_getType() -> \"ai\"`, content\n * blocks including text/thinking/reasoning, `tool_calls`).\n */\nexport const useLangChainStreamingTiming = (\n messages: readonly LangChainBaseMessage[],\n isRunning: boolean,\n): Record<string, MessageTiming> =>\n useStreamingTiming(messages, isRunning, langChainStreamingTimingAccessors);\n"],"mappings":";;;;AAUA,MAAM,iBACJ,UACA,cAEA,SAAS,MAAM,MAAM,eAAe,CAAC,MAAM,QAAQ,EAAE,OAAO,SAAS;AAEvE,MAAM,uBAAuB,SAGf;CACZ,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GACxC,OAAO,KAAK,QAAQ,KAAK,MAAM,GAAG,QAAQ,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;CAC/D,OAAO,KAAK,WAAW,UAAU;AACnC;AAEA,MAAM,iBACJ,UACA,cACW;CACX,MAAM,IAAI,cAAc,UAAU,SAAS;CAC3C,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,UAAU,EAAE;CAClB,IAAI,OAAO,YAAY,UAAU,OAAO,QAAQ;CAChD,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,IAAI,MAAM;CACV,KAAK,MAAM,QAAQ,SACjB,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;GACH,IAAI,OAAO,KAAK,SAAS,UAAU,OAAO,KAAK,KAAK;GACpD;EACF,KAAK;GACH,IAAI,OAAO,KAAK,aAAa,UAAU,OAAO,KAAK,SAAS;GAC5D;EACF,KAAK;GACH,OAAO,oBAAoB,IAAI;GAC/B;CACJ;CAEF,OAAO;AACT;AAEA,MAAM,oBACJ,UACA,cACW,cAAc,UAAU,SAAS,CAAC,EAAE,YAAY,UAAU;AAEvE,MAAM,yBACJ,aACuB;CACvB,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,IAAI,SAAS;EACnB,IAAI,KAAK,eAAe,CAAC,MAAM,QAAQ,EAAE,IAAI,OAAO,EAAE;CACxD;AAEF;AAEA,MAAa,oCACX;CACE;CACA;CACA;AACF;;;;;;;AAQF,MAAa,+BACX,UACA,cAEA,mBAAmB,UAAU,WAAW,iCAAiC"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { MessageStatus } from "@assistant-ui/core";
1
+ import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, MessageStatus, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
2
+ import { AnyStream, AssembledToolCall, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, UseStreamOptions } from "@langchain/react";
3
+ import { AssistantCloud } from "assistant-cloud";
2
4
 
3
5
  //#region src/types.d.ts
4
6
  /** Known content block types from @langchain/core messages. */
@@ -11,17 +13,18 @@ type LangChainContentBlock = {
11
13
  } | {
12
14
  type: "image_url";
13
15
  image_url: string | {
14
- url: string;
16
+ url?: string;
15
17
  };
16
18
  } | {
17
19
  type: "thinking";
18
20
  thinking: string;
19
21
  } | {
20
22
  type: "reasoning";
21
- summary: Array<{
23
+ summary?: Array<{
22
24
  type: "summary_text";
23
- text: string;
25
+ text?: string;
24
26
  }>;
27
+ reasoning?: string;
25
28
  } | {
26
29
  type: "file";
27
30
  data: string;
@@ -38,6 +41,36 @@ type LangChainToolCall = {
38
41
  name: string;
39
42
  args: Record<string, unknown>;
40
43
  };
44
+ /**
45
+ * A generative UI component the graph accumulates in its state. Read from
46
+ * `stream.values[uiStateKey]` and rendered via `makeAssistantDataUI`.
47
+ *
48
+ * The parent assistant message is carried in `metadata.message_id` by the
49
+ * Python SDK and in `metadata.id` by the JS SDK; the runtime reads either.
50
+ */
51
+ type UIMessage<TName extends string = string, TProps extends Record<string, unknown> = Record<string, unknown>> = {
52
+ type: "ui";
53
+ id: string;
54
+ name: TName;
55
+ props: TProps;
56
+ metadata?: {
57
+ merge?: boolean;
58
+ run_id?: string;
59
+ name?: string;
60
+ tags?: string[];
61
+ message_id?: string;
62
+ id?: string;
63
+ [key: string]: unknown;
64
+ };
65
+ };
66
+ /**
67
+ * Emitted on the live `custom` channel by the graph's `remove_ui_message`
68
+ * helper to drop a pushed `UIMessage` by id.
69
+ */
70
+ type RemoveUIMessage = {
71
+ type: "remove-ui";
72
+ id: string;
73
+ };
41
74
  /**
42
75
  * Minimal duck-typed interface for BaseMessage class instances returned by
43
76
  * `useStream`. Used internally by the message converter.
@@ -56,6 +89,73 @@ type LangChainBaseMessage = {
56
89
  status?: MessageStatus | "success" | "error" | undefined; /** Present on ToolMessage */
57
90
  artifact?: unknown;
58
91
  };
92
+ type LangChainRuntimeExtras = {
93
+ interrupt: {
94
+ value?: unknown;
95
+ } | undefined;
96
+ interrupts: readonly {
97
+ id?: string;
98
+ value?: unknown;
99
+ }[];
100
+ toolCalls: readonly AssembledToolCall[];
101
+ subagents: ReadonlyMap<string, SubagentDiscoverySnapshot>;
102
+ subgraphs: ReadonlyMap<string, SubgraphDiscoverySnapshot>;
103
+ stream: AnyStream;
104
+ error: unknown;
105
+ submit: (values: Record<string, unknown> | null | undefined, options?: Record<string, unknown>) => Promise<void>;
106
+ respond: (response: unknown, options?: Record<string, unknown>) => Promise<void>;
107
+ respondAll: (responsesById: Record<string, unknown>, options?: Record<string, unknown>) => Promise<void>;
108
+ values: Record<string, unknown>;
109
+ messagesKey: string;
110
+ };
111
+ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
112
+ /**
113
+ * Called whenever the active thread's canonical (remote) ID changes, so the
114
+ * value can be treated as a managed/controlled variable (e.g. synced to a URL
115
+ * query param). Only the settled remote ID is emitted: while a freshly created
116
+ * thread is still optimistic the value is `undefined`, and the real ID is
117
+ * emitted once the thread is initialized; the transient local ID is never
118
+ * surfaced.
119
+ */
120
+ onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
121
+ cloud?: AssistantCloud | undefined;
122
+ adapters?: {
123
+ attachments?: AttachmentAdapter | undefined;
124
+ speech?: SpeechSynthesisAdapter | undefined;
125
+ dictation?: DictationAdapter | undefined;
126
+ voice?: RealtimeVoiceAdapter | undefined;
127
+ feedback?: FeedbackAdapter | undefined;
128
+ } | undefined;
129
+ /**
130
+ * When the user sends a new message while previous tool calls are
131
+ * still pending, automatically submit `tool` messages that cancel
132
+ * them so the agent's tool-call accounting stays consistent.
133
+ * Defaults to `true`.
134
+ */
135
+ autoCancelPendingToolCalls?: boolean | undefined;
136
+ /**
137
+ * Routes the Cancel button's click to `useStream().stop()`. On by
138
+ * default. Pass `false` to disable the Cancel button.
139
+ */
140
+ unstable_allowCancellation?: boolean | undefined;
141
+ /**
142
+ * Custom `RemoteThreadListAdapter`. When provided, replaces the
143
+ * cloud-backed thread list adapter.
144
+ */
145
+ unstable_threadListAdapter?: RemoteThreadListAdapter | undefined; /** Custom thread-creation hook, forwarded to the cloud adapter. */
146
+ create?: (() => Promise<{
147
+ externalId: string | undefined;
148
+ }>) | undefined; /** Custom thread-deletion hook, forwarded to the cloud adapter. */
149
+ delete?: ((threadId: string) => Promise<void>) | undefined;
150
+ /**
151
+ * State key the graph accumulates generative `UIMessage`s under. Each UI
152
+ * is attached to the assistant message identified by its `metadata.message_id`
153
+ * (Python SDK) or `metadata.id` (JS SDK) and emitted as a `data` part.
154
+ * Defaults to `"ui"`.
155
+ */
156
+ uiStateKey?: string | undefined;
157
+ };
158
+ type UseStreamRuntimeOptions = UseStreamOptions extends infer O ? O extends UseStreamOptions ? O & LangChainRuntimeExtraOptions : never : never;
59
159
  //#endregion
60
- export { LangChainBaseMessage, LangChainContentBlock, LangChainToolCall };
160
+ export { LangChainBaseMessage, LangChainContentBlock, LangChainRuntimeExtraOptions, LangChainRuntimeExtras, LangChainToolCall, RemoveUIMessage, UIMessage, UseStreamRuntimeOptions };
61
161
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAGY,qBAAA;EACN,IAAA;EAAc,IAAA;AAAA;EACd,IAAA;EAAoB,IAAA;AAAA;EACpB,IAAA;EAAmB,SAAA;IAAsB,GAAA;EAAA;AAAA;EACzC,IAAA;EAAkB,QAAA;AAAA;EAElB,IAAA;EACA,OAAA,EAAS,KAAK;IAAG,IAAA;IAAsB,IAAA;EAAA;AAAA;EAGvC,IAAA;EACA,IAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;IAAa,QAAA;EAAA;AAAA;EAEb,IAAA;AAAA;AAAA,KAEM,iBAAA;EACV,EAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAM;AAAA;;AAAA;AAUd;;;;;KAAY,oBAAA;EACV,QAAA;EACA,OAAA;EACA,EAAA;EACA,IAAA;EACA,iBAAA,GAAoB,MAAA,+BADpB;EAGA,UAAA,YAAsB,iBAAA,gBAFF;EAIpB,YAAA,uBAFsB;EAItB,MAAA,GAAS,aAAA,oCAAT;EAEA,QAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;KAoBY,qBAAA;EACN,IAAA;EAAc,IAAA;AAAA;EACd,IAAA;EAAoB,IAAA;AAAA;EACpB,IAAA;EAAmB,SAAA;IAAsB,GAAA;EAAA;AAAA;EACzC,IAAA;EAAkB,QAAA;AAAA;EAElB,IAAA;EACA,OAAA,GAAU,KAAK;IAAG,IAAA;IAAsB,IAAA;EAAA;EACxC,SAAA;AAAA;EAGA,IAAA;EACA,IAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;IAAa,QAAA;EAAA;AAAA;EAEb,IAAA;AAAA;AAAA,KAEM,iBAAA;EACV,EAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAM;AAAA;;AAAA;AAUd;;;;;KAAY,SAAA,+CAEK,MAAA,oBAA0B,MAAA;EAEzC,IAAA;EACA,EAAA;EACA,IAAA,EAAM,KAAA;EACN,KAAA,EAAO,MAAA;EACP,QAAA;IACE,KAAA;IACA,MAAA;IACA,IAAA;IACA,IAAA;IACA,UAAA;IACA,EAAA;IAAA,CACC,GAAA;EAAA;AAAA;;;;;KAQO,eAAA;EACV,IAAA;EACA,EAAE;AAAA;AAVY;AAQhB;;;;AAEI;AAUJ;AApBgB,KAoBJ,oBAAA;EACV,QAAA;EACA,OAAA;EACA,EAAA;EACA,IAAA;EACA,iBAAA,GAAoB,MAAA,+BAME;EAJtB,UAAA,YAAsB,iBAAA,gBALtB;EAOA,YAAA,uBALA;EAOA,MAAA,GAAS,aAAA,oCANW;EAQpB,QAAA;AAAA;AAAA,KAGU,sBAAA;EACV,SAAA;IAAa,KAAA;EAAA;EACb,UAAA;IAAuB,EAAA;IAAa,KAAA;EAAA;EACpC,SAAA,WAAoB,iBAAA;EACpB,SAAA,EAAW,WAAA,SAAoB,yBAAA;EAC/B,SAAA,EAAW,WAAA,SAAoB,yBAAA;EAC/B,MAAA,EAAQ,SAAA;EACR,KAAA;EACA,MAAA,GACE,MAAA,EAAQ,MAAA,sCACR,OAAA,GAAU,MAAA,sBACP,OAAA;EACL,OAAA,GACE,QAAA,WACA,OAAA,GAAU,MAAA,sBACP,OAAA;EACL,UAAA,GACE,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,MAAA,sBACP,OAAA;EACL,MAAA,EAAQ,MAAA;EACR,WAAA;AAAA;AAAA,KAGU,4BAAA,GAA+B,0BAAA;EAPxB;;;;;;;;EAgBjB,gBAAA,KAAqB,QAAA;EACrB,KAAA,GAAQ,cAAA;EACR,QAAA;IAEM,WAAA,GAAc,iBAAA;IACd,MAAA,GAAS,sBAAA;IACT,SAAA,GAAY,gBAAA;IACZ,KAAA,GAAQ,oBAAA;IACR,QAAA,GAAW,eAAA;EAAA;EApCN;;;;;;EA6CX,0BAAA;EAzCE;;;;EA8CF,0BAAA;EA1CE;;;;EA+CF,0BAAA,GAA6B,uBAAA,cA3CZ;EA6CjB,MAAA,UAAgB,OAAA;IAAU,UAAA;EAAA,iBA3CrB;EA6CL,MAAA,KAAW,QAAA,aAAqB,OAAA;EA5CxB;;;AACG;AAGb;;EA+CE,UAAA;AAAA;AAAA,KAOU,uBAAA,GAA0B,gBAAA,mBAClC,CAAA,SAAU,gBAAA,GACR,CAAA,GAAI,4BAAA"}
@@ -0,0 +1,24 @@
1
+ import { RemoveUIMessage, UIMessage } from "./types.js";
2
+
3
+ //#region src/uiMessages.d.ts
4
+ type UIUpdate = UIMessage | RemoveUIMessage;
5
+ declare const isUIUpdate: (value: unknown) => value is UIUpdate | readonly UIUpdate[];
6
+ declare const applyUIUpdate: (list: readonly UIMessage[], update: UIUpdate | readonly UIUpdate[]) => UIMessage[];
7
+ /**
8
+ * Pulls a UI update out of a raw `custom`-channel event. The graph writes the
9
+ * `UIMessage` straight to the channel, so it lands at `params.data`; some
10
+ * transports wrap it one level deeper at `params.data.payload`.
11
+ */
12
+ declare const extractUIUpdate: (event: unknown) => UIUpdate | readonly UIUpdate[] | undefined;
13
+ declare const foldUIUpdates: (events: readonly unknown[]) => UIMessage[];
14
+ /**
15
+ * Merges live-streamed UI with the state snapshot. The snapshot is
16
+ * authoritative by id: once a UI lands in graph state it supersedes its live
17
+ * copy. A consequence is that a live `remove-ui` is overridden while the
18
+ * snapshot still contains that id; the removal only takes visible effect once
19
+ * the snapshot catches up.
20
+ */
21
+ declare const mergeUIMessages: (live: readonly UIMessage[], snapshot: unknown) => UIMessage[];
22
+ //#endregion
23
+ export { UIUpdate, applyUIUpdate, extractUIUpdate, foldUIUpdates, isUIUpdate, mergeUIMessages };
24
+ //# sourceMappingURL=uiMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uiMessages.d.ts","names":[],"sources":["../src/uiMessages.ts"],"mappings":";;;KAEY,QAAA,GAAW,SAAA,GAAY,eAAe;AAAA,cAErC,UAAA,GACX,KAAA,cACC,KAAA,IAAS,QAAA,YAAoB,QAAQ;AAAA,cAmB3B,aAAA,GACX,IAAA,WAAe,SAAA,IACf,MAAA,EAAQ,QAAA,YAAoB,QAAA,OAC3B,SAAA;;;;AA1B+C;AAElD;cAkDa,eAAA,GACX,KAAA,cACC,QAAA,YAAoB,QAAQ;AAAA,cAQlB,aAAA,GAAiB,MAAA,yBAA6B,SAAS;;;;;;;AA1D5B;cA0E3B,eAAA,GACX,IAAA,WAAe,SAAA,IACf,QAAA,cACC,SAAS"}
@@ -0,0 +1,68 @@
1
+ //#region src/uiMessages.ts
2
+ const isUIUpdate = (value) => {
3
+ if (Array.isArray(value)) return value.length > 0 && value.every(isUIUpdate);
4
+ if (value == null || typeof value !== "object") return false;
5
+ const v = value;
6
+ if (typeof v.id !== "string") return false;
7
+ if (v.type === "remove-ui") return true;
8
+ return v.type === "ui" && typeof v.name === "string" && typeof v.props === "object" && v.props !== null;
9
+ };
10
+ const applyUIUpdate = (list, update) => {
11
+ const events = Array.isArray(update) ? update : [update];
12
+ let next = list.slice();
13
+ for (const event of events) {
14
+ if (event.type === "remove-ui") {
15
+ next = next.filter((ui) => ui.id !== event.id);
16
+ continue;
17
+ }
18
+ const index = next.findIndex((ui) => ui.id === event.id);
19
+ if (index === -1) {
20
+ next.push(event);
21
+ continue;
22
+ }
23
+ next[index] = event.metadata?.merge === true ? {
24
+ ...event,
25
+ props: {
26
+ ...next[index].props,
27
+ ...event.props
28
+ }
29
+ } : event;
30
+ }
31
+ return next;
32
+ };
33
+ /**
34
+ * Pulls a UI update out of a raw `custom`-channel event. The graph writes the
35
+ * `UIMessage` straight to the channel, so it lands at `params.data`; some
36
+ * transports wrap it one level deeper at `params.data.payload`.
37
+ */
38
+ const extractUIUpdate = (event) => {
39
+ const data = event?.params?.data;
40
+ if (isUIUpdate(data)) return data;
41
+ const payload = data?.payload;
42
+ if (isUIUpdate(payload)) return payload;
43
+ };
44
+ const foldUIUpdates = (events) => {
45
+ let acc = [];
46
+ for (const event of events) {
47
+ const update = extractUIUpdate(event);
48
+ if (update) acc = applyUIUpdate(acc, update);
49
+ }
50
+ return acc;
51
+ };
52
+ /**
53
+ * Merges live-streamed UI with the state snapshot. The snapshot is
54
+ * authoritative by id: once a UI lands in graph state it supersedes its live
55
+ * copy. A consequence is that a live `remove-ui` is overridden while the
56
+ * snapshot still contains that id; the removal only takes visible effect once
57
+ * the snapshot catches up.
58
+ */
59
+ const mergeUIMessages = (live, snapshot) => {
60
+ const byId = /* @__PURE__ */ new Map();
61
+ for (const ui of live) byId.set(ui.id, ui);
62
+ if (Array.isArray(snapshot)) for (const ui of snapshot) byId.set(ui.id, ui);
63
+ return [...byId.values()];
64
+ };
65
+ //#endregion
66
+ export { applyUIUpdate, extractUIUpdate, foldUIUpdates, isUIUpdate, mergeUIMessages };
67
+
68
+ //# sourceMappingURL=uiMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uiMessages.js","names":[],"sources":["../src/uiMessages.ts"],"sourcesContent":["import type { RemoveUIMessage, UIMessage } from \"./types\";\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 const foldUIUpdates = (events: readonly unknown[]): UIMessage[] => {\n let acc: UIMessage[] = [];\n for (const event of events) {\n const update = extractUIUpdate(event);\n if (update) acc = applyUIUpdate(acc, update);\n }\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,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;AAEA,MAAa,iBAAiB,WAA4C;CACxE,IAAI,MAAmB,CAAC;CACxB,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,gBAAgB,KAAK;EACpC,IAAI,QAAQ,MAAM,cAAc,KAAK,MAAM;CAC7C;CACA,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,47 +1,20 @@
1
1
  /// <reference types="@assistant-ui/core/store" />
2
- import { LangChainBaseMessage } from "./types.js";
3
- import { AppendMessage, AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
4
- import { AssembledToolCall, UseStreamOptions } from "@langchain/react";
5
- import { AssistantCloud } from "assistant-cloud";
2
+ import { UIMessage, UseStreamRuntimeOptions } from "./types.js";
3
+ import { AppendMessage } from "@assistant-ui/core";
6
4
 
7
5
  //#region src/useStreamRuntime.d.ts
8
- type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
9
- cloud?: AssistantCloud | undefined;
10
- adapters?: {
11
- attachments?: AttachmentAdapter | undefined;
12
- speech?: SpeechSynthesisAdapter | undefined;
13
- dictation?: DictationAdapter | undefined;
14
- voice?: RealtimeVoiceAdapter | undefined;
15
- feedback?: FeedbackAdapter | undefined;
16
- } | undefined;
17
- /**
18
- * When the user sends a new message while previous tool calls are
19
- * still pending, automatically submit `tool` messages that cancel
20
- * them so the agent's tool-call accounting stays consistent.
21
- * Defaults to `true`.
22
- */
23
- autoCancelPendingToolCalls?: boolean | undefined;
24
- /**
25
- * Routes the Cancel button's click to `useStream().stop()`. On by
26
- * default. Pass `false` to disable the Cancel button.
27
- */
28
- unstable_allowCancellation?: boolean | undefined;
29
- /**
30
- * Custom `RemoteThreadListAdapter`. When provided, replaces the
31
- * cloud-backed thread list adapter.
32
- */
33
- unstable_threadListAdapter?: RemoteThreadListAdapter | undefined; /** Custom thread-creation hook, forwarded to the cloud adapter. */
34
- create?: (() => Promise<{
35
- externalId: string | undefined;
36
- }>) | undefined; /** Custom thread-deletion hook, forwarded to the cloud adapter. */
37
- delete?: ((threadId: string) => Promise<void>) | undefined;
38
- };
39
6
  declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runConfig"]) => {
40
7
  config: {
41
8
  configurable: Record<string, unknown>;
42
9
  };
43
10
  } | undefined;
44
- type UseStreamRuntimeOptions = UseStreamOptions extends infer O ? O extends UseStreamOptions ? O & LangChainRuntimeExtraOptions : never : never;
11
+ /**
12
+ * Group the graph's accumulated `UIMessage`s by the assistant message they
13
+ * belong to. Non-array state and entries without a parent link are dropped.
14
+ * The parent id comes from `metadata.message_id` (Python SDK) or
15
+ * `metadata.id` (JS SDK).
16
+ */
17
+ declare const groupUIMessagesByParent: (value: unknown) => Map<string, UIMessage[]>;
45
18
  /**
46
19
  * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.
47
20
  * Accepts the same options as `useStream` from `@langchain/react`, plus
@@ -67,54 +40,6 @@ type UseStreamRuntimeOptions = UseStreamOptions extends infer O ? O extends UseS
67
40
  * ```
68
41
  */
69
42
  declare const useStreamRuntime: (rawOptions: UseStreamRuntimeOptions) => import("@assistant-ui/core").AssistantRuntime;
70
- /**
71
- * Read the current LangGraph interrupt state from the runtime extras.
72
- */
73
- declare const useLangChainInterruptState: () => {
74
- value?: unknown;
75
- } | undefined;
76
- /** Read the last run/hydration error from the runtime extras. */
77
- declare const useLangChainError: () => unknown;
78
- /**
79
- * Read the root tool calls assembled by `useStream` from the `tools`
80
- * channel. Defaults to an empty array, so consumers can `.map` without
81
- * a guard. Useful for rendering pending/streamed tool calls and
82
- * approval UIs.
83
- */
84
- declare const useLangChainToolCalls: () => readonly AssembledToolCall<string, unknown, unknown>[];
85
- /**
86
- * Returns a function to submit raw state updates to the LangGraph agent,
87
- * bypassing the normal message flow. Useful for sending interrupt resume
88
- * commands.
89
- */
90
- declare const useLangChainSubmit: () => (values: Record<string, unknown> | null | undefined, options?: Record<string, unknown>) => Promise<void>;
91
- /**
92
- * Submit a list of LangChain-shaped messages on the current thread.
93
- * Parity helper for migrating from `useLangGraphSend`. Routes to
94
- * `useStream().submit({ [messagesKey]: messages }, options)`.
95
- */
96
- declare const useLangChainSend: () => (messages: readonly LangChainBaseMessage[], options?: Record<string, unknown>) => Promise<void>;
97
- /**
98
- * Submit a `useStream` command (e.g. interrupt resume). Parity helper
99
- * for migrating from `useLangGraphSendCommand`. Note that v1's command
100
- * shape (`{ resume?, goto?, update? }`) differs from the legacy
101
- * `{ resume: string }` form — to carry a payload, use the input or
102
- * `stream.respond` instead.
103
- */
104
- declare const useLangChainSendCommand: () => (command: Record<string, unknown>) => Promise<void>;
105
- /**
106
- * Read a custom LangGraph state key from the current thread. Mirrors
107
- * `useStream().values[key]` from `@langchain/react` and updates when the
108
- * stream emits new state.
109
- *
110
- * @example
111
- * ```tsx
112
- * const todos = useLangChainState<Todo[]>("todos");
113
- * const files = useLangChainState<Record<string, string>>("files", {});
114
- * ```
115
- */
116
- declare function useLangChainState<T>(key: string): T | undefined;
117
- declare function useLangChainState<T>(key: string, defaultValue: T): T;
118
43
  //#endregion
119
- export { UseStreamRuntimeOptions, runConfigToSubmitOptions, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime };
44
+ export { groupUIMessagesByParent, runConfigToSubmitOptions, useStreamRuntime };
120
45
  //# sourceMappingURL=useStreamRuntime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;;;KA8DK,4BAAA,GAA+B,0BAAA;EAClC,KAAA,GAAQ,cAAA;EACR,QAAA;IAEM,WAAA,GAAc,iBAAA;IACd,MAAA,GAAS,sBAAA;IACT,SAAA,GAAY,gBAAA;IACZ,KAAA,GAAQ,oBAAA;IACR,QAAA,GAAW,eAAA;EAAA;EAFC;;;;;;EAWlB,0BAAA;EAcuC;;;;EATvC,0BAAA;EAlBM;;;;EAuBN,0BAAA,GAA6B,uBAAA,cArBX;EAuBlB,MAAA,UAAgB,OAAA;IAAU,UAAA;EAAA,iBArBT;EAuBjB,MAAA,KAAW,QAAA,aAAqB,OAAA;AAAA;AAAA,cAGrB,wBAAA,GACX,SAAA,EAAW,aAAA;;kBAAA,MAAA;EAAA;AAAA;AAAA,KAyBD,uBAAA,GAA0B,gBAAA,mBAClC,CAAA,SAAU,gBAAA,GACR,CAAA,GAAI,4BAAA;;;;AA/B+B;AAGzC;;;;;;;;;;;AA0BA;;;;;;;;;cA2Ma,gBAAA,GAAoB,UAAA,EAAY,uBAAuB,kCAAA,gBAAA;;;;cA+BvD,0BAAA;EAMZ,KAAA;AAAA;AA9OqC;AAAA,cAiPzB,iBAAA;;;;;;;cAcA,qBAAA,iBAAqB,iBAAA;AA5BjC;AAKD;;;;AALC,cAyCY,kBAAA,SAGT,MAAA,EAAQ,MAAA,sCACR,OAAA,GAAU,MAAA,sBAAuB,OAAA;AA/BrC;;;;AAMC;AAND,cA4Ca,gBAAA,SAGT,QAAA,WAAmB,oBAAA,IACnB,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;AAlCH;AAalC;;;cAqCa,uBAAA,SAEH,OAAA,EAAS,MAAA,sBAAuB,OAAA;;;;;;;;;;;AAnCL;iBAiDrB,iBAAA,IAAqB,GAAA,WAAc,CAAC;AAAA,iBACpC,iBAAA,IAAqB,GAAA,UAAa,YAAA,EAAc,CAAA,GAAI,CAAC"}
1
+ {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;cAsCa,wBAAA,GACX,SAAA,EAAW,aAAA;;kBAAA,MAAA;EAAA;AAAA;;;;;;;cAYA,uBAAA,GACX,KAAA,cACC,GAAG,SAAS,SAAA;;AAFf;;;;;;;;AAEwB;AAyZxB;;;;;;;;AA4BC;;;;;;cA5BY,gBAAA,GAAoB,UAAA,EAAY,uBAAuB,kCAAA,gBAAA"}