@assistant-ui/core 0.3.15 → 0.3.16
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/internal.d.ts +7 -2
- package/dist/internal.js +8 -3
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +3 -2
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts +2 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +88 -25
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
- package/dist/model-context/interactable-composer-metadata.js +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/model-context/registry.d.ts +2 -2
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +9 -28
- package/dist/model-context/registry.js.map +1 -1
- package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/react/client/InMemoryThreadList.js +24 -20
- package/dist/react/client/InMemoryThreadList.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +59 -177
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +23 -56
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
- package/dist/react/interactables-legacy/Interactables.js +56 -174
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
- package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +23 -55
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
- package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts +6 -25
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +33 -233
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +1 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +14 -29
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +3 -9
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
- package/dist/runtime/internal.d.ts +2 -1
- package/dist/runtime/internal.js +2 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +1 -1
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/queue/queue-item.d.ts +12 -0
- package/dist/runtime/queue/queue-item.d.ts.map +1 -0
- package/dist/runtime/queue/queue-item.js +6 -0
- package/dist/runtime/queue/queue-item.js.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.js +35 -0
- package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.js +43 -0
- package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
- package/dist/runtime/utils/auto-status.d.ts +6 -1
- package/dist/runtime/utils/auto-status.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.js +7 -1
- package/dist/runtime/utils/auto-status.js.map +1 -1
- package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
- package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
- package/dist/runtime/utils/external-message-conversion.js +221 -0
- package/dist/runtime/utils/external-message-conversion.js.map +1 -0
- package/dist/runtime/utils/external-store-message.d.ts +2 -1
- package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
- package/dist/runtime/utils/external-store-message.js +2 -1
- package/dist/runtime/utils/external-store-message.js.map +1 -1
- package/dist/runtime/utils/message-repository-session.d.ts +30 -0
- package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
- package/dist/runtime/utils/message-repository-session.js +130 -0
- package/dist/runtime/utils/message-repository-session.js.map +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +3 -4
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
- package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
- package/dist/runtime/utils/pending-tool-calls.js +22 -0
- package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.d.ts +2 -1
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -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 +122 -20
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +20 -2
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +74 -99
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/single-thread-list.d.ts.map +1 -1
- package/dist/store/clients/single-thread-list.js +1 -1
- package/dist/store/clients/single-thread-list.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +16 -7
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/index.d.ts +4 -2
- package/dist/store/scope-registration.d.ts +1 -1
- package/dist/store/scopes/composer.d.ts +2 -1
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/message.d.ts +1 -1
- package/dist/store/scopes/queue-item.d.ts +2 -9
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js +0 -6
- package/dist/types/message.d.ts +7 -3
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +14 -1
- package/dist/utils/data-url.d.ts.map +1 -1
- package/dist/utils/data-url.js +13 -1
- package/dist/utils/data-url.js.map +1 -1
- package/dist/utils/invoke-user-callback.d.ts +5 -0
- package/dist/utils/invoke-user-callback.d.ts.map +1 -0
- package/dist/utils/invoke-user-callback.js +19 -0
- package/dist/utils/invoke-user-callback.js.map +1 -0
- package/dist/utils/normalizePartStatus.js.map +1 -1
- package/package.json +10 -10
- package/src/internal.ts +12 -0
- package/src/model-context/frame/host.test.ts +91 -7
- package/src/model-context/frame/host.ts +7 -2
- package/src/model-context/frame/provider.test.ts +267 -12
- package/src/model-context/frame/provider.ts +113 -31
- package/src/model-context/interactable-composer-metadata.ts +1 -1
- package/src/model-context/registry.test.ts +44 -0
- package/src/model-context/registry.ts +13 -35
- package/src/react/client/InMemoryThreadList.test.tsx +49 -0
- package/src/react/client/InMemoryThreadList.ts +7 -1
- package/src/react/client/Interactables.ts +19 -178
- package/src/react/client/RemoteThreadList.test.ts +96 -0
- package/src/react/client/RemoteThreadList.ts +27 -59
- package/src/react/interactables-legacy/Interactables.ts +20 -177
- package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
- package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +26 -56
- package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
- package/src/react/runtimes/external-message-converter.test.ts +0 -14
- package/src/react/runtimes/external-message-converter.test.tsx +77 -0
- package/src/react/runtimes/external-message-converter.ts +58 -481
- package/src/runtime/api/composer-runtime.ts +1 -1
- package/src/runtime/api/message-runtime.test.ts +28 -1
- package/src/runtime/base/base-composer-runtime-core.ts +20 -40
- package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
- package/src/runtime/base/base-thread-runtime-core.ts +6 -11
- package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
- package/src/runtime/internal.ts +5 -0
- package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
- package/src/runtime/queue/message-queue.ts +1 -4
- package/src/runtime/queue/queue-item.ts +10 -0
- package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
- package/src/runtime/utils/abortable-thread-load.ts +53 -0
- package/src/runtime/utils/attachment-add-operations.ts +59 -0
- package/src/runtime/utils/auto-status.ts +33 -0
- package/src/runtime/utils/external-message-conversion.test.ts +113 -0
- package/src/runtime/utils/external-message-conversion.ts +494 -0
- package/src/runtime/utils/external-store-message.ts +2 -0
- package/src/runtime/utils/message-repository-session.test.ts +195 -0
- package/src/runtime/utils/message-repository-session.ts +176 -0
- package/src/runtime/utils/message-repository.ts +7 -4
- package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
- package/src/runtime/utils/pending-tool-calls.ts +35 -0
- package/src/runtime/utils/thread-message-like.ts +2 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +175 -28
- package/src/runtimes/local/local-thread-runtime-core.test.ts +172 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +48 -3
- package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
- package/src/store/clients/external-thread.ts +16 -61
- package/src/store/clients/single-thread-list.ts +4 -1
- package/src/store/clients/suggestions.test.ts +31 -0
- package/src/store/clients/suggestions.ts +18 -19
- package/src/store/scopes/message.ts +1 -1
- package/src/store/scopes/queue-item.ts +2 -9
- package/src/tests/MessageRepository.test.ts +77 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +164 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
- package/src/tests/external-thread-attachments.test.tsx +173 -0
- package/src/tests/single-thread-list-item.test.tsx +47 -0
- package/src/tests/thread-message-like.test.ts +39 -1
- package/src/types/message.ts +8 -3
- package/src/utils/data-url.test.ts +52 -1
- package/src/utils/data-url.ts +21 -0
- package/src/utils/invoke-user-callback.test.ts +82 -0
- package/src/utils/invoke-user-callback.ts +31 -0
- package/src/utils/normalizePartStatus.ts +1 -1
- package/dist/store/scopes/queue-item.js.map +0 -1
|
@@ -99,6 +99,52 @@ describe("AssistantFrameProvider", () => {
|
|
|
99
99
|
await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
it("defaults to the current origin", async () => {
|
|
103
|
+
const execute = vi.fn(async () => "result");
|
|
104
|
+
AssistantFrameProvider.addModelContextProvider({
|
|
105
|
+
getModelContext: () => ({
|
|
106
|
+
tools: { sensitiveTool: { execute } },
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
111
|
+
expect.anything(),
|
|
112
|
+
window.location.origin,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
dispatchToolCall("https://untrusted.example");
|
|
116
|
+
expect(execute).not.toHaveBeenCalled();
|
|
117
|
+
|
|
118
|
+
dispatchToolCall(window.location.origin);
|
|
119
|
+
await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("reports a failure even when the thrown error has an empty message", async () => {
|
|
123
|
+
const execute = vi.fn(async () => {
|
|
124
|
+
throw new Error();
|
|
125
|
+
});
|
|
126
|
+
AssistantFrameProvider.addModelContextProvider({
|
|
127
|
+
getModelContext: () => ({
|
|
128
|
+
tools: {
|
|
129
|
+
sensitiveTool: { execute },
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
dispatchToolCall(window.location.origin);
|
|
135
|
+
|
|
136
|
+
await vi.waitFor(() => {
|
|
137
|
+
const frame = (
|
|
138
|
+
parentWindow.postMessage as ReturnType<typeof vi.fn>
|
|
139
|
+
).mock.calls
|
|
140
|
+
.map(([data]) => data)
|
|
141
|
+
.find((data) => data?.message?.type === "tool-result");
|
|
142
|
+
expect(frame).toBeDefined();
|
|
143
|
+
expect(frame.message).toHaveProperty("error");
|
|
144
|
+
expect(frame.message).not.toHaveProperty("result");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
102
148
|
it("aborts in-flight tool calls when the parent cancels them", async () => {
|
|
103
149
|
let toolSignal: AbortSignal | undefined;
|
|
104
150
|
const execute = vi.fn(
|
|
@@ -119,10 +165,10 @@ describe("AssistantFrameProvider", () => {
|
|
|
119
165
|
}),
|
|
120
166
|
});
|
|
121
167
|
|
|
122
|
-
dispatchToolCall(
|
|
168
|
+
dispatchToolCall(window.location.origin);
|
|
123
169
|
await vi.waitFor(() => expect(toolSignal).toBeDefined());
|
|
124
170
|
|
|
125
|
-
dispatchToolCancel(
|
|
171
|
+
dispatchToolCancel(window.location.origin);
|
|
126
172
|
|
|
127
173
|
expect(toolSignal?.aborted).toBe(true);
|
|
128
174
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -155,11 +201,11 @@ describe("AssistantFrameProvider", () => {
|
|
|
155
201
|
getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
|
|
156
202
|
});
|
|
157
203
|
|
|
158
|
-
dispatchToolCall(
|
|
159
|
-
dispatchToolCall(
|
|
204
|
+
dispatchToolCall(window.location.origin, parentWindow, "tool-a");
|
|
205
|
+
dispatchToolCall(window.location.origin, parentWindow, "tool-b");
|
|
160
206
|
await vi.waitFor(() => expect(signals.size).toBe(2));
|
|
161
207
|
|
|
162
|
-
dispatchToolCancel(
|
|
208
|
+
dispatchToolCancel(window.location.origin, parentWindow, "tool-a");
|
|
163
209
|
|
|
164
210
|
expect(signals.get("tool-a")?.aborted).toBe(true);
|
|
165
211
|
expect(signals.get("tool-b")?.aborted).toBe(false);
|
|
@@ -183,9 +229,9 @@ describe("AssistantFrameProvider", () => {
|
|
|
183
229
|
getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
|
|
184
230
|
});
|
|
185
231
|
|
|
186
|
-
dispatchToolCall(
|
|
232
|
+
dispatchToolCall(window.location.origin, parentWindow, "duplicate");
|
|
187
233
|
await vi.waitFor(() => expect(signals).toHaveLength(1));
|
|
188
|
-
dispatchToolCall(
|
|
234
|
+
dispatchToolCall(window.location.origin, parentWindow, "duplicate");
|
|
189
235
|
await vi.waitFor(() => expect(signals).toHaveLength(2));
|
|
190
236
|
|
|
191
237
|
expect(signals[0]?.aborted).toBe(true);
|
|
@@ -210,7 +256,7 @@ describe("AssistantFrameProvider", () => {
|
|
|
210
256
|
getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
|
|
211
257
|
});
|
|
212
258
|
|
|
213
|
-
dispatchToolCall(
|
|
259
|
+
dispatchToolCall(window.location.origin);
|
|
214
260
|
await vi.waitFor(() => expect(toolSignal).toBeDefined());
|
|
215
261
|
|
|
216
262
|
AssistantFrameProvider.dispose();
|
|
@@ -225,7 +271,7 @@ describe("AssistantFrameProvider", () => {
|
|
|
225
271
|
error: "AssistantFrameProvider has been disposed",
|
|
226
272
|
},
|
|
227
273
|
},
|
|
228
|
-
{ targetOrigin:
|
|
274
|
+
{ targetOrigin: window.location.origin },
|
|
229
275
|
);
|
|
230
276
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
231
277
|
const toolResults = vi
|
|
@@ -312,7 +358,185 @@ describe("AssistantFrameProvider", () => {
|
|
|
312
358
|
);
|
|
313
359
|
});
|
|
314
360
|
|
|
315
|
-
it("
|
|
361
|
+
it("rolls back a provider when registration fails", () => {
|
|
362
|
+
const execute = vi.fn(async () => "result");
|
|
363
|
+
expect(() =>
|
|
364
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
365
|
+
{
|
|
366
|
+
getModelContext: () => ({
|
|
367
|
+
tools: { sensitiveTool: { execute } },
|
|
368
|
+
}),
|
|
369
|
+
subscribe: () => {
|
|
370
|
+
throw new Error("subscribe failed");
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
"https://first.example",
|
|
374
|
+
),
|
|
375
|
+
).toThrow("subscribe failed");
|
|
376
|
+
|
|
377
|
+
dispatchToolCall("https://first.example");
|
|
378
|
+
expect(execute).not.toHaveBeenCalled();
|
|
379
|
+
|
|
380
|
+
expect(() =>
|
|
381
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
382
|
+
{ getModelContext: () => ({}) },
|
|
383
|
+
"https://second.example",
|
|
384
|
+
),
|
|
385
|
+
).not.toThrow();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("keeps an existing registration when the same provider fails to register again", async () => {
|
|
389
|
+
const execute = vi.fn(async () => "result");
|
|
390
|
+
const firstUnsubscribe = vi.fn();
|
|
391
|
+
let subscriptionCount = 0;
|
|
392
|
+
const provider = {
|
|
393
|
+
getModelContext: () => ({
|
|
394
|
+
tools: { sensitiveTool: { execute } },
|
|
395
|
+
}),
|
|
396
|
+
subscribe: () => {
|
|
397
|
+
subscriptionCount += 1;
|
|
398
|
+
if (subscriptionCount === 1) return firstUnsubscribe;
|
|
399
|
+
throw new Error("second subscribe failed");
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
const releaseFirst = AssistantFrameProvider.addModelContextProvider(
|
|
403
|
+
provider,
|
|
404
|
+
"https://parent.example",
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
expect(() =>
|
|
408
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
409
|
+
provider,
|
|
410
|
+
"https://parent.example",
|
|
411
|
+
),
|
|
412
|
+
).toThrow("second subscribe failed");
|
|
413
|
+
expect(firstUnsubscribe).not.toHaveBeenCalled();
|
|
414
|
+
|
|
415
|
+
dispatchToolCall("https://parent.example");
|
|
416
|
+
|
|
417
|
+
await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
|
|
418
|
+
releaseFirst();
|
|
419
|
+
expect(firstUnsubscribe).toHaveBeenCalledOnce();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("merges a provider registered more than once only once", () => {
|
|
423
|
+
const provider = {
|
|
424
|
+
getModelContext: () => ({ system: "shared system" }),
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
AssistantFrameProvider.addModelContextProvider(provider);
|
|
428
|
+
AssistantFrameProvider.addModelContextProvider(provider);
|
|
429
|
+
|
|
430
|
+
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
431
|
+
{
|
|
432
|
+
channel: FRAME_MESSAGE_CHANNEL,
|
|
433
|
+
message: {
|
|
434
|
+
type: "model-context-update",
|
|
435
|
+
context: {
|
|
436
|
+
system: "shared system",
|
|
437
|
+
tools: {},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
window.location.origin,
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("releases a subscription when the initial broadcast fails", () => {
|
|
446
|
+
const unsubscribe = vi.fn();
|
|
447
|
+
expect(() =>
|
|
448
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
449
|
+
{
|
|
450
|
+
getModelContext: () => {
|
|
451
|
+
throw new Error("context failed");
|
|
452
|
+
},
|
|
453
|
+
subscribe: () => unsubscribe,
|
|
454
|
+
},
|
|
455
|
+
"https://first.example",
|
|
456
|
+
),
|
|
457
|
+
).toThrow("context failed");
|
|
458
|
+
|
|
459
|
+
expect(unsubscribe).toHaveBeenCalledOnce();
|
|
460
|
+
expect(() =>
|
|
461
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
462
|
+
{ getModelContext: () => ({}) },
|
|
463
|
+
"https://second.example",
|
|
464
|
+
),
|
|
465
|
+
).not.toThrow();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("reports rollback cleanup failures without replacing the original error", () => {
|
|
469
|
+
const contextError = new Error("context failed");
|
|
470
|
+
const unsubscribeError = new Error("unsubscribe failed");
|
|
471
|
+
const consoleError = vi
|
|
472
|
+
.spyOn(console, "error")
|
|
473
|
+
.mockImplementation(() => undefined);
|
|
474
|
+
|
|
475
|
+
expect(() =>
|
|
476
|
+
AssistantFrameProvider.addModelContextProvider({
|
|
477
|
+
getModelContext: () => {
|
|
478
|
+
throw contextError;
|
|
479
|
+
},
|
|
480
|
+
subscribe: () => () => {
|
|
481
|
+
throw unsubscribeError;
|
|
482
|
+
},
|
|
483
|
+
}),
|
|
484
|
+
).toThrow(contextError);
|
|
485
|
+
|
|
486
|
+
expect(consoleError).toHaveBeenCalledWith(unsubscribeError);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("cleans up provider state when its unsubscribe throws", () => {
|
|
490
|
+
const unsubscribe = vi.fn(() => {
|
|
491
|
+
throw new Error("unsubscribe failed");
|
|
492
|
+
});
|
|
493
|
+
const release = AssistantFrameProvider.addModelContextProvider(
|
|
494
|
+
{
|
|
495
|
+
getModelContext: () => ({}),
|
|
496
|
+
subscribe: () => unsubscribe,
|
|
497
|
+
},
|
|
498
|
+
"https://first.example",
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
expect(release).toThrow("unsubscribe failed");
|
|
502
|
+
expect(unsubscribe).toHaveBeenCalledOnce();
|
|
503
|
+
expect(() =>
|
|
504
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
505
|
+
{ getModelContext: () => ({}) },
|
|
506
|
+
"https://second.example",
|
|
507
|
+
),
|
|
508
|
+
).not.toThrow();
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("finishes disposal when a provider unsubscribe throws", () => {
|
|
512
|
+
const error = new Error("unsubscribe failed");
|
|
513
|
+
const firstUnsubscribe = vi.fn(() => {
|
|
514
|
+
throw error;
|
|
515
|
+
});
|
|
516
|
+
const secondUnsubscribe = vi.fn();
|
|
517
|
+
AssistantFrameProvider.addModelContextProvider({
|
|
518
|
+
getModelContext: () => ({}),
|
|
519
|
+
subscribe: () => firstUnsubscribe,
|
|
520
|
+
});
|
|
521
|
+
AssistantFrameProvider.addModelContextProvider({
|
|
522
|
+
getModelContext: () => ({}),
|
|
523
|
+
subscribe: () => secondUnsubscribe,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
expect(() => AssistantFrameProvider.dispose()).toThrow(error);
|
|
527
|
+
expect(firstUnsubscribe).toHaveBeenCalledOnce();
|
|
528
|
+
expect(secondUnsubscribe).toHaveBeenCalledOnce();
|
|
529
|
+
|
|
530
|
+
expect(() =>
|
|
531
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
532
|
+
{ getModelContext: () => ({}) },
|
|
533
|
+
"https://new.example",
|
|
534
|
+
),
|
|
535
|
+
).not.toThrow();
|
|
536
|
+
expect(window.addEventListener).toHaveBeenCalledTimes(2);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("returns to the same-origin policy after every provider unsubscribes", () => {
|
|
316
540
|
const unsubscribe = AssistantFrameProvider.addModelContextProvider(
|
|
317
541
|
{ getModelContext: () => ({}) },
|
|
318
542
|
"https://first.example",
|
|
@@ -322,7 +546,7 @@ describe("AssistantFrameProvider", () => {
|
|
|
322
546
|
|
|
323
547
|
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
324
548
|
expect.anything(),
|
|
325
|
-
|
|
549
|
+
window.location.origin,
|
|
326
550
|
);
|
|
327
551
|
|
|
328
552
|
expect(() =>
|
|
@@ -360,7 +584,7 @@ describe("AssistantFrameProvider", () => {
|
|
|
360
584
|
|
|
361
585
|
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
362
586
|
expect.anything(),
|
|
363
|
-
|
|
587
|
+
window.location.origin,
|
|
364
588
|
);
|
|
365
589
|
});
|
|
366
590
|
|
|
@@ -382,6 +606,37 @@ describe("AssistantFrameProvider", () => {
|
|
|
382
606
|
);
|
|
383
607
|
});
|
|
384
608
|
|
|
609
|
+
it("returns to the same-origin policy after a wildcard provider unsubscribes", () => {
|
|
610
|
+
const unsubscribe = AssistantFrameProvider.addModelContextProvider(
|
|
611
|
+
{ getModelContext: () => ({}) },
|
|
612
|
+
"*",
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
unsubscribe();
|
|
616
|
+
|
|
617
|
+
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
618
|
+
expect.anything(),
|
|
619
|
+
window.location.origin,
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("allows opting back into a wildcard policy after every provider unsubscribes", () => {
|
|
624
|
+
const unsubscribe = AssistantFrameProvider.addModelContextProvider({
|
|
625
|
+
getModelContext: () => ({}),
|
|
626
|
+
});
|
|
627
|
+
unsubscribe();
|
|
628
|
+
|
|
629
|
+
AssistantFrameProvider.addModelContextProvider(
|
|
630
|
+
{ getModelContext: () => ({}) },
|
|
631
|
+
"*",
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
|
|
635
|
+
expect.anything(),
|
|
636
|
+
"*",
|
|
637
|
+
);
|
|
638
|
+
});
|
|
639
|
+
|
|
385
640
|
it("keeps a shared strict origin after one provider unsubscribes", async () => {
|
|
386
641
|
const unsubscribeFirst = AssistantFrameProvider.addModelContextProvider(
|
|
387
642
|
{ getModelContext: () => ({}) },
|
|
@@ -29,22 +29,22 @@ const serializeModelContext = (
|
|
|
29
29
|
}),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
const getDefaultTargetOrigin = () => window.location.origin;
|
|
33
|
+
|
|
32
34
|
export class AssistantFrameProvider {
|
|
33
35
|
private static _instance: AssistantFrameProvider | null = null;
|
|
34
36
|
|
|
35
|
-
private _providers = new
|
|
36
|
-
private _providerUnsubscribes = new Map<
|
|
37
|
-
ModelContextProvider,
|
|
38
|
-
Unsubscribe | undefined
|
|
39
|
-
>();
|
|
37
|
+
private _providers = new Map<symbol, ModelContextProvider>();
|
|
38
|
+
private _providerUnsubscribes = new Map<symbol, Unsubscribe | undefined>();
|
|
40
39
|
private _activeToolCalls = new Map<
|
|
41
40
|
string,
|
|
42
41
|
{ abortController: AbortController; event: MessageEvent }
|
|
43
42
|
>();
|
|
44
43
|
private _targetOrigin: string;
|
|
45
44
|
private _strictRegistrations = 0;
|
|
45
|
+
private _wildcardRegistrations = 0;
|
|
46
46
|
|
|
47
|
-
private constructor(targetOrigin: string =
|
|
47
|
+
private constructor(targetOrigin: string = getDefaultTargetOrigin()) {
|
|
48
48
|
this._targetOrigin = targetOrigin;
|
|
49
49
|
this.handleMessage = this.handleMessage.bind(this);
|
|
50
50
|
window.addEventListener("message", this.handleMessage);
|
|
@@ -63,8 +63,17 @@ export class AssistantFrameProvider {
|
|
|
63
63
|
return AssistantFrameProvider._instance;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
private reconcileTargetOrigin(
|
|
67
|
-
|
|
66
|
+
private reconcileTargetOrigin(
|
|
67
|
+
targetOrigin: string = getDefaultTargetOrigin(),
|
|
68
|
+
) {
|
|
69
|
+
if (targetOrigin === this._targetOrigin) return;
|
|
70
|
+
|
|
71
|
+
if (this._providers.size === 0) {
|
|
72
|
+
this._targetOrigin = targetOrigin;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (targetOrigin === "*") return;
|
|
68
77
|
|
|
69
78
|
if (this._targetOrigin === "*") {
|
|
70
79
|
this._targetOrigin = targetOrigin;
|
|
@@ -141,7 +150,7 @@ export class AssistantFrameProvider {
|
|
|
141
150
|
this.sendMessage(event, {
|
|
142
151
|
type: "tool-result",
|
|
143
152
|
id: message.id,
|
|
144
|
-
...(error ? { error } : { result }),
|
|
153
|
+
...(error !== undefined ? { error } : { result }),
|
|
145
154
|
});
|
|
146
155
|
}
|
|
147
156
|
|
|
@@ -160,7 +169,7 @@ export class AssistantFrameProvider {
|
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
private getModelContext(): ModelContext {
|
|
163
|
-
const contexts = Array.from(this._providers).map((p) =>
|
|
172
|
+
const contexts = Array.from(new Set(this._providers.values())).map((p) =>
|
|
164
173
|
p.getModelContext(),
|
|
165
174
|
);
|
|
166
175
|
|
|
@@ -191,34 +200,87 @@ export class AssistantFrameProvider {
|
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
|
|
203
|
+
private removeProvider(id: symbol, origin: string): Unsubscribe | undefined {
|
|
204
|
+
this._providers.delete(id);
|
|
205
|
+
const unsubscribe = this._providerUnsubscribes.get(id);
|
|
206
|
+
this._providerUnsubscribes.delete(id);
|
|
207
|
+
if (origin === "*") {
|
|
208
|
+
this._wildcardRegistrations -= 1;
|
|
209
|
+
if (
|
|
210
|
+
this._wildcardRegistrations === 0 &&
|
|
211
|
+
this._strictRegistrations === 0
|
|
212
|
+
) {
|
|
213
|
+
this._targetOrigin = getDefaultTargetOrigin();
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
this._strictRegistrations -= 1;
|
|
217
|
+
if (this._strictRegistrations === 0) {
|
|
218
|
+
this._targetOrigin =
|
|
219
|
+
this._wildcardRegistrations > 0 ? "*" : getDefaultTargetOrigin();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return unsubscribe;
|
|
223
|
+
}
|
|
224
|
+
|
|
194
225
|
static addModelContextProvider(
|
|
195
226
|
provider: ModelContextProvider,
|
|
196
227
|
targetOrigin?: string,
|
|
197
228
|
): Unsubscribe {
|
|
198
|
-
const origin = targetOrigin ??
|
|
229
|
+
const origin = targetOrigin ?? getDefaultTargetOrigin();
|
|
199
230
|
const instance = AssistantFrameProvider.getInstance(origin);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
instance.
|
|
231
|
+
const id = Symbol();
|
|
232
|
+
instance._providers.set(id, provider);
|
|
233
|
+
if (origin === "*") {
|
|
234
|
+
instance._wildcardRegistrations += 1;
|
|
235
|
+
} else {
|
|
236
|
+
instance._strictRegistrations += 1;
|
|
206
237
|
}
|
|
207
238
|
|
|
208
|
-
|
|
239
|
+
try {
|
|
240
|
+
const unsubscribe = provider.subscribe?.(() =>
|
|
241
|
+
instance.broadcastUpdate(),
|
|
242
|
+
);
|
|
243
|
+
if (unsubscribe) {
|
|
244
|
+
instance._providerUnsubscribes.set(id, unsubscribe);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
instance.broadcastUpdate();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
const unsubscribe = instance.removeProvider(id, origin);
|
|
250
|
+
// Rollback failures must not replace the registration error.
|
|
251
|
+
try {
|
|
252
|
+
unsubscribe?.();
|
|
253
|
+
} catch (unsubscribeError) {
|
|
254
|
+
console.error(unsubscribeError);
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
instance.broadcastUpdate();
|
|
258
|
+
} catch (broadcastError) {
|
|
259
|
+
console.error(broadcastError);
|
|
260
|
+
}
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
209
263
|
|
|
210
264
|
let released = false;
|
|
211
265
|
return () => {
|
|
212
266
|
if (released) return;
|
|
213
267
|
released = true;
|
|
214
|
-
instance.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
268
|
+
const unsubscribe = instance.removeProvider(id, origin);
|
|
269
|
+
let unsubscribeFailed = false;
|
|
270
|
+
let unsubscribeError: unknown;
|
|
271
|
+
try {
|
|
272
|
+
unsubscribe?.();
|
|
273
|
+
} catch (error) {
|
|
274
|
+
unsubscribeFailed = true;
|
|
275
|
+
unsubscribeError = error;
|
|
220
276
|
}
|
|
221
|
-
|
|
277
|
+
try {
|
|
278
|
+
instance.broadcastUpdate();
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (!unsubscribeFailed) throw error;
|
|
281
|
+
console.error(error);
|
|
282
|
+
}
|
|
283
|
+
if (unsubscribeFailed) throw unsubscribeError;
|
|
222
284
|
};
|
|
223
285
|
}
|
|
224
286
|
|
|
@@ -227,20 +289,40 @@ export class AssistantFrameProvider {
|
|
|
227
289
|
const instance = AssistantFrameProvider._instance;
|
|
228
290
|
window.removeEventListener("message", instance.handleMessage);
|
|
229
291
|
|
|
230
|
-
|
|
292
|
+
let cleanupFailed = false;
|
|
293
|
+
let cleanupError: unknown;
|
|
294
|
+
const runCleanup = (cleanup: () => void) => {
|
|
295
|
+
try {
|
|
296
|
+
cleanup();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (cleanupFailed) {
|
|
299
|
+
console.error(error);
|
|
300
|
+
} else {
|
|
301
|
+
cleanupFailed = true;
|
|
302
|
+
cleanupError = error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
instance._providerUnsubscribes.forEach((unsubscribe) => {
|
|
308
|
+
if (unsubscribe) runCleanup(unsubscribe);
|
|
309
|
+
});
|
|
231
310
|
instance._providerUnsubscribes.clear();
|
|
232
311
|
instance._providers.clear();
|
|
233
312
|
instance._activeToolCalls.forEach(({ abortController, event }, id) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
313
|
+
runCleanup(() => {
|
|
314
|
+
abortController.abort();
|
|
315
|
+
instance.sendMessage(event, {
|
|
316
|
+
type: "tool-result",
|
|
317
|
+
id,
|
|
318
|
+
error: "AssistantFrameProvider has been disposed",
|
|
319
|
+
});
|
|
239
320
|
});
|
|
240
321
|
});
|
|
241
322
|
instance._activeToolCalls.clear();
|
|
242
323
|
|
|
243
324
|
AssistantFrameProvider._instance = null;
|
|
325
|
+
if (cleanupFailed) throw cleanupError;
|
|
244
326
|
}
|
|
245
327
|
}
|
|
246
328
|
}
|
|
@@ -33,7 +33,7 @@ type SnapshotCarrierMessage = {
|
|
|
33
33
|
* `metadata.custom.interactables`, or `undefined` if none. This is the read
|
|
34
34
|
* half of the snapshot channel — integrations use it to surface interactable
|
|
35
35
|
* state to the model (see `unstable_injectInteractableContext` in
|
|
36
|
-
* `@assistant-ui/
|
|
36
|
+
* `@assistant-ui/ai-sdk` for the AI SDK implementation).
|
|
37
37
|
*
|
|
38
38
|
* @deprecated Unstable / Experimental (not actually removed).
|
|
39
39
|
*/
|
|
@@ -128,4 +128,48 @@ describe("ModelContextRegistry", () => {
|
|
|
128
128
|
expect(() => publishUpdate()).toThrow(error);
|
|
129
129
|
expect(laterSubscriber).toHaveBeenCalledTimes(1);
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
it("notifies subscribers exactly once when a provider changes", () => {
|
|
133
|
+
const registry = new ModelContextRegistry();
|
|
134
|
+
const callbacks = new Set<() => void>();
|
|
135
|
+
const subscriber = vi.fn();
|
|
136
|
+
|
|
137
|
+
registry.addProvider({
|
|
138
|
+
getModelContext: () => ({ system: "provider instructions" }),
|
|
139
|
+
subscribe: (callback) => {
|
|
140
|
+
callbacks.add(callback);
|
|
141
|
+
return () => callbacks.delete(callback);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
registry.subscribe(subscriber);
|
|
145
|
+
|
|
146
|
+
for (const callback of callbacks) {
|
|
147
|
+
callback();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
expect(subscriber).toHaveBeenCalledTimes(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("removes a provider's context and subscription through the addProvider handle", () => {
|
|
154
|
+
const registry = new ModelContextRegistry();
|
|
155
|
+
const unsubscribe = vi.fn();
|
|
156
|
+
|
|
157
|
+
const handle = registry.addProvider({
|
|
158
|
+
getModelContext: () => ({ system: "provider instructions" }),
|
|
159
|
+
subscribe: () => unsubscribe,
|
|
160
|
+
});
|
|
161
|
+
expect(registry.getModelContext().system).toContain(
|
|
162
|
+
"provider instructions",
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const subscriber = vi.fn();
|
|
166
|
+
registry.subscribe(subscriber);
|
|
167
|
+
handle.remove();
|
|
168
|
+
|
|
169
|
+
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(registry.getModelContext().system ?? "").not.toContain(
|
|
171
|
+
"provider instructions",
|
|
172
|
+
);
|
|
173
|
+
expect(subscriber).toHaveBeenCalledTimes(1);
|
|
174
|
+
});
|
|
131
175
|
});
|