@assistant-ui/react 0.11.21 → 0.11.22

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 (176) hide show
  1. package/dist/augmentations.d.ts +20 -9
  2. package/dist/augmentations.d.ts.map +1 -1
  3. package/dist/client/AssistantClient.d.ts +6 -7
  4. package/dist/client/AssistantClient.d.ts.map +1 -1
  5. package/dist/client/AssistantClient.js +9 -4
  6. package/dist/client/AssistantClient.js.map +1 -1
  7. package/dist/client/NoOpComposerClient.d.ts +9 -0
  8. package/dist/client/NoOpComposerClient.d.ts.map +1 -0
  9. package/dist/client/NoOpComposerClient.js +59 -0
  10. package/dist/client/NoOpComposerClient.js.map +1 -0
  11. package/dist/client/ThreadMessageClient.d.ts +14 -0
  12. package/dist/client/ThreadMessageClient.d.ts.map +1 -0
  13. package/dist/client/ThreadMessageClient.js +140 -0
  14. package/dist/client/ThreadMessageClient.js.map +1 -0
  15. package/dist/client/types/Part.d.ts +5 -0
  16. package/dist/client/types/Part.d.ts.map +1 -1
  17. package/dist/client/types/Thread.d.ts +8 -0
  18. package/dist/client/types/Thread.d.ts.map +1 -1
  19. package/dist/context/providers/MessageProvider.d.ts +1 -8
  20. package/dist/context/providers/MessageProvider.d.ts.map +1 -1
  21. package/dist/context/providers/MessageProvider.js +4 -181
  22. package/dist/context/providers/MessageProvider.js.map +1 -1
  23. package/dist/context/providers/TextMessagePartProvider.d.ts.map +1 -1
  24. package/dist/context/providers/TextMessagePartProvider.js +3 -1
  25. package/dist/context/providers/TextMessagePartProvider.js.map +1 -1
  26. package/dist/context/react/AssistantApiContext.d.ts +3 -1
  27. package/dist/context/react/AssistantApiContext.d.ts.map +1 -1
  28. package/dist/context/react/AssistantApiContext.js +17 -4
  29. package/dist/context/react/AssistantApiContext.js.map +1 -1
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal.d.ts +1 -0
  34. package/dist/internal.d.ts.map +1 -1
  35. package/dist/internal.js +5 -1
  36. package/dist/internal.js.map +1 -1
  37. package/dist/legacy-runtime/client/MessagePartRuntimeClient.d.ts.map +1 -1
  38. package/dist/legacy-runtime/client/MessagePartRuntimeClient.js +9 -10
  39. package/dist/legacy-runtime/client/MessagePartRuntimeClient.js.map +1 -1
  40. package/dist/legacy-runtime/client/ThreadRuntimeClient.d.ts.map +1 -1
  41. package/dist/legacy-runtime/client/ThreadRuntimeClient.js +6 -0
  42. package/dist/legacy-runtime/client/ThreadRuntimeClient.js.map +1 -1
  43. package/dist/legacy-runtime/hooks/AssistantContext.js +1 -1
  44. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  45. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +1 -1
  46. package/dist/legacy-runtime/hooks/AttachmentContext.js +1 -1
  47. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  48. package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +1 -1
  49. package/dist/legacy-runtime/hooks/ComposerContext.js +1 -1
  50. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  51. package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +1 -1
  52. package/dist/legacy-runtime/hooks/MessageContext.js +1 -1
  53. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  54. package/dist/legacy-runtime/hooks/MessagePartContext.js +1 -1
  55. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  56. package/dist/legacy-runtime/hooks/ThreadContext.js +1 -1
  57. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  58. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +1 -1
  59. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  60. package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +6 -0
  61. package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts.map +1 -1
  62. package/dist/legacy-runtime/runtime/MessagePartRuntime.js +13 -0
  63. package/dist/legacy-runtime/runtime/MessagePartRuntime.js.map +1 -1
  64. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts +1 -0
  65. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts.map +1 -1
  66. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +2 -0
  67. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -1
  68. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  69. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +5 -2
  70. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  71. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +2 -1
  72. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +1 -1
  73. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +3 -3
  74. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
  75. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +9 -1
  76. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
  77. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +48 -7
  78. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
  79. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts +2 -1
  80. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts.map +1 -1
  81. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.js.map +1 -1
  82. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts +6 -1
  83. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts.map +1 -1
  84. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.d.ts +5 -1
  85. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.d.ts.map +1 -1
  86. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.d.ts +2 -1
  87. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.d.ts.map +1 -1
  88. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.js +6 -1
  89. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.js.map +1 -1
  90. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.d.ts +4 -1
  91. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.d.ts.map +1 -1
  92. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js +5 -1
  93. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js.map +1 -1
  94. package/dist/legacy-runtime/runtime-cores/external-store/createMessageConverter.d.ts +3 -2
  95. package/dist/legacy-runtime/runtime-cores/external-store/createMessageConverter.d.ts.map +1 -1
  96. package/dist/legacy-runtime/runtime-cores/external-store/createMessageConverter.js +6 -4
  97. package/dist/legacy-runtime/runtime-cores/external-store/createMessageConverter.js.map +1 -1
  98. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.d.ts +8 -3
  99. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.d.ts.map +1 -1
  100. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js +20 -6
  101. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js.map +1 -1
  102. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.d.ts +2 -1
  103. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.d.ts.map +1 -1
  104. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.js +3 -0
  105. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.js.map +1 -1
  106. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.d.ts.map +1 -1
  107. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.js +3 -0
  108. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.js.map +1 -1
  109. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts +2 -0
  110. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  111. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  112. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  113. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js +1 -1
  114. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js.map +1 -1
  115. package/dist/model-context/frame/AssistantFrameProvider.d.ts.map +1 -1
  116. package/dist/model-context/frame/AssistantFrameProvider.js +6 -1
  117. package/dist/model-context/frame/AssistantFrameProvider.js.map +1 -1
  118. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  119. package/dist/primitives/message/MessageParts.js +12 -3
  120. package/dist/primitives/message/MessageParts.js.map +1 -1
  121. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  122. package/dist/primitives/message/MessagePartsGrouped.js +11 -2
  123. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  124. package/dist/types/AssistantTypes.d.ts +2 -2
  125. package/dist/types/AssistantTypes.d.ts.map +1 -1
  126. package/dist/types/MessagePartComponentTypes.d.ts +1 -0
  127. package/dist/types/MessagePartComponentTypes.d.ts.map +1 -1
  128. package/dist/types/MessagePartTypes.d.ts +1 -0
  129. package/dist/types/MessagePartTypes.d.ts.map +1 -1
  130. package/dist/utils/smooth/SmoothContext.d.ts +6 -6
  131. package/package.json +2 -2
  132. package/src/augmentations.ts +21 -24
  133. package/src/client/AssistantClient.ts +15 -8
  134. package/src/client/NoOpComposerClient.tsx +56 -0
  135. package/src/client/ThreadMessageClient.tsx +161 -0
  136. package/src/client/types/Attachment.ts +1 -1
  137. package/src/client/types/Composer.ts +1 -1
  138. package/src/client/types/Message.ts +1 -1
  139. package/src/client/types/Part.ts +7 -1
  140. package/src/client/types/Thread.ts +11 -1
  141. package/src/client/types/ThreadListItem.ts +1 -1
  142. package/src/context/providers/MessageProvider.tsx +4 -222
  143. package/src/context/providers/TextMessagePartProvider.tsx +3 -1
  144. package/src/context/react/AssistantApiContext.tsx +27 -5
  145. package/src/index.ts +3 -0
  146. package/src/internal.ts +4 -0
  147. package/src/legacy-runtime/client/MessagePartRuntimeClient.ts +12 -13
  148. package/src/legacy-runtime/client/ThreadRuntimeClient.ts +6 -0
  149. package/src/legacy-runtime/hooks/AssistantContext.ts +1 -1
  150. package/src/legacy-runtime/hooks/AttachmentContext.ts +3 -1
  151. package/src/legacy-runtime/hooks/ComposerContext.ts +3 -1
  152. package/src/legacy-runtime/hooks/MessageContext.ts +3 -1
  153. package/src/legacy-runtime/hooks/MessagePartContext.ts +1 -1
  154. package/src/legacy-runtime/hooks/ThreadContext.ts +1 -1
  155. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +1 -1
  156. package/src/legacy-runtime/runtime/MessagePartRuntime.ts +23 -0
  157. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +2 -0
  158. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +10 -3
  159. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +4 -2
  160. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +73 -6
  161. package/src/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.tsx +2 -0
  162. package/src/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.tsx +7 -1
  163. package/src/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.tsx +4 -1
  164. package/src/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.tsx +8 -1
  165. package/src/legacy-runtime/runtime-cores/external-store/auto-status.tsx +11 -3
  166. package/src/legacy-runtime/runtime-cores/external-store/createMessageConverter.tsx +9 -2
  167. package/src/legacy-runtime/runtime-cores/external-store/external-message-converter.tsx +33 -5
  168. package/src/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.tsx +5 -0
  169. package/src/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.tsx +4 -0
  170. package/src/legacy-runtime/runtime-cores/utils/MessageRepository.tsx +1 -1
  171. package/src/model-context/frame/AssistantFrameProvider.ts +5 -0
  172. package/src/primitives/message/MessageParts.tsx +11 -3
  173. package/src/primitives/message/MessagePartsGrouped.tsx +10 -2
  174. package/src/types/AssistantTypes.ts +2 -2
  175. package/src/types/MessagePartComponentTypes.tsx +1 -0
  176. package/src/types/MessagePartTypes.ts +1 -0
