@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.
- package/dist/adapters/voice.js +11 -0
- package/dist/adapters/voice.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -1
- package/dist/model-context/frame/host.d.ts +1 -1
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +17 -3
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts +4 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +50 -3
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/frame/types.d.ts +4 -1
- package/dist/model-context/frame/types.d.ts.map +1 -1
- package/dist/model-context/frame/types.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts +1 -1
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +95 -23
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
- package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
- package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
- package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
- package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +6 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +192 -50
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.js +2 -0
- package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +27 -18
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +25 -6
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +1 -1
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
- package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
- package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
- package/dist/types/message.d.ts +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/silent-runtime-action.d.ts +6 -0
- package/dist/utils/silent-runtime-action.d.ts.map +1 -0
- package/dist/utils/silent-runtime-action.js +7 -0
- package/dist/utils/silent-runtime-action.js.map +1 -0
- package/package.json +4 -4
- package/src/adapters/voice.test.ts +66 -1
- package/src/adapters/voice.ts +8 -0
- package/src/internal.ts +1 -0
- package/src/model-context/frame/host.test.ts +64 -5
- package/src/model-context/frame/host.ts +16 -3
- package/src/model-context/frame/provider.test.ts +347 -20
- package/src/model-context/frame/provider.ts +58 -2
- package/src/model-context/frame/types.ts +5 -0
- package/src/react/client/RemoteThreadList.test.ts +196 -0
- package/src/react/client/RemoteThreadList.ts +93 -24
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
- package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
- package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +268 -63
- package/src/react/runtimes/RemoteThreadResource.ts +2 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
- package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
- package/src/runtime/utils/message-repository.ts +26 -18
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +4 -0
- package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
- package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +319 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +38 -8
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
- package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
- package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
- package/src/runtimes/remote-thread-list/types.ts +4 -0
- package/src/store/clients/external-thread.ts +1 -1
- package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
- package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
- package/src/tests/MessageRepository.test.ts +48 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
- package/src/tests/empty-thread-core.test.ts +6 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +20 -0
- package/src/tests/external-thread-isEmpty.test.tsx +100 -0
- package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
- package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
- package/src/types/message.ts +1 -1
- 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.
|
|
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.
|
|
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.
|
|
97
|
+
"zustand": "^5.0.15",
|
|
98
98
|
"@assistant-ui/store": "0.3.10",
|
|
99
|
-
"@assistant-ui/tap": "0.9.
|
|
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
|
|
package/src/adapters/voice.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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-${
|
|
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
|
|
240
|
+
for (const [id, pending] of this._pendingRequests) {
|
|
241
|
+
this.cancelToolCall(id);
|
|
229
242
|
pending.reject(error);
|
|
230
243
|
}
|
|
231
244
|
this._pendingRequests.clear();
|