@assistant-ui/core 0.3.14 → 0.3.15

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 (137) hide show
  1. package/dist/adapters/voice.js +11 -0
  2. package/dist/adapters/voice.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/internal.d.ts +2 -1
  5. package/dist/internal.js +2 -1
  6. package/dist/model-context/frame/host.d.ts +1 -1
  7. package/dist/model-context/frame/host.d.ts.map +1 -1
  8. package/dist/model-context/frame/host.js +17 -3
  9. package/dist/model-context/frame/host.js.map +1 -1
  10. package/dist/model-context/frame/provider.d.ts +4 -0
  11. package/dist/model-context/frame/provider.d.ts.map +1 -1
  12. package/dist/model-context/frame/provider.js +50 -3
  13. package/dist/model-context/frame/provider.js.map +1 -1
  14. package/dist/model-context/frame/types.d.ts +4 -1
  15. package/dist/model-context/frame/types.d.ts.map +1 -1
  16. package/dist/model-context/frame/types.js.map +1 -1
  17. package/dist/react/client/RemoteThreadList.d.ts +1 -1
  18. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  19. package/dist/react/client/RemoteThreadList.js +95 -23
  20. package/dist/react/client/RemoteThreadList.js.map +1 -1
  21. package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
  22. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  23. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
  24. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  25. package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
  26. package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
  27. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
  28. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  29. package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
  30. package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
  31. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
  32. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  33. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
  34. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  35. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +6 -0
  36. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  37. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +192 -50
  38. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  39. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  40. package/dist/react/runtimes/RemoteThreadResource.js +2 -0
  41. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  42. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  43. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
  44. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  45. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  46. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
  47. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  48. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  49. package/dist/runtime/utils/message-repository.js +27 -18
  50. package/dist/runtime/utils/message-repository.js.map +1 -1
  51. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  52. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -0
  53. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  54. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
  55. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
  56. package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
  57. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
  58. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  59. package/dist/runtimes/local/local-thread-runtime-core.js +25 -6
  60. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  61. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
  62. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  63. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
  64. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  65. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
  66. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
  67. package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
  68. package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
  69. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  70. package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
  71. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  72. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
  73. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  74. package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
  75. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  76. package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
  77. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  78. package/dist/store/clients/external-thread.js +1 -1
  79. package/dist/store/clients/external-thread.js.map +1 -1
  80. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
  81. package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
  82. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
  83. package/dist/types/message.d.ts +1 -1
  84. package/dist/types/message.js.map +1 -1
  85. package/dist/utils/silent-runtime-action.d.ts +6 -0
  86. package/dist/utils/silent-runtime-action.d.ts.map +1 -0
  87. package/dist/utils/silent-runtime-action.js +7 -0
  88. package/dist/utils/silent-runtime-action.js.map +1 -0
  89. package/package.json +4 -4
  90. package/src/adapters/voice.test.ts +66 -1
  91. package/src/adapters/voice.ts +8 -0
  92. package/src/internal.ts +1 -0
  93. package/src/model-context/frame/host.test.ts +64 -5
  94. package/src/model-context/frame/host.ts +16 -3
  95. package/src/model-context/frame/provider.test.ts +347 -20
  96. package/src/model-context/frame/provider.ts +58 -2
  97. package/src/model-context/frame/types.ts +5 -0
  98. package/src/react/client/RemoteThreadList.test.ts +196 -0
  99. package/src/react/client/RemoteThreadList.ts +93 -24
  100. package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
  101. package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
  102. package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
  103. package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
  104. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
  105. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
  106. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +268 -63
  107. package/src/react/runtimes/RemoteThreadResource.ts +2 -0
  108. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
  109. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
  110. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
  111. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
  112. package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
  113. package/src/runtime/utils/message-repository.ts +26 -18
  114. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +4 -0
  115. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
  116. package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
  117. package/src/runtimes/local/local-thread-runtime-core.test.ts +319 -0
  118. package/src/runtimes/local/local-thread-runtime-core.ts +38 -8
  119. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
  120. package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
  121. package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
  122. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
  123. package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
  124. package/src/runtimes/remote-thread-list/types.ts +4 -0
  125. package/src/store/clients/external-thread.ts +1 -1
  126. package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
  127. package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
  128. package/src/tests/MessageRepository.test.ts +48 -0
  129. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
  130. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
  131. package/src/tests/empty-thread-core.test.ts +6 -0
  132. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +20 -0
  133. package/src/tests/external-thread-isEmpty.test.tsx +100 -0
  134. package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
  135. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
  136. package/src/types/message.ts +1 -1
  137. package/src/utils/silent-runtime-action.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "sideEffects": false,
61
61
  "dependencies": {
62
- "assistant-stream": "^0.3.38",
62
+ "assistant-stream": "^0.3.39",
63
63
  "nanoid": "^6.0.1"
64
64
  },
