@assistant-ui/core 0.2.6 → 0.2.8

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 (192) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/speech.d.ts.map +1 -1
  3. package/dist/adapters/speech.js.map +1 -1
  4. package/dist/index.d.ts +4 -1
  5. package/dist/index.js +8 -1
  6. package/dist/index.js.map +1 -0
  7. package/dist/internal/duplicate-detection.d.ts +5 -0
  8. package/dist/internal/duplicate-detection.d.ts.map +1 -0
  9. package/dist/internal/duplicate-detection.js +11 -0
  10. package/dist/internal/duplicate-detection.js.map +1 -0
  11. package/dist/internal.d.ts +2 -2
  12. package/dist/internal.js +2 -2
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js.map +1 -1
  15. package/dist/model-context/frame/provider.d.ts.map +1 -1
  16. package/dist/model-context/frame/provider.js.map +1 -1
  17. package/dist/model-context/registry.d.ts.map +1 -1
  18. package/dist/model-context/tool.d.ts.map +1 -1
  19. package/dist/react/AssistantProvider.d.ts.map +1 -1
  20. package/dist/react/AssistantProvider.js.map +1 -1
  21. package/dist/react/client/Interactables.js.map +1 -1
  22. package/dist/react/client/Tools.d.ts.map +1 -1
  23. package/dist/react/client/Tools.js +26 -15
  24. package/dist/react/client/Tools.js.map +1 -1
  25. package/dist/react/index.d.ts +5 -4
  26. package/dist/react/index.js +2 -2
  27. package/dist/react/model-context/toolbox.d.ts +29 -2
  28. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  29. package/dist/react/model-context/toolbox.js +18 -0
  30. package/dist/react/model-context/toolbox.js.map +1 -0
  31. package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
  32. package/dist/react/model-context/useAssistantTool.js +6 -3
  33. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  34. package/dist/react/model-context/useAssistantToolUI.d.ts +6 -0
  35. package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
  36. package/dist/react/model-context/useAssistantToolUI.js +4 -2
  37. package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
  38. package/dist/react/model-context/useInlineRender.js.map +1 -1
  39. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
  40. package/dist/react/primitives/message/MessageGroupedParts.d.ts +49 -7
  41. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  42. package/dist/react/primitives/message/MessageGroupedParts.js +28 -3
  43. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  44. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  45. package/dist/react/primitives/message/MessageParts.js +2 -7
  46. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  47. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  48. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  49. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  50. package/dist/react/runtimes/RuntimeAdapterProvider.js +6 -5
  51. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  52. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  53. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  54. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  55. package/dist/react/runtimes/external-message-converter.d.ts +1 -1
  56. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  57. package/dist/react/runtimes/external-message-converter.js +1 -0
  58. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  59. package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts +7 -0
  60. package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts.map +1 -0
  61. package/dist/react/runtimes/useExternalStoreSharedOptions.js +21 -0
  62. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -0
  63. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  64. package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
  65. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  66. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  67. package/dist/react/types/scopes/tools.d.ts +19 -2
  68. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  69. package/dist/react/utils/groupParts.d.ts +32 -11
  70. package/dist/react/utils/groupParts.d.ts.map +1 -1
  71. package/dist/react/utils/groupParts.js +13 -6
  72. package/dist/react/utils/groupParts.js.map +1 -1
  73. package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
  74. package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
  75. package/dist/runtime/api/attachment-runtime.js.map +1 -1
  76. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  77. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  78. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  79. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  80. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  81. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  82. package/dist/runtime/base/base-assistant-runtime-core.d.ts.map +1 -1
  83. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  84. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  85. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  86. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  87. package/dist/runtime/interfaces/thread-runtime-core.d.ts +8 -0
  88. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  89. package/dist/runtime/utils/message-repository.d.ts +9 -1
  90. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  91. package/dist/runtime/utils/message-repository.js +34 -14
  92. package/dist/runtime/utils/message-repository.js.map +1 -1
  93. package/dist/runtime/utils/thread-message-like.d.ts +1 -0
  94. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  95. package/dist/runtime/utils/thread-message-like.js +2 -1
  96. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  97. package/dist/runtimes/external-store/external-store-adapter.d.ts +31 -0
  98. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  99. package/dist/runtimes/external-store/external-store-shared-options.d.ts +8 -0
  100. package/dist/runtimes/external-store/external-store-shared-options.d.ts.map +1 -0
  101. package/dist/runtimes/external-store/external-store-shared-options.js +11 -0
  102. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -0
  103. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  104. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  105. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +25 -2
  106. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  107. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +106 -26
  108. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  109. package/dist/runtimes/external-store/thread-message-converter.d.ts.map +1 -1
  110. package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
  111. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  112. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  113. package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
  114. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  115. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +168 -0
  116. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -0
  117. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +449 -0
  118. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -0
  119. package/dist/subscribable/subscribable.d.ts.map +1 -1
  120. package/dist/subscribable/subscribable.js.map +1 -1
  121. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
  122. package/dist/types/message.d.ts +6 -0
  123. package/dist/types/message.d.ts.map +1 -1
  124. package/dist/types/message.js.map +1 -1
  125. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  126. package/dist/utils/id.d.ts +1 -3
  127. package/dist/utils/id.d.ts.map +1 -1
  128. package/dist/utils/id.js +1 -4
  129. package/dist/utils/id.js.map +1 -1
  130. package/package.json +10 -10
  131. package/src/adapters/index.ts +1 -4
  132. package/src/adapters/speech.ts +0 -1
  133. package/src/index.ts +12 -0
  134. package/src/internal/duplicate-detection.ts +26 -0
  135. package/src/internal.ts +0 -2
  136. package/src/model-context/frame/host.ts +0 -1
  137. package/src/model-context/frame/provider.ts +0 -1
  138. package/src/react/AssistantProvider.tsx +2 -3
  139. package/src/react/client/Interactables.ts +0 -1
  140. package/src/react/client/Tools.ts +50 -25
  141. package/src/react/index.ts +9 -8
  142. package/src/react/model-context/toolbox.ts +46 -1
  143. package/src/react/model-context/useAssistantTool.ts +8 -3
  144. package/src/react/model-context/useAssistantToolUI.ts +9 -2
  145. package/src/react/model-context/useInlineRender.ts +0 -1
  146. package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -2
  147. package/src/react/primitives/message/MessageAttachments.test.tsx +1 -1
  148. package/src/react/primitives/message/MessageGroupedParts.tsx +102 -13
  149. package/src/react/primitives/message/MessageParts.tsx +4 -7
  150. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +0 -3
  151. package/src/react/runtimes/RuntimeAdapterProvider.tsx +12 -7
  152. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +0 -3
  153. package/src/react/runtimes/external-message-converter.ts +5 -1
  154. package/src/react/runtimes/useExternalStoreSharedOptions.ts +23 -0
  155. package/src/react/runtimes/useLocalRuntime.ts +0 -10
  156. package/src/react/runtimes/useRemoteThreadListRuntime.ts +0 -6
  157. package/src/react/types/scopes/tools.ts +20 -1
  158. package/src/react/utils/groupParts.ts +49 -18
  159. package/src/runtime/api/attachment-runtime.ts +1 -2
  160. package/src/runtime/interfaces/thread-runtime-core.ts +8 -0
  161. package/src/runtime/internal.ts +1 -4
  162. package/src/runtime/utils/message-repository.ts +57 -16
  163. package/src/runtime/utils/thread-message-like.ts +2 -0
  164. package/src/runtimes/external-store/external-store-adapter.ts +33 -0
  165. package/src/runtimes/external-store/external-store-shared-options.ts +18 -0
  166. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +1 -3
  167. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +179 -37
  168. package/src/runtimes/tool-invocations/EDGE_CASES.md +194 -0
  169. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +1054 -0
  170. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +782 -0
  171. package/src/subscribable/subscribable.ts +3 -3
  172. package/src/tests/MessageRepository.test.ts +83 -52
  173. package/src/tests/OptimisticState-delete-crash.test.ts +2 -0
  174. package/src/tests/OptimisticState-list-race.test.ts +2 -4
  175. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +5 -5
  176. package/src/tests/auiV0Encode.test.ts +1 -1
  177. package/src/tests/composer-can-send.test.ts +8 -4
  178. package/src/tests/duplicate-detection.test.ts +34 -0
  179. package/src/tests/external-store-thread-list-runtime-core.test.ts +1 -1
  180. package/src/tests/external-store-thread-runtime-core.test.ts +112 -79
  181. package/src/tests/groupParts.test.ts +70 -0
  182. package/src/tests/no-unsafe-process-env.test.ts +1 -0
  183. package/src/tests/remote-thread-list-isLoading.test.ts +2 -5
  184. package/src/tests/thread-message-like.test.ts +4 -1
  185. package/src/types/index.ts +1 -4
  186. package/src/types/message.ts +6 -0
  187. package/src/utils/id.ts +0 -4
  188. package/dist/react/runtimes/useToolInvocations.d.ts +0 -53
  189. package/dist/react/runtimes/useToolInvocations.d.ts.map +0 -1
  190. package/dist/react/runtimes/useToolInvocations.js +0 -380
  191. package/dist/react/runtimes/useToolInvocations.js.map +0 -1
  192. package/src/react/runtimes/useToolInvocations.ts +0 -694