@@ -30,6 +30,12 @@ export type MessagePartRuntime = {
30
30
  */
31
31
  addToolResult(result: any | ToolResponse<any>): void;
32
32
 
33
+ /**
34
+ * Resume an interrupted tool call with a payload.
35
+ * This is useful when a tool has interrupted its execution and is waiting for user input.
36
+ */
37
+ resumeToolCall(payload: unknown): void;
38
+
33
39
  readonly path: MessagePartRuntimePath;
34
40
  getState(): MessagePartState;
35
41
  subscribe(callback: () => void): Unsubscribe;
@@ -50,6 +56,7 @@ export class MessagePartRuntimeImpl implements MessagePartRuntime {
50
56
 
51
57
  protected __internal_bindMethods() {
52
58
  this.addToolResult = this.addToolResult.bind(this);
59
+ this.resumeToolCall = this.resumeToolCall.bind(this);
53
60
  this.getState = this.getState.bind(this);
54
61
  this.subscribe = this.subscribe.bind(this);
55
62
  }
@@ -88,6 +95,22 @@ export class MessagePartRuntimeImpl implements MessagePartRuntime {
88
95
  });
89
96
  }
90
97
 
98
+ public resumeToolCall(payload: unknown) {
99
+ const state = this.contentBinding.getState();
100
+ if (!state) throw new Error("Message part is not available");
101
+
102
+ if (state.type !== "tool-call")
103
+ throw new Error("Tried to resume tool call on non-tool message part");
104
+
105
+ if (!this.threadApi) throw new Error("Thread API is not available");
106
+
107
+ const toolCallId = state.toolCallId;
108
+ this.threadApi.getState().resumeToolCall({
109
+ toolCallId,
110
+ payload,
111
+ });
112
+ }
113
+
91
114
  public subscribe(callback: () => void) {
92
115
  return this.contentBinding.subscribe(callback);
93
116
  }