65
65
  "optionalDevDependencies": {
@@ -94,9 +94,9 @@
94
94
  "react": "^19.2.8",
95
95
  "react-dom": "^19.2.8",
96
96
  "vitest": "^4.1.10",
97
- "zustand": "^5.0.14",
97
+ "zustand": "^5.0.15",
98
98
  "@assistant-ui/store": "0.3.10",
99
- "@assistant-ui/tap": "0.9.13",
99
+ "@assistant-ui/tap": "0.9.14",
100
100
  "@assistant-ui/vite": "0.0.14",
101
101
  "@assistant-ui/x-buildutils": "0.0.23",
102
102
  "assistant-cloud": "0.1.41"
@@ -73,6 +73,25 @@ describe("createVoiceSession", () => {
73
73
  expect(controls.mute).not.toHaveBeenCalled();
74
74
  });
75
75
 
76
+ it("disconnects immediately when created with an already-aborted signal", async () => {
77
+ const abortController = new AbortController();
78
+ abortController.abort();
79
+ const setup = vi.fn(async () => ({
80
+ disconnect: vi.fn(),
81
+ mute: vi.fn(),
82
+ unmute: vi.fn(),
83
+ }));
84
+
85
+ const session = createVoiceSession(
86
+ { abortSignal: abortController.signal },
87
+ setup,
88
+ );
89
+ await Promise.resolve();
90
+
91
+ expect(setup).not.toHaveBeenCalled();
92
+ expect(session.status).toEqual({ type: "ended", reason: "cancelled" });
93
+ });
94
+
76
95
  it("removes the abort listener after disconnecting", async () => {
77
96
  const abortController = new AbortController();
78
97
  const controls = {
@@ -85,11 +104,52 @@ describe("createVoiceSession", () => {
85
104
  async () => controls,
86
105
  );
87
106
  await Promise.resolve();
107
+ const statusListener = vi.fn();
108
+ session.onStatusChange(statusListener);
88
109
 
89
110
  session.disconnect();
90
111
  session.disconnect();
91
112
  abortController.abort();
92
113
 
114
+ expect(session.status).toEqual({ type: "ended", reason: "cancelled" });
115
+ expect(statusListener).toHaveBeenCalledOnce();
116
+ expect(statusListener).toHaveBeenCalledWith({
117
+ type: "ended",
118
+ reason: "cancelled",
119
+ });
120
+ expect(controls.disconnect).toHaveBeenCalledOnce();
121
+ });
122
+
123
+ it("ends a running session when the abort signal fires", async () => {
124
+ const abortController = new AbortController();
125
+ const controls = {
126
+ disconnect: vi.fn(),
127
+ mute: vi.fn(),
128
+ unmute: vi.fn(),
129
+ };
130
+ let helpers: VoiceSessionHelpers | undefined;
131
+ const session = createVoiceSession(
132
+ { abortSignal: abortController.signal },
133
+ async (sessionHelpers) => {
134
+ helpers = sessionHelpers;
135
+ return controls;
136
+ },
137
+ );
138
+ await Promise.resolve();
139
+ if (!helpers) throw new Error("Voice session setup did not start");
140
+
141
+ helpers.setStatus({ type: "running" });
142
+ const statusListener = vi.fn();
143
+ session.onStatusChange(statusListener);
144
+
145
+ abortController.abort();
146
+
147
+ expect(session.status).toEqual({ type: "ended", reason: "cancelled" });
148
+ expect(statusListener).toHaveBeenCalledOnce();
149
+ expect(statusListener).toHaveBeenCalledWith({
150
+ type: "ended",
151
+ reason: "cancelled",
152
+ });
93
153
  expect(controls.disconnect).toHaveBeenCalledOnce();
94
154
  });
95
155
 
@@ -101,7 +161,7 @@ describe("createVoiceSession", () => {
101
161
  unmute: vi.fn(),
102
162
  };
103
163
  let helpers: VoiceSessionHelpers | undefined;
104
- createVoiceSession(
164
+ const session = createVoiceSession(
105
165
  { abortSignal: abortController.signal },
106
166
  async (sessionHelpers) => {
107
167
  helpers = sessionHelpers;
@@ -114,6 +174,11 @@ describe("createVoiceSession", () => {
114
174
  helpers.end("error");
115
175
  abortController.abort();
116
176
 
177
+ expect(session.status).toEqual({
178
+ type: "ended",
179
+ reason: "error",
180
+ error: undefined,
181
+ });
117
182
  expect(controls.disconnect).toHaveBeenCalledOnce();
118
183
  });
119
184
 
@@ -130,6 +130,10 @@ export function createVoiceSession(
130
130
  if (disconnected) return;
131
131
  disconnected = true;
132
132
  detachAbortHandler();
133
+ if (currentStatus.type !== "ended") {
134
+ currentStatus = { type: "ended", reason: "cancelled" };
135
+ notifyEventListeners(statusCbs, currentStatus, "Voice session");
136
+ }
133
137
  try {
134
138
  controls?.disconnect();
135
139
  } finally {
@@ -165,6 +169,10 @@ export function createVoiceSession(
165
169
  if (abortSignal) {
166
170
  abortHandler = () => session.disconnect();
167
171
  abortSignal.addEventListener("abort", abortHandler, { once: true });
172
+ if (abortSignal.aborted) {
173
+ session.disconnect();
174
+ return session;
175
+ }
168
176
  }
169
177
 
170
178
  const doSetup = async () => {
package/src/internal.ts CHANGED
@@ -33,6 +33,7 @@ export {
33
33
  // Message utilities
34
34
  export { getThreadMessageText } from "./utils/text";
35
35
  export { toMessagePartStatus } from "./utils/normalizePartStatus";
36
+ export { notifyEventListeners } from "./utils/notify-event-listeners";
36
37
  export { resolveToolApprovalResponse } from "./runtime/utils/resolveToolApprovalResponse";
37
38
  export { consumeSuggestionResult } from "./adapters/suggestion";
38
39
 
@@ -46,7 +46,15 @@ const createHost = () => {
46
46
  const execute = host.getModelContext().tools?.search?.execute;
47
47
  if (!execute) throw new Error("Expected the search tool to be available");
48
48
 
49
- return { dispatchMessage, execute, host, postMessage };
49
+ const getToolCallId = () => {
50
+ const call = postMessage.mock.calls.find(
51
+ ([data]) => data.message.type === "tool-call",
52
+ );
53
+ if (!call) throw new Error("Expected a tool call to be posted");
54
+ return call[0].message.id as string;
55
+ };
56
+
57
+ return { dispatchMessage, execute, getToolCallId, host, postMessage };
50
58
  };
51
59
 
52
60
  beforeEach(() => {
@@ -60,14 +68,14 @@ afterEach(() => {
60
68
 
61
69
  describe("AssistantFrameHost", () => {
62
70
  it("resolves tool calls from frame results", async () => {
63
- const { dispatchMessage, execute, host } = createHost();
71
+ const { dispatchMessage, execute, getToolCallId, host } = createHost();
64
72
  const result = Promise.resolve(
65
73
  execute({ query: "weather" }, executionContext),
66
74
  );
67
75
 
68
76
  dispatchMessage({
69
77
  type: "tool-result",
70
- id: "tool-0",
78
+ id: getToolCallId(),
71
79
  result: "sunny",
72
80
  });
73
81
 
@@ -77,19 +85,27 @@ describe("AssistantFrameHost", () => {
77
85
  });
78
86
 
79
87
  it("rejects pending tool calls when disposed", async () => {
80
- const { execute, host } = createHost();
88
+ const { execute, getToolCallId, host, postMessage } = createHost();
81
89
  const result = Promise.resolve(execute({}, executionContext));
90
+ const toolCallId = getToolCallId();
82
91
 
83
92
  host.dispose();
84
93
 
85
94
  await expect(result).rejects.toThrow(
86
95
  "AssistantFrameHost has been disposed",
87
96
  );
97
+ expect(postMessage).toHaveBeenLastCalledWith(
98
+ {
99
+ channel: FRAME_MESSAGE_CHANNEL,
100
+ message: { type: "tool-cancel", id: toolCallId },
101
+ },
102
+ "*",
103
+ );
88
104
  expect(vi.getTimerCount()).toBe(0);
89
105
  });
90
106
 
91
107
  it("rejects pending tool calls when execution is aborted", async () => {
92
- const { execute, host } = createHost();
108
+ const { execute, getToolCallId, host, postMessage } = createHost();
93
109
  const abortController = new AbortController();
94
110
  const abortError = new Error("Run cancelled");
95
111
  abortError.name = "AbortError";
@@ -104,15 +120,58 @@ describe("AssistantFrameHost", () => {
104
120
  );
105
121
  const onRejected = vi.fn();
106
122
  void result.catch(onRejected);
123
+ const toolCallId = getToolCallId();
107
124
 
108
125
  abortController.abort(abortError);
109
126
  await Promise.resolve();
110
127
 
111
128
  expect(onRejected).toHaveBeenCalledWith(abortError);
129
+ expect(postMessage).toHaveBeenLastCalledWith(
130
+ {
131
+ channel: FRAME_MESSAGE_CHANNEL,
132
+ message: { type: "tool-cancel", id: toolCallId },
133
+ },
134
+ "*",
135
+ );
136
+ expect(vi.getTimerCount()).toBe(0);
137
+ host.dispose();
138
+ });
139
+
140
+ it("cancels tool calls when they time out", async () => {
141
+ const { execute, getToolCallId, host, postMessage } = createHost();
142
+ const result = Promise.resolve(execute({}, executionContext));
143
+ const toolCallId = getToolCallId();
144
+ const rejection = expect(result).rejects.toThrow(
145
+ 'Tool call "search" timed out',
146
+ );
147
+
148
+ await vi.advanceTimersByTimeAsync(30000);
149
+
150
+ await rejection;
151
+ expect(postMessage).toHaveBeenLastCalledWith(
152
+ {
153
+ channel: FRAME_MESSAGE_CHANNEL,
154
+ message: { type: "tool-cancel", id: toolCallId },
155
+ },
156
+ "*",
157
+ );
112
158
  expect(vi.getTimerCount()).toBe(0);
113
159
  host.dispose();
114
160
  });
115
161
 
162
+ it("uses unique tool IDs across host instances", () => {
163
+ const first = createHost();
164
+ const second = createHost();
165
+
166
+ void first.execute({}, executionContext).catch(() => undefined);
167
+ void second.execute({}, executionContext).catch(() => undefined);
168
+
169
+ expect(first.getToolCallId()).not.toBe(second.getToolCallId());
170
+
171
+ first.host.dispose();
172
+ second.host.dispose();
173
+ });
174
+
116
175
  it("does not post tool calls when execution is already aborted", async () => {
117
176
  const { execute, host, postMessage } = createHost();
118
177
  const abortController = new AbortController();
@@ -2,6 +2,7 @@ import type { ModelContextProvider, ModelContext } from "../types";
2
2
  import type { Unsubscribe } from "../../types/unsubscribe";
3
3
  import type { Tool } from "assistant-stream";
4
4
  import { notifySubscribers as notifyStateSubscribers } from "../../subscribable/subscribable";
5
+ import { generateId } from "../../utils/id";
5
6
  import {
6
7
  type FrameMessage,
7
8
  FRAME_MESSAGE_CHANNEL,
@@ -58,7 +59,6 @@ export class AssistantFrameHost implements ModelContextProvider {
58
59
  reject: (error: any) => void;
59
60
  }
60
61
  >();
61
- private _requestCounter = 0;
62
62
  private _iframeWindow: Window;
63
63
  private _targetOrigin: string;
64
64
  private _disposed = false;
@@ -130,7 +130,7 @@ export class AssistantFrameHost implements ModelContextProvider {
130
130
  return this.sendRequest(
131
131
  {
132
132
  type: "tool-call",
133
- id: `tool-${this._requestCounter++}`,
133
+ id: `tool-${generateId()}`,
134
134
  toolName,
135
135
  args,
136
136
  },
@@ -159,6 +159,7 @@ export class AssistantFrameHost implements ModelContextProvider {
159
159
  if (!abortSignal) return;
160
160
  const pending = this._pendingRequests.get(message.id);
161
161
  if (pending) {
162
+ this.cancelToolCall(message.id);
162
163
  pending.reject(getAbortReason(abortSignal));
163
164
  this._pendingRequests.delete(message.id);
164
165
  }
@@ -182,6 +183,7 @@ export class AssistantFrameHost implements ModelContextProvider {
182
183
  timeoutId = setTimeout(() => {
183
184
  const pending = this._pendingRequests.get(message.id);
184
185
  if (pending) {
186
+ this.cancelToolCall(message.id);
185
187
  pending.reject(new Error(timeoutMessage));
186
188
  this._pendingRequests.delete(message.id);
187
189
  }
@@ -195,6 +197,16 @@ export class AssistantFrameHost implements ModelContextProvider {
195
197
  });
196
198
  }
197
199
 
200
+ private cancelToolCall(id: string) {
201
+ this._iframeWindow.postMessage(
202
+ {
203
+ channel: FRAME_MESSAGE_CHANNEL,
204
+ message: { type: "tool-cancel", id } satisfies FrameMessage,
205
+ },
206
+ this._targetOrigin,
207
+ );
208
+ }
209
+
198
210
  private requestContext() {
199
211
  this._iframeWindow.postMessage(
200
212
  {
@@ -225,7 +237,8 @@ export class AssistantFrameHost implements ModelContextProvider {
225
237
  window.removeEventListener("message", this.handleMessage);
226
238
  this._subscribers.clear();
227
239
  const error = new Error("AssistantFrameHost has been disposed");
228
- for (const pending of this._pendingRequests.values()) {
240
+ for (const [id, pending] of this._pendingRequests) {
241
+ this.cancelToolCall(id);
229
242
  pending.reject(error);
230
243
  }
231
244
  this._pendingRequests.clear();