@@ -160,6 +160,76 @@ describe("groupPartByType", () => {
160
160
  expect(fn(notMcp)).toEqual(["group-tool"]);
161
161
  });
162
162
 
163
+ const standaloneContext = (...names: string[]) => ({
164
+ toolUIs: Object.fromEntries(
165
+ names.map((name) => [name, [{ render: () => null, standalone: true }]]),
166
+ ),
167
+ });
168
+
169
+ it("routes context-standalone tool calls through the 'standalone-tool-call' entry", () => {
170
+ const fn = groupPartByType({
171
+ "tool-call": ["group-tool"],
172
+ "standalone-tool-call": [],
173
+ });
174
+ const standalone = part({
175
+ type: "tool-call",
176
+ toolName: "ask_user",
177
+ } as Partial<PartState>);
178
+ const regular = part({
179
+ type: "tool-call",
180
+ toolName: "search",
181
+ } as Partial<PartState>);
182
+ expect(fn(standalone, standaloneContext("ask_user"))).toEqual([]);
183
+ expect(fn(regular, standaloneContext("ask_user"))).toEqual(["group-tool"]);
184
+ // No context → not standalone, falls through to "tool-call".
185
+ expect(fn(standalone)).toEqual(["group-tool"]);
186
+ // Registered but not standalone → also falls through to "tool-call".
187
+ const inlineCtx = {
188
+ toolUIs: { ask_user: [{ render: () => null, standalone: false }] },
189
+ };
190
+ expect(fn(standalone, inlineCtx)).toEqual(["group-tool"]);
191
+ });
192
+
193
+ it("routes MCP-app parts through 'standalone-tool-call' from the part alone", () => {
194
+ const fn = groupPartByType({
195
+ "tool-call": ["group-tool"],
196
+ "standalone-tool-call": [],
197
+ });
198
+ const mcpApp = part({
199
+ type: "tool-call",
200
+ toolName: "render",
201
+ mcp: { app: { resourceUri: "ui://my-app" } },
202
+ } as Partial<PartState>);
203
+ expect(fn(mcpApp)).toEqual([]);
204
+ });
205
+
206
+ it("routes MCP-app parts through the deprecated 'mcp-app' entry", () => {
207
+ const fn = groupPartByType({
208
+ "tool-call": ["group-tool"],
209
+ "mcp-app": ["group-mcp"],
210
+ });
211
+ const mcpApp = part({
212
+ type: "tool-call",
213
+ toolName: "render",
214
+ mcp: { app: { resourceUri: "ui://my-app" } },
215
+ } as Partial<PartState>);
216
+ expect(fn(mcpApp)).toEqual(["group-mcp"]);
217
+ });
218
+
219
+ it("prefers 'standalone-tool-call' over the deprecated 'mcp-app' entry", () => {
220
+ const fn = groupPartByType({
221
+ "tool-call": ["group-tool"],
222
+ "standalone-tool-call": ["group-standalone"],
223
+ "mcp-app": ["group-mcp"],
224
+ });
225
+ const mcpApp = part({
226
+ type: "tool-call",
227
+ toolName: "render",
228
+ mcp: { app: { resourceUri: "ui://x" } },
229
+ } as Partial<PartState>);
230
+ expect(fn(mcpApp)).toEqual(["group-standalone"]);
231
+ });
232
+
163
233
  it("tags the function with a GROUPBY_MEMO_KEY fingerprint", () => {
164
234
  const fn = groupPartByType({ reasoning: ["group-r"] });
165
235
  const memoKey = (fn as unknown as { [GROUPBY_MEMO_KEY]: string })[
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { describe, expect, it } from "vitest";
2
3
  import { readFileSync, readdirSync, statSync } from "node:fs";
3
4
  import { resolve, relative, join } from "node:path";
@@ -26,6 +26,8 @@ type ListResult = {
26
26
 
27
27
  const INITIAL_STATE: RemoteThreadState = {
28
28
  isLoading: true,
29
+ isLoadingMore: false,
30
+ cursor: undefined,
29
31
  newThreadId: undefined,
30
32
  threadIds: [],
31
33
  archivedThreadIds: [],
@@ -84,7 +86,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
84
86
  state.optimisticUpdate({
85
87
  execute: () => d.promise,
86
88
  loading: (s) => ({ ...s, isLoading: true }),
87
- // biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
88
89
  then: applyListResult,
89
90
  });
90
91
 
@@ -98,7 +99,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
98
99
  const promise = state.optimisticUpdate({
99
100
  execute: () => d.promise,
100
101
  loading: (s) => ({ ...s, isLoading: true }),
101
- // biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
102
102
  then: applyListResult,
103
103
  });
104
104
 
@@ -115,7 +115,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
115
115
  const promise = state.optimisticUpdate({
116
116
  execute: () => d.promise,
117
117
  loading: (s) => ({ ...s, isLoading: true }),
118
- // biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
119
118
  then: applyListResult,
120
119
  });
121
120
 
@@ -140,7 +139,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
140
139
  const promise = state.optimisticUpdate({
141
140
  execute: () => d.promise,
142
141
  loading: (s) => ({ ...s, isLoading: true }),
143
- // biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
144
142
  then: applyListResult,
145
143
  });
146
144
 
@@ -164,7 +162,6 @@ describe("RemoteThreadList isLoading error path", () => {
164
162
  .optimisticUpdate({
165
163
  execute: () => d.promise,
166
164
  loading: (s) => ({ ...s, isLoading: true }),
167
- // biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
168
165
  then: applyListResult,
169
166
  })
170
167
  .catch(() => {
@@ -2,7 +2,10 @@ import { describe, expect, it } from "vitest";
2
2
  import { fromThreadMessageLike } from "../runtime/utils/thread-message-like";
3
3
 
4
4
  const fallbackId = "test-id";
5
- const fallbackStatus = { type: "complete" as const };
5
+ const fallbackStatus = {
6
+ type: "complete" as const,
7
+ reason: "stop" as const,
8
+ };
6
9
 
7
10
  describe("fromThreadMessageLike", () => {
8
11
  describe("data-* prefixed types", () => {
@@ -49,7 +49,4 @@ export type {
49
49
  Unstable_DirectiveFormatter,
50
50
  } from "./directive";
51
51
 
52
- export type {
53
- Unstable_TriggerItem,
54
- Unstable_TriggerCategory,
55
- } from "./trigger";
52
+ export type { Unstable_TriggerItem, Unstable_TriggerCategory } from "./trigger";
@@ -314,6 +314,11 @@ export type ThreadAssistantMessage = MessageCommonProps & {
314
314
  readonly steps: readonly ThreadStep[];
315
315
  readonly submittedFeedback?: { readonly type: "positive" | "negative" };
316
316
  readonly timing?: MessageTiming;
317
+ /**
318
+ * Marks a client-side optimistic placeholder. Such messages are evicted
319
+ * once off the head branch and are never persisted.
320
+ */
321
+ readonly isOptimistic?: boolean;
317
322
  readonly custom: Record<string, unknown>;
318
323
  };
319
324
  };
@@ -327,6 +332,7 @@ type BaseThreadMessage = {
327
332
  readonly steps?: readonly ThreadStep[];
328
333
  readonly submittedFeedback?: { readonly type: "positive" | "negative" };
329
334
  readonly timing?: MessageTiming;
335
+ readonly isOptimistic?: boolean;
330
336
  readonly custom: Record<string, unknown>;
331
337
  };
332
338
  readonly attachments?: ThreadUserMessage["attachments"];
package/src/utils/id.ts CHANGED
@@ -5,10 +5,6 @@ export const generateId = customAlphabet(
5
5
  7,
6
6
  );
7
7
 
8
- const optimisticPrefix = "__optimistic__";
9
- export const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
10
- export const isOptimisticId = (id: string) => id.startsWith(optimisticPrefix);
11
-
12
8
  const errorPrefix = "__error__";
13
9
  export const generateErrorMessageId = () => `${errorPrefix}${generateId()}`;
14
10
  export const isErrorMessageId = (id: string) => id.startsWith(errorPrefix);
@@ -1,53 +0,0 @@
1
- import { ThreadMessage } from "../../types/message.js";
2
- import { Tool, ToolModelContentPart } from "assistant-stream";
3
- import { ReadonlyJSONValue } from "assistant-stream/utils";
4
-
5
- //#region src/react/runtimes/useToolInvocations.d.ts
6
- type AssistantTransportState = {
7
- readonly messages: readonly ThreadMessage[];
8
- readonly state?: ReadonlyJSONValue;
9
- readonly isRunning: boolean;
10
- };
11
- type AddToolResultCommand = {
12
- readonly type: "add-tool-result";
13
- readonly toolCallId: string;
14
- readonly toolName: string;
15
- readonly result: ReadonlyJSONValue;
16
- readonly isError: boolean;
17
- readonly artifact?: ReadonlyJSONValue;
18
- readonly modelContent?: readonly ToolModelContentPart[];
19
- };
20
- type UseToolInvocationsParams = {
21
- state: AssistantTransportState;
22
- getTools: () => Record<string, Tool> | undefined;
23
- onResult: (command: AddToolResultCommand) => void;
24
- setToolStatuses: (updater: Record<string, ToolExecutionStatus> | ((prev: Record<string, ToolExecutionStatus>) => Record<string, ToolExecutionStatus>)) => void;
25
- };
26
- /**
27
- * Streaming execution state for a frontend tool.
28
- *
29
- * Custom runtime integrations use this to mirror in-flight tool calls while
30
- * `useToolInvocations` executes tools in the browser.
31
- */
32
- type ToolExecutionStatus = {
33
- /** The tool's execute function is currently running. */type: "executing";
34
- } | {
35
- /** The tool is waiting for a human input payload before continuing. */type: "interrupt"; /** Human input request emitted by the tool execution context. */
36
- payload: {
37
- type: "human";
38
- payload: unknown;
39
- };
40
- };
41
- declare function useToolInvocations({
42
- state,
43
- getTools,
44
- onResult,
45
- setToolStatuses
46
- }: UseToolInvocationsParams): {
47
- reset: () => void;
48
- abort: () => Promise<void>;
49
- resume: (toolCallId: string, payload: unknown) => void;
50
- };
51
- //#endregion
52
- export { AddToolResultCommand, AssistantTransportState, ToolExecutionStatus, useToolInvocations };
53
- //# sourceMappingURL=useToolInvocations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useToolInvocations.d.ts","names":[],"sources":["../../../src/react/runtimes/useToolInvocations.ts"],"mappings":";;;;;KAkBY,uBAAA;EAAA,SACD,QAAA,WAAmB,aAAA;EAAA,SACnB,KAAA,GAAQ,iBAAiB;EAAA,SACzB,SAAA;AAAA;AAAA,KAGC,oBAAA;EAAA,SACD,IAAA;EAAA,SACA,UAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,EAAQ,iBAAA;EAAA,SACR,OAAA;EAAA,SACA,QAAA,GAAW,iBAAA;EAAA,SACX,YAAA,YAAwB,oBAAA;AAAA;AAAA,KA2B9B,wBAAA;EACH,KAAA,EAAO,uBAAA;EACP,QAAA,QAAgB,MAAA,SAAe,IAAA;EAC/B,QAAA,GAAW,OAAA,EAAS,oBAAA;EACpB,eAAA,GACE,OAAA,EACI,MAAA,SAAe,mBAAA,MAEb,IAAA,EAAM,MAAA,SAAe,mBAAA,MAClB,MAAA,SAAe,mBAAA;AAAA;;;;;;;KAUhB,mBAAA;EA/CD,wDAkDL,IAAA;AAAA;EAjD6B,uEAqD7B,IAAA,eArDiD;EAuDjD,OAAA;IAAW,IAAA;IAAe,OAAA;EAAA;AAAA;AAAA,iBAyChB,kBAAA,CAAA;EACd,KAAA;EACA,QAAA;EACA,QAAA;EACA;AAAA,GACC,wBAAA;;eA+fiB,OAAA;+BAkCW,OAAA;AAAA"}
@@ -1,380 +0,0 @@
1
- import { isJSONValueEqual } from "../../utils/json/is-json-equal.js";
2
- import { ToolResponse, createAssistantStreamController, unstable_toolResultStream } from "assistant-stream";
3
- import { AssistantMetaTransformStream } from "assistant-stream/utils";
4
- import { useEffect, useRef, useState } from "react";
5
- //#region src/react/runtimes/useToolInvocations.ts
6
- const isArgsTextComplete = (argsText) => {
7
- try {
8
- JSON.parse(argsText);
9
- return true;
10
- } catch {
11
- return false;
12
- }
13
- };
14
- const parseArgsText = (argsText) => {
15
- try {
16
- return JSON.parse(argsText);
17
- } catch {
18
- return;
19
- }
20
- };
21
- const isEquivalentCompleteArgsText = (previous, next) => {
22
- const previousValue = parseArgsText(previous);
23
- const nextValue = parseArgsText(next);
24
- if (previousValue === void 0 || nextValue === void 0) return false;
25
- return isJSONValueEqual(previousValue, nextValue);
26
- };
27
- function useToolInvocations({ state, getTools, onResult, setToolStatuses }) {
28
- /**
29
- * Single source of truth for per-tool-call lifecycle. Keyed by *logical*
30
- * toolCallId (the id the host knows). Restored entries have no controller;
31
- * active entries carry their stream id and rewrite/execution bookkeeping.
32
- */
33
- const entriesRef = useRef(/* @__PURE__ */ new Map());
34
- /**
35
- * Reverse alias map populated only when a rewrite assigns a synthetic stream
36
- * id to an entry. Identity mappings are implicit via the fallback in
37
- * `getLogicalToolCallId`.
38
- */
39
- const streamToLogicalRef = useRef(/* @__PURE__ */ new Map());
40
- /**
41
- * Stream ids whose `result` chunks must be dropped before reaching `onResult`.
42
- * Populated when:
43
- * - an argsText rewrite supersedes a stream (the old stream's result, if
44
- * any, is no longer authoritative)
45
- * - `reset()` is called while a pre-resolved tool call has a never-settling
46
- * Promise pending in the executor — the eventual cancellation chunk
47
- * would otherwise be forwarded to a host that has already moved on.
48
- */
49
- const abandonedStreamIdsRef = useRef(/* @__PURE__ */ new Set());
50
- /**
51
- * Stream ids whose `execute` should be short-circuited in the tool wrapper.
52
- * Tracked by physical stream id (not logical id) so cleanup is keyed off
53
- * the same id the wrapper sees in its context.
54
- */
55
- const skipExecuteStreamIdsRef = useRef(/* @__PURE__ */ new Set());
56
- const humanInputRef = useRef(/* @__PURE__ */ new Map());
57
- /**
58
- * In-flight `execute` invocations keyed by physical stream id. Lives outside
59
- * `entriesRef` so `reset()` can drop tool-call state without orphaning the
60
- * cleanup the cancellation `onExecutionEnd` still needs.
61
- */
62
- const executingRef = useRef(/* @__PURE__ */ new Map());
63
- const acRef = useRef(new AbortController());
64
- const executingCountRef = useRef(0);
65
- const settledResolversRef = useRef([]);
66
- const rewriteCounterRef = useRef(0);
67
- /**
68
- * `true` until the first snapshot has been processed; `reset()` flips it
69
- * back to `true`. Snapshots observed while this is `true` are treated as
70
- * historical: their tool calls are recorded in `entriesRef` as restored
71
- * but no streamCall/execute fires. The next snapshot is processed as live.
72
- */
73
- const pendingRestoreRef = useRef(true);
74
- const getLogicalToolCallId = (streamId) => streamToLogicalRef.current.get(streamId) ?? streamId;
75
- const getWrappedTools = () => {
76
- const tools = getTools();
77
- if (!tools) return void 0;
78
- return Object.fromEntries(Object.entries(tools).map(([name, tool]) => {
79
- const execute = tool.execute;
80
- const streamCall = tool.streamCall;
81
- const toModelOutput = tool.toModelOutput;
82
- return [name, {
83
- ...tool,
84
- ...execute !== void 0 && { execute: (...[args, context]) => {
85
- if (skipExecuteStreamIdsRef.current.has(context.toolCallId)) return new Promise(() => {});
86
- return execute(args, {
87
- ...context,
88
- toolCallId: getLogicalToolCallId(context.toolCallId)
89
- });
90
- } },
91
- ...streamCall !== void 0 && { streamCall: (...[reader, context]) => streamCall(reader, {
92
- ...context,
93
- toolCallId: getLogicalToolCallId(context.toolCallId)
94
- }) },
95
- ...toModelOutput !== void 0 && { toModelOutput: (options) => toModelOutput({
96
- ...options,
97
- toolCallId: getLogicalToolCallId(options.toolCallId)
98
- }) }
99
- }];
100
- }));
101
- };
102
- const resolveAllSettledResolvers = () => {
103
- const resolvers = settledResolversRef.current;
104
- settledResolversRef.current = [];
105
- resolvers.forEach((resolve) => resolve());
106
- };
107
- const [controller] = useState(() => {
108
- const [stream, controller] = createAssistantStreamController();
109
- const transform = unstable_toolResultStream(getWrappedTools, () => acRef.current?.signal ?? new AbortController().signal, (toolCallId, payload) => {
110
- const logicalToolCallId = getLogicalToolCallId(toolCallId);
111
- return new Promise((resolve, reject) => {
112
- const previous = humanInputRef.current.get(logicalToolCallId);
113
- if (previous) previous.reject(/* @__PURE__ */ new Error("Human input request was superseded by a new request"));
114
- humanInputRef.current.set(logicalToolCallId, {
115
- resolve,
116
- reject
117
- });
118
- setToolStatuses((prev) => ({
119
- ...prev,
120
- [logicalToolCallId]: {
121
- type: "interrupt",
122
- payload: {
123
- type: "human",
124
- payload
125
- }
126
- }
127
- }));
128
- });
129
- }, {
130
- onExecutionStart: (streamId) => {
131
- if (skipExecuteStreamIdsRef.current.has(streamId)) return;
132
- const logicalToolCallId = getLogicalToolCallId(streamId);
133
- const abandoned = abandonedStreamIdsRef.current.has(streamId);
134
- executingRef.current.set(streamId, {
135
- logicalToolCallId,
136
- abandoned
137
- });
138
- executingCountRef.current++;
139
- if (!abandoned) setToolStatuses((prev) => ({
140
- ...prev,
141
- [logicalToolCallId]: { type: "executing" }
142
- }));
143
- },
144
- onExecutionEnd: (streamId) => {
145
- const info = executingRef.current.get(streamId);
146
- if (!info) return;
147
- executingRef.current.delete(streamId);
148
- executingCountRef.current--;
149
- if (!info.abandoned) setToolStatuses((prev) => {
150
- const next = { ...prev };
151
- delete next[info.logicalToolCallId];
152
- return next;
153
- });
154
- if (executingCountRef.current === 0) resolveAllSettledResolvers();
155
- }
156
- });
157
- stream.pipeThrough(transform).pipeThrough(new AssistantMetaTransformStream()).pipeTo(new WritableStream({ write(chunk) {
158
- if (chunk.type !== "result") return;
159
- const streamId = chunk.meta.toolCallId;
160
- const logicalToolCallId = getLogicalToolCallId(streamId);
161
- const entry = entriesRef.current.get(logicalToolCallId);
162
- if (abandonedStreamIdsRef.current.delete(streamId)) {
163
- streamToLogicalRef.current.delete(streamId);
164
- return;
165
- }
166
- if (!entry && skipExecuteStreamIdsRef.current.has(streamId)) return;
167
- if (entry?.hasResult) return;
168
- if (streamId !== logicalToolCallId) streamToLogicalRef.current.delete(streamId);
169
- onResult({
170
- type: "add-tool-result",
171
- toolCallId: logicalToolCallId,
172
- toolName: chunk.meta.toolName,
173
- result: chunk.result,
174
- isError: chunk.isError,
175
- ...chunk.artifact !== void 0 && { artifact: chunk.artifact },
176
- ...chunk.modelContent !== void 0 && { modelContent: chunk.modelContent }
177
- });
178
- } }));
179
- return controller;
180
- });
181
- useEffect(() => {
182
- const hasExecutableTool = (toolName) => {
183
- const tool = getTools()?.[toolName];
184
- return tool?.execute !== void 0 || tool?.streamCall !== void 0;
185
- };
186
- const shouldCloseArgsStream = ({ toolName, argsText, hasResult }) => {
187
- if (hasResult) return true;
188
- if (!hasExecutableTool(toolName)) return !state.isRunning && isArgsTextComplete(argsText);
189
- return isArgsTextComplete(argsText);
190
- };
191
- const startActiveEntry = (toolCallId, toolName, skipExecute) => {
192
- const toolCallController = controller.addToolCallPart({
193
- toolName,
194
- toolCallId
195
- });
196
- if (skipExecute) skipExecuteStreamIdsRef.current.add(toolCallId);
197
- const entry = {
198
- toolName,
199
- controller: toolCallController,
200
- streamId: toolCallId,
201
- argsText: "",
202
- hasResult: false,
203
- argsComplete: false
204
- };
205
- entriesRef.current.set(toolCallId, entry);
206
- return entry;
207
- };
208
- const restartArgsStream = (entry, toolCallId) => {
209
- if (!entry.controller) return;
210
- abandonedStreamIdsRef.current.add(entry.streamId);
211
- const wasSkipExecute = skipExecuteStreamIdsRef.current.has(entry.streamId);
212
- entry.controller.argsText.close();
213
- const newStreamId = `${toolCallId}:rewrite:${rewriteCounterRef.current++}`;
214
- streamToLogicalRef.current.set(newStreamId, toolCallId);
215
- const newController = controller.addToolCallPart({
216
- toolName: entry.toolName,
217
- toolCallId: newStreamId
218
- });
219
- if (wasSkipExecute) skipExecuteStreamIdsRef.current.add(newStreamId);
220
- if (process.env.NODE_ENV !== "production") console.warn("started replacement stream tool call", {
221
- toolCallId,
222
- streamToolCallId: newStreamId
223
- });
224
- entry.controller = newController;
225
- entry.streamId = newStreamId;
226
- entry.argsText = "";
227
- entry.argsComplete = false;
228
- };
229
- const processArgsText = (entry, content) => {
230
- if (!entry.controller) return;
231
- const hasResult = content.result !== void 0;
232
- if (content.argsText !== entry.argsText) {
233
- let shouldWriteArgsText = true;
234
- if (entry.argsComplete) if (isEquivalentCompleteArgsText(entry.argsText, content.argsText)) {
235
- entry.argsText = content.argsText;
236
- shouldWriteArgsText = false;
237
- } else {
238
- const canRestart = !entry.hasResult && !executingRef.current.has(entry.streamId);
239
- if (process.env.NODE_ENV !== "production") console.warn(canRestart ? "argsText updated after controller was closed, restarting tool args stream:" : "argsText updated after controller was closed:", {
240
- previous: entry.argsText,
241
- next: content.argsText
242
- });
243
- if (!canRestart) {
244
- entry.argsText = content.argsText;
245
- shouldWriteArgsText = false;
246
- } else restartArgsStream(entry, content.toolCallId);
247
- }
248
- else if (!content.argsText.startsWith(entry.argsText)) if (isArgsTextComplete(entry.argsText) && isArgsTextComplete(content.argsText) && isEquivalentCompleteArgsText(entry.argsText, content.argsText)) {
249
- const shouldClose = shouldCloseArgsStream({
250
- toolName: content.toolName,
251
- argsText: content.argsText,
252
- hasResult
253
- });
254
- if (shouldClose) entry.controller.argsText.close();
255
- entry.argsText = content.argsText;
256
- entry.argsComplete = shouldClose;
257
- shouldWriteArgsText = false;
258
- } else {
259
- if (process.env.NODE_ENV !== "production") console.warn("argsText rewrote previous snapshot, restarting tool args stream:", {
260
- previous: entry.argsText,
261
- next: content.argsText,
262
- toolCallId: content.toolCallId
263
- });
264
- restartArgsStream(entry, content.toolCallId);
265
- }
266
- if (shouldWriteArgsText) {
267
- const delta = content.argsText.slice(entry.argsText.length);
268
- entry.controller.argsText.append(delta);
269
- const shouldClose = shouldCloseArgsStream({
270
- toolName: content.toolName,
271
- argsText: content.argsText,
272
- hasResult
273
- });
274
- if (shouldClose) entry.controller.argsText.close();
275
- entry.argsText = content.argsText;
276
- entry.argsComplete = shouldClose;
277
- }
278
- }
279
- if (!entry.argsComplete) {
280
- if (shouldCloseArgsStream({
281
- toolName: content.toolName,
282
- argsText: content.argsText,
283
- hasResult
284
- })) {
285
- entry.controller.argsText.close();
286
- entry.argsText = content.argsText;
287
- entry.argsComplete = true;
288
- }
289
- }
290
- };
291
- const processMessages = (messages) => {
292
- const isRestore = pendingRestoreRef.current;
293
- messages.forEach((message) => {
294
- message.content.forEach((content) => {
295
- if (content.type !== "tool-call") return;
296
- const existing = entriesRef.current.get(content.toolCallId);
297
- if (isRestore) {
298
- if (!existing?.controller) entriesRef.current.set(content.toolCallId, {
299
- toolName: content.toolName,
300
- argsText: content.argsText,
301
- hasResult: content.result !== void 0
302
- });
303
- if (content.messages) processMessages(content.messages);
304
- return;
305
- }
306
- let entry = existing;
307
- if (entry && !entry.controller) {
308
- if (!(content.argsText !== entry.argsText || content.result !== void 0 !== entry.hasResult)) {
309
- if (content.messages) processMessages(content.messages);
310
- return;
311
- }
312
- entriesRef.current.delete(content.toolCallId);
313
- entry = void 0;
314
- }
315
- if (!entry) entry = startActiveEntry(content.toolCallId, content.toolName, content.result !== void 0);
316
- processArgsText(entry, content);
317
- if (content.result !== void 0 && !entry.hasResult) {
318
- const { controller: activeController } = entry;
319
- if (!activeController) return;
320
- entry.hasResult = true;
321
- entry.argsComplete = true;
322
- activeController.setResponse(new ToolResponse({
323
- result: content.result,
324
- artifact: content.artifact,
325
- isError: content.isError,
326
- ...content.modelContent !== void 0 ? { modelContent: content.modelContent } : {}
327
- }));
328
- activeController.close();
329
- }
330
- if (content.messages) processMessages(content.messages);
331
- });
332
- });
333
- };
334
- processMessages(state.messages);
335
- pendingRestoreRef.current = false;
336
- }, [
337
- state,
338
- controller,
339
- getTools
340
- ]);
341
- const abort = () => {
342
- humanInputRef.current.forEach(({ reject }) => {
343
- reject(/* @__PURE__ */ new Error("Tool execution aborted"));
344
- });
345
- humanInputRef.current.clear();
346
- acRef.current.abort();
347
- acRef.current = new AbortController();
348
- if (executingCountRef.current === 0) return Promise.resolve();
349
- return new Promise((resolve) => {
350
- settledResolversRef.current.push(resolve);
351
- });
352
- };
353
- return {
354
- reset: () => {
355
- pendingRestoreRef.current = true;
356
- entriesRef.current.clear();
357
- abort().finally(() => {
358
- executingRef.current.clear();
359
- streamToLogicalRef.current.clear();
360
- rewriteCounterRef.current = 0;
361
- });
362
- },
363
- abort,
364
- resume: (toolCallId, payload) => {
365
- const handlers = humanInputRef.current.get(toolCallId);
366
- if (handlers) {
367
- humanInputRef.current.delete(toolCallId);
368
- setToolStatuses((prev) => ({
369
- ...prev,
370
- [toolCallId]: { type: "executing" }
371
- }));
372
- handlers.resolve(payload);
373
- } else throw new Error(`Tool call ${toolCallId} is not waiting for human input`);
374
- }
375
- };
376
- }
377
- //#endregion
378
- export { useToolInvocations };
379
-
380
- //# sourceMappingURL=useToolInvocations.js.map