@@ -2,6 +2,7 @@ import { ReadonlyJSONValue } from "assistant-stream/utils";
2
2
  import { ThreadMessage } from "../../../types";
3
3
  import { AttachmentAdapter, ThreadHistoryAdapter } from "..";
4
4
  import { UserCommands } from "../../../augmentations";
5
+ import type { ToolExecutionStatus } from "./useToolInvocations";
5
6
 
6
7
  // Message part types
7
8
  export type TextPart = {
@@ -56,6 +57,7 @@ export type AssistantTransportState = {
56
57
  export type AssistantTransportConnectionMetadata = {
57
58
  pendingCommands: AssistantTransportCommand[];
58
59
  isSending: boolean;
60
+ toolStatuses: Record<string, ToolExecutionStatus>;
59
61
  };
60
62
 
61
63
  export type AssistantTransportStateConverter<T> = (
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import {
4
- ReadonlyJSONObject,
5
- ReadonlyJSONValue,
4
+ type ReadonlyJSONObject,
5
+ type ReadonlyJSONValue,
6
6
  asAsyncIterableStream,
7
7
  } from "assistant-stream/utils";
8
8
  import { AppendMessage } from "../../../types";
@@ -26,7 +26,7 @@ import {
26
26
  import { useCommandQueue } from "./commandQueue";
27
27
  import { useRunManager } from "./runManager";
28
28
  import { useConvertedState } from "./useConvertedState";
29
- import { useToolInvocations } from "./useToolInvocations";
29
+ import { ToolExecutionStatus, useToolInvocations } from "./useToolInvocations";
30
30
  import { toAISDKTools, getEnabledTools, createRequestHeaders } from "./utils";
31
31
  import { useRemoteThreadListRuntime } from "../remote-thread-list/useRemoteThreadListRuntime";
32
32
  import { InMemoryThreadListAdapter } from "../remote-thread-list/adapter/in-memory";
@@ -177,6 +177,11 @@ const useAssistantTransportThreadRuntime = <T,>(
177
177
  },
178
178
  });
179
179
 
180
+ // Tool execution status state
181
+ const [toolStatuses, setToolStatuses] = useState<
182
+ Record<string, ToolExecutionStatus>
183
+ >({});
184
+
180
185
  // Reactive conversion of agent state + connection metadata → UI state
181
186
  const pendingCommands = useMemo(
182
187
  () => [...commandQueue.state.inTransit, ...commandQueue.state.queued],
@@ -187,6 +192,7 @@ const useAssistantTransportThreadRuntime = <T,>(
187
192
  agentStateRef.current,
188
193
  pendingCommands,
189
194
  runManager.isRunning,
195
+ toolStatuses,
190
196
  );
191
197
 
192
198
  // Create runtime
@@ -266,6 +272,7 @@ const useAssistantTransportThreadRuntime = <T,>(
266
272
  state: converted,
267
273
  getTools: () => runtime.thread.getModelContext().tools,
268
274
  onResult: commandQueue.enqueue,
275
+ setToolStatuses,
269
276
  });
270
277
 
271
278
  return runtime;
@@ -4,15 +4,17 @@ import type {
4
4
  AssistantTransportState,
5
5
  AssistantTransportStateConverter,
6
6
  } from "./types";
7
+ import type { ToolExecutionStatus } from "./useToolInvocations";
7
8
 
8
9
  export function useConvertedState<T>(
9
10
  converter: AssistantTransportStateConverter<T>,
10
11
  agentState: T,
11
12
  pendingCommands: AssistantTransportCommand[],
12
13
  isSending: boolean,
14
+ toolStatuses: Record<string, ToolExecutionStatus>,
13
15
  ): AssistantTransportState {
14
16
  return useMemo(
15
- () => converter(agentState, { pendingCommands, isSending }),
16
- [converter, agentState, pendingCommands, isSending],
17
+ () => converter(agentState, { pendingCommands, isSending, toolStatuses }),
18
+ [converter, agentState, pendingCommands, isSending, toolStatuses],
17
19
  );
18
20
  }
@@ -12,7 +12,7 @@ import type {
12
12
  } from "./types";
13
13
  import {
14
14
  AssistantMetaTransformStream,
15
- ReadonlyJSONValue,
15
+ type ReadonlyJSONValue,
16
16
  } from "assistant-stream/utils";
17
17
 
18
18
  const isArgsTextComplete = (argsText: string) => {
@@ -28,12 +28,24 @@ type UseToolInvocationsParams = {
28
28
  state: AssistantTransportState;
29
29
  getTools: () => Record<string, Tool> | undefined;
30
30
  onResult: (command: AssistantTransportCommand) => void;
31
+ setToolStatuses: (
32
+ updater:
33
+ | Record<string, ToolExecutionStatus>
34
+ | ((
35
+ prev: Record<string, ToolExecutionStatus>,
36
+ ) => Record<string, ToolExecutionStatus>),
37
+ ) => void;
31
38
  };
32
39
 
40
+ export type ToolExecutionStatus =
41
+ | { type: "executing" }
42
+ | { type: "interrupt"; payload: unknown };
43
+
33
44
  export function useToolInvocations({
34
45
  state,
35
46
  getTools,
36
47
  onResult,
48
+ setToolStatuses,
37
49
  }: UseToolInvocationsParams) {
38
50
  const lastToolStates = useRef<
39
51
  Record<
@@ -46,12 +58,39 @@ export function useToolInvocations({
46
58
  >
47
59
  >({});
48
60
 
61
+ const interruptedToolsRef = useRef<
62
+ Map<
63
+ string,
64
+ {
65
+ resolve: (payload: unknown) => void;
66
+ reject: (reason: unknown) => void;
67
+ }
68
+ >
69
+ >(new Map());
70
+
49
71
  const acRef = useRef<AbortController>(new AbortController());
50
72
  const [controller] = useState(() => {
51
73
  const [stream, controller] = createAssistantStreamController();
52
74
  const transform = unstable_toolResultStream(
53
75
  getTools,
54
76
  () => acRef.current?.signal ?? new AbortController().signal,
77
+ (toolCallId: string, payload: unknown) => {
78
+ return new Promise<unknown>((resolve, reject) => {
79
+ // Reject previous interrupt if it exists
80
+ const previous = interruptedToolsRef.current.get(toolCallId);
81
+ if (previous) {
82
+ previous.reject(
83
+ new Error("Interrupt was superseded by a new interrupt"),
84
+ );
85
+ }
86
+
87
+ interruptedToolsRef.current.set(toolCallId, { resolve, reject });
88
+ setToolStatuses((prev) => ({
89
+ ...prev,
90
+ [toolCallId]: { type: "interrupt", payload },
91
+ }));
92
+ });
93
+ },
55
94
  );
56
95
  stream
57
96
  .pipeThrough(transform)
@@ -72,6 +111,13 @@ export function useToolInvocations({
72
111
  isError: chunk.isError,
73
112
  ...(chunk.artifact && { artifact: chunk.artifact }),
74
113
  });
114
+
115
+ // Clear status when result is set
116
+ setToolStatuses((prev) => {
117
+ const next = { ...prev };
118
+ delete next[chunk.meta.toolCallId];
119
+ return next;
120
+ });
75
121
  }
76
122
  },
77
123
  }),
@@ -159,15 +205,36 @@ export function useToolInvocations({
159
205
  }
160
206
  }, [state, controller, onResult]);
161
207
 
208
+ const abort = () => {
209
+ interruptedToolsRef.current.forEach(({ reject }) => {
210
+ reject(new Error("Tool execution aborted"));
211
+ });
212
+ interruptedToolsRef.current.clear();
213
+ setToolStatuses({});
214
+
215
+ acRef.current.abort();
216
+ acRef.current = new AbortController();
217
+ };
218
+
162
219
  return {
163
220
  reset: () => {
164
- acRef.current.abort();
165
- acRef.current = new AbortController();
221
+ abort();
166
222
  isInititialState.current = true;
167
223
  },
168
- abort: () => {
169
- acRef.current.abort();
170
- acRef.current = new AbortController();
224
+ abort,
225
+ resume: (toolCallId: string, payload: unknown) => {
226
+ const handlers = interruptedToolsRef.current.get(toolCallId);
227
+ if (handlers) {
228
+ interruptedToolsRef.current.delete(toolCallId);
229
+ setToolStatuses((prev) => {
230
+ const next = { ...prev };
231
+ delete next[toolCallId];
232
+ return next;
233
+ });
234
+ handlers.resolve(payload);
235
+ } else {
236
+ throw new Error(`Tool call ${toolCallId} is not interrupted`);
237
+ }
171
238
  },
172
239
  };
173
240
  }
@@ -6,6 +6,7 @@ import {
6
6
  import { DefaultThreadComposerRuntimeCore } from "../composer/DefaultThreadComposerRuntimeCore";
7
7
  import {
8
8
  AddToolResultOptions,
9
+ ResumeToolCallOptions,
9
10
  ThreadSuggestion,
10
11
  SubmitFeedbackOptions,
11
12
  ThreadRuntimeCore,
@@ -46,6 +47,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
46
47
  public abstract startRun(config: StartRunConfig): void;
47
48
  public abstract resumeRun(config: ResumeRunConfig): void;
48
49
  public abstract addToolResult(options: AddToolResultOptions): void;
50
+ public abstract resumeToolCall(options: ResumeToolCallOptions): void;
49
51
  public abstract cancelRun(): void;
50
52
  public abstract unstable_loadExternalState(state: any): void;
51
53
 
@@ -1,4 +1,4 @@
1
- import { ReadonlyJSONValue } from "assistant-stream/utils";
1
+ import type { ReadonlyJSONValue } from "assistant-stream/utils";
2
2
  import { ModelContext } from "../../../model-context";
3
3
  import { AppendMessage, ThreadMessage } from "../../../types";
4
4
  import { RunConfig } from "../../../types/AssistantTypes";
@@ -33,6 +33,11 @@ export type AddToolResultOptions = {
33
33
  artifact?: ReadonlyJSONValue | undefined;
34
34
  };
35
35
 
36
+ export type ResumeToolCallOptions = {
37
+ toolCallId: string;
38
+ payload: unknown;
39
+ };
40
+
36
41
  export type SubmitFeedbackOptions = {
37
42
  messageId: string;
38
43
  type: "negative" | "positive";
@@ -86,6 +91,7 @@ export type ThreadRuntimeCore = Readonly<{
86
91
  cancelRun: () => void;
87
92
 
88
93
  addToolResult: (options: AddToolResultOptions) => void;
94
+ resumeToolCall: (options: ResumeToolCallOptions) => void;
89
95
 
90
96
  speak: (messageId: string) => void;
91
97
  stopSpeaking: () => void;
@@ -10,7 +10,7 @@ import { FeedbackAdapter } from "../adapters/feedback/FeedbackAdapter";
10
10
  import { SpeechSynthesisAdapter } from "../adapters/speech/SpeechAdapterTypes";
11
11
  import { ThreadMessageLike } from "./ThreadMessageLike";
12
12
  import { ExportedMessageRepository } from "../utils/MessageRepository";
13
- import { ReadonlyJSONValue } from "assistant-stream/utils";
13
+ import type { ReadonlyJSONValue } from "assistant-stream/utils";
14
14
 
15
15
  export type ExternalStoreThreadData<TState extends "regular" | "archived"> = {
16
16
  status: TState;
@@ -77,6 +77,9 @@ type ExternalStoreAdapterBase<T> = {
77
77
  onAddToolResult?:
78
78
  | ((options: AddToolResultOptions) => Promise<void> | void)
79
79
  | undefined;
80
+ onResumeToolCall?:
81
+ | ((options: { toolCallId: string; payload: unknown }) => void)
82
+ | undefined;
80
83
  convertMessage?: ExternalStoreMessageConverter<T> | undefined;
81
84
  adapters?:
82
85
  | {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AddToolResultOptions,
3
3
  ResumeRunConfig,
4
+ ResumeToolCallOptions,
4
5
  StartRunConfig,
5
6
  ThreadSuggestion,
6
7
  } from "../core/ThreadRuntimeCore";
@@ -161,7 +162,7 @@ export class ExternalStoreThreadRuntimeCore
161
162
  if (!store.convertMessage) return m;
162
163
 
163
164
  const isLast = idx === store.messages!.length - 1;
164
- const autoStatus = getAutoStatus(isLast, isRunning, false);
165
+ const autoStatus = getAutoStatus(isLast, isRunning, false, false);
165
166
 
166
167
  if (
167
168
  cache &&
@@ -309,6 +310,12 @@ export class ExternalStoreThreadRuntimeCore
309
310
  this._store.onAddToolResult?.(options);
310
311
  }
311
312
 
313
+ public resumeToolCall(options: ResumeToolCallOptions) {
314
+ if (!this._store.onResumeToolCall)
315
+ throw new Error("Runtime does not support resuming tool calls.");
316
+ this._store.onResumeToolCall(options);
317
+ }
318
+
312
319
  public override reset(initialMessages?: readonly ThreadMessageLike[]) {
313
320
  const repo = new MessageRepository();
314
321
  repo.import(ExportedMessageRepository.fromArray(initialMessages ?? []));
@@ -11,16 +11,24 @@ const AUTO_STATUS_PENDING = Object.freeze({
11
11
  reason: "tool-calls",
12
12
  });
13
13
 
14
+ const AUTO_STATUS_SUSPENDED = Object.freeze({
15
+ type: "requires-action",
16
+ reason: "interrupt",
17
+ });
18
+
14
19
  export const isAutoStatus = (status: MessageStatus) =>
15
20
  status === AUTO_STATUS_RUNNING || status === AUTO_STATUS_COMPLETE;
16
21
 
17
22
  export const getAutoStatus = (
18
23
  isLast: boolean,
19
24
  isRunning: boolean,
25
+ hasSuspendedToolCalls: boolean,
20
26
  hasPendingToolCalls: boolean,
21
27
  ) =>
22
28
  isLast && isRunning
23
29
  ? AUTO_STATUS_RUNNING
24
- : hasPendingToolCalls
25
- ? AUTO_STATUS_PENDING
26
- : AUTO_STATUS_COMPLETE;
30
+ : hasSuspendedToolCalls
31
+ ? AUTO_STATUS_SUSPENDED
32
+ : hasPendingToolCalls
33
+ ? AUTO_STATUS_PENDING
34
+ : AUTO_STATUS_COMPLETE;
@@ -16,20 +16,27 @@ export const createMessageConverter = <T extends object>(
16
16
  messages,
17
17
  isRunning,
18
18
  joinStrategy,
19
+ metadata,
19
20
  }: {
20
21
  messages: T[];
21
22
  isRunning: boolean;
22
23
  joinStrategy?: "concat-content" | "none" | undefined;
24
+ metadata?: useExternalMessageConverter.Metadata;
23
25
  }) => {
24
26
  return useExternalMessageConverter<T>({
25
27
  callback,
26
28
  messages,
27
29
  isRunning,
28
30
  joinStrategy,
31
+ metadata,
29
32
  });
30
33
  },
31
- toThreadMessages: (messages: T[], isRunning = false) => {
32
- return convertExternalMessages(messages, callback, isRunning);
34
+ toThreadMessages: (
35
+ messages: T[],
36
+ isRunning = false,
37
+ metadata: useExternalMessageConverter.Metadata = {},
38
+ ) => {
39
+ return convertExternalMessages(messages, callback, isRunning, metadata);
33
40
  },
34
41
  toOriginalMessages: (
35
42
  input: ThreadState | ThreadMessage | ThreadMessage["content"][number],
@@ -9,6 +9,7 @@ import {
9
9
  import { fromThreadMessageLike, ThreadMessageLike } from "./ThreadMessageLike";
10
10
  import { getAutoStatus, isAutoStatus } from "./auto-status";
11
11
  import { ToolCallMessagePart } from "../../../types";
12
+ import { ToolExecutionStatus } from "../assistant-transport/useToolInvocations";
12
13
 
13
14
  export namespace useExternalMessageConverter {
14
15
  export type Message =
@@ -26,7 +27,14 @@ export namespace useExternalMessageConverter {
26
27
  isError?: boolean;
27
28
  };
28
29
 
29
- export type Callback<T> = (message: T) => Message | Message[];
30
+ export type Metadata = {
31
+ readonly toolStatuses?: Record<string, ToolExecutionStatus>;
32
+ };
33
+
34
+ export type Callback<T> = (
35
+ message: T,
36
+ metadata: Metadata,
37
+ ) => Message | Message[];
30
38
  }
31
39
 
32
40
  type CallbackResult<T> = {
@@ -218,10 +226,11 @@ export const convertExternalMessages = <T extends WeakKey>(
218
226
  messages: T[],
219
227
  callback: useExternalMessageConverter.Callback<T>,
220
228
  isRunning: boolean,
229
+ metadata: useExternalMessageConverter.Metadata,
221
230
  ) => {
222
231
  const callbackResults: CallbackResult<T>[] = [];
223
232
  for (const message of messages) {
224
- const output = callback(message);
233
+ const output = callback(message, metadata);
225
234
  const outputs = Array.isArray(output) ? output : [output];
226
235
  const result = { input: message, outputs };
227
236
  callbackResults.push(result);
@@ -232,12 +241,22 @@ export const convertExternalMessages = <T extends WeakKey>(
232
241
  return chunks.map((message, idx) => {
233
242
  const isLast = idx === chunks.length - 1;
234
243
  const joined = joinExternalMessages(message.outputs);
244
+ const hasSuspendedToolCalls =
245
+ typeof joined.content === "object" &&
246
+ joined.content.some(
247
+ (c) => c.type === "tool-call" && c.result === undefined,
248
+ );
235
249
  const hasPendingToolCalls =
236
250
  typeof joined.content === "object" &&
237
251
  joined.content.some(
238
252
  (c) => c.type === "tool-call" && c.result === undefined,
239
253
  );
240
- const autoStatus = getAutoStatus(isLast, isRunning, hasPendingToolCalls);
254
+ const autoStatus = getAutoStatus(
255
+ isLast,
256
+ isRunning,
257
+ hasSuspendedToolCalls,
258
+ hasPendingToolCalls,
259
+ );
241
260
  const newMessage = fromThreadMessageLike(
242
261
  joined,
243
262
  idx.toString(),
@@ -253,14 +272,17 @@ export const useExternalMessageConverter = <T extends WeakKey>({
253
272
  messages,
254
273
  isRunning,
255
274
  joinStrategy,
275
+ metadata,
256
276
  }: {
257
277
  callback: useExternalMessageConverter.Callback<T>;
258
278
  messages: T[];
259
279
  isRunning: boolean;
260
280
  joinStrategy?: "concat-content" | "none" | undefined;
281
+ metadata?: useExternalMessageConverter.Metadata | undefined;
261
282
  }) => {
262
283
  const state = useMemo(
263
284
  () => ({
285
+ metadata: metadata ?? {},
264
286
  callback,
265
287
  callbackCache: new WeakMap<T, CallbackResult<T>>(),
266
288
  chunkCache: new WeakMap<
@@ -269,7 +291,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
269
291
  >(),
270
292
  converterCache: new ThreadMessageConverter(),
271
293
  }),
272
- [callback],
294
+ [callback, metadata],
273
295
  );
274
296
 
275
297
  return useMemo(() => {
@@ -277,7 +299,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
277
299
  for (const message of messages) {
278
300
  let result = state.callbackCache.get(message);
279
301
  if (!result) {
280
- const output = state.callback(message);
302
+ const output = state.callback(message, state.metadata);
281
303
  const outputs = Array.isArray(output) ? output : [output];
282
304
  result = { input: message, outputs };
283
305
  state.callbackCache.set(message, result);
@@ -304,6 +326,11 @@ export const useExternalMessageConverter = <T extends WeakKey>({
304
326
  const isLast = idx === chunks.length - 1;
305
327
 
306
328
  const joined = joinExternalMessages(message.outputs);
329
+ const hasSuspendedToolCalls =
330
+ typeof joined.content === "object" &&
331
+ joined.content.some(
332
+ (c) => c.type === "tool-call" && c.result === undefined,
333
+ );
307
334
  const hasPendingToolCalls =
308
335
  typeof joined.content === "object" &&
309
336
  joined.content.some(
@@ -312,6 +339,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
312
339
  const autoStatus = getAutoStatus(
313
340
  isLast,
314
341
  isRunning,
342
+ hasSuspendedToolCalls,
315
343
  hasPendingToolCalls,
316
344
  );
317
345
 
@@ -5,6 +5,7 @@ import { shouldContinue } from "./shouldContinue";
5
5
  import { LocalRuntimeOptionsBase } from "./LocalRuntimeOptions";
6
6
  import {
7
7
  AddToolResultOptions,
8
+ ResumeToolCallOptions,
8
9
  ThreadSuggestion,
9
10
  ThreadRuntimeCore,
10
11
  StartRunConfig,
@@ -463,4 +464,8 @@ export class LocalThreadRuntimeCore
463
464
  this.performRoundtrip(parentId, message, this._lastRunConfig);
464
465
  }
465
466
  }
467
+
468
+ public resumeToolCall(_options: ResumeToolCallOptions) {
469
+ throw new Error("Local runtime does not support resuming tool calls.");
470
+ }
466
471
  }
@@ -36,6 +36,10 @@ export const EMPTY_THREAD_CORE: ThreadRuntimeCore = {
36
36
  throw EMPTY_THREAD_ERROR;
37
37
  },
38
38
 
39
+ resumeToolCall() {
40
+ throw EMPTY_THREAD_ERROR;
41
+ },
42
+
39
43
  speak() {
40
44
  throw EMPTY_THREAD_ERROR;
41
45
  },
@@ -67,7 +67,7 @@ export const ExportedMessageRepository = {
67
67
  fromThreadMessageLike(
68
68
  m,
69
69
  generateId(),
70
- getAutoStatus(false, false, false),
70
+ getAutoStatus(false, false, false, false),
71
71
  ),
72
72
  );
73
73
 
@@ -133,6 +133,11 @@ export class AssistantFrameProvider {
133
133
  ? await tool.execute(message.args, {
134
134
  toolCallId: message.id,
135
135
  abortSignal: new AbortController().signal,
136
+ interrupt: async () => {
137
+ throw new Error(
138
+ "Tool interrupt is not supported in frame context",
139
+ );
140
+ },
136
141
  })
137
142
  : undefined;
138
143
  } catch (e) {
@@ -241,11 +241,19 @@ const MessagePartComponent: FC<MessagePartComponentProps> = ({
241
241
 
242
242
  const type = part.type;
243
243
  if (type === "tool-call") {
244
- const addResult = (result: any) => api.part().addToolResult(result);
244
+ const addResult = api.part().addToolResult;
245
+ const resume = api.part().resumeToolCall;
245
246
  if ("Override" in tools)
246
- return <tools.Override {...part} addResult={addResult} />;
247
+ return <tools.Override {...part} addResult={addResult} resume={resume} />;
247
248
  const Tool = tools.by_name?.[part.toolName] ?? tools.Fallback;
248
- return <ToolUIDisplay {...part} Fallback={Tool} addResult={addResult} />;
249
+ return (
250
+ <ToolUIDisplay
251
+ {...part}
252
+ Fallback={Tool}
253
+ addResult={addResult}
254
+ resume={resume}
255
+ />
256
+ );
249
257
  }
250
258
 
251
259
  if (part.status?.type === "requires-action")
@@ -267,10 +267,18 @@ const MessagePartComponent: FC<MessagePartComponentProps> = ({
267
267
  const type = part.type;
268
268
  if (type === "tool-call") {
269
269
  const addResult = (result: any) => api.part().addToolResult(result);
270
+ const resume = api.part().resumeToolCall;
270
271
  if ("Override" in tools)
271
- return <tools.Override {...part} addResult={addResult} />;
272
+ return <tools.Override {...part} addResult={addResult} resume={resume} />;
272
273
  const Tool = tools.by_name?.[part.toolName] ?? tools.Fallback;
273
- return <ToolUIDisplay {...part} Fallback={Tool} addResult={addResult} />;
274
+ return (
275
+ <ToolUIDisplay
276
+ {...part}
277
+ Fallback={Tool}
278
+ addResult={addResult}
279
+ resume={resume}
280
+ />
281
+ );
274
282
  }
275
283
 
276
284
  if (part.status?.type === "requires-action")
@@ -64,7 +64,7 @@ export type MessagePartStatus =
64
64
  export type ToolCallMessagePartStatus =
65
65
  | {
66
66
  readonly type: "requires-action";
67
- readonly reason: "tool-calls";
67
+ readonly reason: "interrupt";
68
68
  }
69
69
  | MessagePartStatus;
70
70
 
@@ -74,7 +74,7 @@ export type MessageStatus =
74
74
  }
75
75
  | {
76
76
  readonly type: "requires-action";
77
- readonly reason: "tool-calls";
77
+ readonly reason: "tool-calls" | "interrupt";
78
78
  }
79
79
  | {
80
80
  readonly type: "complete";
@@ -44,6 +44,7 @@ export type ToolCallMessagePartProps<
44
44
  > = MessagePartState &
45
45
  ToolCallMessagePart<TArgs, TResult> & {
46
46
  addResult: (result: TResult | ToolResponse<TResult>) => void;
47
+ resume: (payload: unknown) => void;
47
48
  };
48
49
 
49
50
  export type ToolCallMessagePartComponent<
@@ -54,6 +54,7 @@ export type ToolCallMessagePart<
54
54
  readonly isError?: boolean | undefined;
55
55
  readonly argsText: string;
56
56
  readonly artifact?: unknown;
57
+ readonly interrupt?: unknown;
57
58
  readonly parentId?: string;
58
59
  };
59
60