@assistant-ui/core 0.2.7 → 0.2.9
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/attachment.d.ts.map +1 -1
- package/dist/adapters/speech.d.ts.map +1 -1
- package/dist/adapters/speech.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/duplicate-detection.d.ts.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -2
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/tool.d.ts.map +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +26 -15
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +6 -3
- package/dist/react/index.js +4 -1
- package/dist/react/model-context/define-toolkit.d.ts +20 -0
- package/dist/react/model-context/define-toolkit.d.ts.map +1 -0
- package/dist/react/model-context/define-toolkit.js +21 -0
- package/dist/react/model-context/define-toolkit.js.map +1 -0
- package/dist/react/model-context/hitl.d.ts +19 -0
- package/dist/react/model-context/hitl.d.ts.map +1 -0
- package/dist/react/model-context/hitl.js +22 -0
- package/dist/react/model-context/hitl.js.map +1 -0
- package/dist/react/model-context/toolbox.d.ts +29 -2
- package/dist/react/model-context/toolbox.d.ts.map +1 -1
- package/dist/react/model-context/toolbox.js +18 -0
- package/dist/react/model-context/toolbox.js.map +1 -0
- package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantTool.js +6 -3
- package/dist/react/model-context/useAssistantTool.js.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.d.ts +6 -0
- package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +4 -2
- package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/useInlineRender.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +49 -7
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +28 -3
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +2 -7
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
- package/dist/react/runtimes/RuntimeAdapterProvider.js +6 -5
- package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +1 -0
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts +7 -0
- package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts.map +1 -0
- package/dist/react/runtimes/useExternalStoreSharedOptions.js +21 -0
- package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -0
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/types/scopes/tools.d.ts +19 -2
- package/dist/react/types/scopes/tools.d.ts.map +1 -1
- package/dist/react/utils/groupParts.d.ts +32 -11
- package/dist/react/utils/groupParts.d.ts.map +1 -1
- package/dist/react/utils/groupParts.js +13 -6
- package/dist/react/utils/groupParts.js.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-assistant-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts +9 -1
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +34 -14
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts +1 -0
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +2 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-shared-options.d.ts +8 -0
- package/dist/runtimes/external-store/external-store-shared-options.d.ts.map +1 -0
- package/dist/runtimes/external-store/external-store-shared-options.js +11 -0
- package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -0
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +0 -2
- 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 +12 -23
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/thread-message-converter.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/types/message.d.ts +6 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/composite-context-provider.d.ts.map +1 -1
- package/dist/utils/id.d.ts +1 -3
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +1 -4
- package/dist/utils/id.js.map +1 -1
- package/package.json +10 -10
- package/src/adapters/speech.ts +0 -1
- package/src/index.ts +2 -0
- package/src/internal.ts +0 -2
- package/src/model-context/frame/host.ts +0 -1
- package/src/model-context/frame/provider.ts +0 -1
- package/src/react/client/Interactables.ts +0 -1
- package/src/react/client/Tools.ts +50 -25
- package/src/react/index.ts +10 -2
- package/src/react/model-context/define-toolkit.test.ts +13 -0
- package/src/react/model-context/define-toolkit.ts +23 -0
- package/src/react/model-context/hitl.ts +22 -0
- package/src/react/model-context/toolbox.ts +46 -1
- package/src/react/model-context/useAssistantTool.ts +8 -3
- package/src/react/model-context/useAssistantToolUI.ts +9 -2
- package/src/react/model-context/useInlineRender.ts +0 -1
- package/src/react/primitives/message/MessageGroupedParts.tsx +101 -12
- package/src/react/primitives/message/MessageParts.tsx +4 -7
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +0 -3
- package/src/react/runtimes/RuntimeAdapterProvider.tsx +12 -7
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +0 -3
- package/src/react/runtimes/external-message-converter.ts +4 -0
- package/src/react/runtimes/useExternalStoreSharedOptions.ts +23 -0
- package/src/react/runtimes/useLocalRuntime.ts +0 -10
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +0 -6
- package/src/react/types/scopes/tools.ts +20 -1
- package/src/react/utils/groupParts.ts +49 -18
- package/src/runtime/utils/message-repository.ts +57 -16
- package/src/runtime/utils/thread-message-like.ts +2 -0
- package/src/runtimes/external-store/external-store-shared-options.ts +18 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +18 -33
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +0 -1
- package/src/tests/MessageRepository.test.ts +83 -52
- package/src/tests/OptimisticState-list-race.test.ts +0 -4
- package/src/tests/external-store-thread-runtime-core.test.ts +105 -73
- package/src/tests/groupParts.test.ts +70 -0
- package/src/tests/remote-thread-list-isLoading.test.ts +0 -5
- package/src/types/message.ts +6 -0
- package/src/utils/id.ts +0 -4
|
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
import { ExternalStoreThreadRuntimeCore } from "../runtimes/external-store/external-store-thread-runtime-core";
|
|
3
3
|
import type { ExternalStoreAdapter } from "../runtimes/external-store/external-store-adapter";
|
|
4
4
|
import type { ModelContextProvider } from "../model-context/types";
|
|
5
|
+
import type { ThreadMessageLike } from "../runtime/utils/thread-message-like";
|
|
5
6
|
|
|
6
7
|
const mockContextProvider: ModelContextProvider = {
|
|
7
8
|
getModelContext: () => ({}),
|
|
@@ -151,117 +152,148 @@ describe("ExternalStoreThreadRuntimeCore - state reference stability", () => {
|
|
|
151
152
|
expect(runtime.capabilities).toBe(capsBefore);
|
|
152
153
|
});
|
|
153
154
|
});
|
|
155
|
+
describe("ExternalStoreThreadRuntimeCore - optimistic message reconciliation", () => {
|
|
156
|
+
type Raw = {
|
|
157
|
+
id: string;
|
|
158
|
+
role: "user" | "assistant";
|
|
159
|
+
text: string;
|
|
160
|
+
optimistic?: boolean;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const convertMessage = (m: Raw): ThreadMessageLike => ({
|
|
164
|
+
id: m.id,
|
|
165
|
+
role: m.role,
|
|
166
|
+
content: [{ type: "text", text: m.text }],
|
|
167
|
+
...(m.optimistic && { metadata: { isOptimistic: true } }),
|
|
168
|
+
});
|
|
154
169
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
const childrenOf = (
|
|
171
|
+
runtime: ExternalStoreThreadRuntimeCore,
|
|
172
|
+
parentId: string,
|
|
173
|
+
) =>
|
|
174
|
+
runtime
|
|
175
|
+
.export()
|
|
176
|
+
.messages.filter((m) => m.parentId === parentId)
|
|
177
|
+
.map((m) => m.message.id);
|
|
161
178
|
|
|
179
|
+
it("drops the orphaned placeholder when an optimistic id is swapped mid-run", () => {
|
|
180
|
+
const u: Raw = { id: "u", role: "user", text: "hi" };
|
|
162
181
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
163
182
|
mockContextProvider,
|
|
164
|
-
makeStore({
|
|
183
|
+
makeStore({
|
|
184
|
+
messages: [
|
|
185
|
+
u,
|
|
186
|
+
{ id: "client_id", role: "assistant", text: "", optimistic: true },
|
|
187
|
+
],
|
|
188
|
+
convertMessage,
|
|
189
|
+
isRunning: true,
|
|
190
|
+
}),
|
|
165
191
|
);
|
|
166
192
|
|
|
167
|
-
|
|
193
|
+
// AI SDK v6 swaps the client-generated id for the server-provided one.
|
|
194
|
+
runtime.__internal_setAdapter(
|
|
195
|
+
makeStore({
|
|
196
|
+
messages: [
|
|
197
|
+
u,
|
|
198
|
+
{
|
|
199
|
+
id: "server_id",
|
|
200
|
+
role: "assistant",
|
|
201
|
+
text: "hello",
|
|
202
|
+
optimistic: true,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
convertMessage,
|
|
206
|
+
isRunning: true,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
168
209
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.map((m) => m.message.id);
|
|
174
|
-
expect(userChildren).toEqual(["a2"]);
|
|
210
|
+
// No phantom sibling in the live tree (what BranchPicker reads): the user
|
|
211
|
+
// message has a single child. export() omits the still-optimistic
|
|
212
|
+
// streaming message, so assert against the live getBranches instead.
|
|
213
|
+
expect(runtime.getBranches("server_id")).toEqual(["server_id"]);
|
|
175
214
|
});
|
|
176
215
|
|
|
177
|
-
it("
|
|
178
|
-
const
|
|
179
|
-
|
|
216
|
+
it("clears the optimistic flag once the run settles", () => {
|
|
217
|
+
const u: Raw = { id: "u", role: "user", text: "hi" };
|
|
180
218
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
181
219
|
mockContextProvider,
|
|
182
|
-
makeStore({
|
|
220
|
+
makeStore({
|
|
221
|
+
messages: [
|
|
222
|
+
u,
|
|
223
|
+
{ id: "a", role: "assistant", text: "...", optimistic: true },
|
|
224
|
+
],
|
|
225
|
+
convertMessage,
|
|
226
|
+
isRunning: true,
|
|
227
|
+
}),
|
|
183
228
|
);
|
|
184
229
|
|
|
185
|
-
runtime.__internal_setAdapter(
|
|
230
|
+
runtime.__internal_setAdapter(
|
|
231
|
+
makeStore({
|
|
232
|
+
messages: [u, { id: "a", role: "assistant", text: "done" }],
|
|
233
|
+
convertMessage,
|
|
234
|
+
isRunning: false,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
186
237
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
]);
|
|
238
|
+
const settled = runtime.export().messages.find((m) => m.message.id === "a");
|
|
239
|
+
expect(settled?.message.metadata.isOptimistic).toBeFalsy();
|
|
240
|
+
expect(childrenOf(runtime, "u")).toEqual(["a"]);
|
|
191
241
|
});
|
|
192
242
|
|
|
193
|
-
it("removes
|
|
194
|
-
const
|
|
195
|
-
const u2 = { id: "u2", role: "user" as const, content: [] };
|
|
196
|
-
|
|
243
|
+
it("removes the runtime placeholder once the store provides the assistant message", () => {
|
|
244
|
+
const u: Raw = { id: "u", role: "user", text: "hi" };
|
|
197
245
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
198
246
|
mockContextProvider,
|
|
199
|
-
makeStore({ messages: [
|
|
247
|
+
makeStore({ messages: [u], convertMessage, isRunning: true }),
|
|
200
248
|
);
|
|
201
249
|
|
|
202
|
-
|
|
250
|
+
// Running with a trailing user message: a placeholder is appended to the
|
|
251
|
+
// live tree. export() omits optimistic messages, so inspect the live
|
|
252
|
+
// messages (which include the placeholder on the head path). It's a plain
|
|
253
|
+
// assistant message flagged optimistic (no special id scheme).
|
|
254
|
+
const live = runtime.messages;
|
|
255
|
+
expect(live).toHaveLength(2);
|
|
256
|
+
expect(live[1]!.role).toBe("assistant");
|
|
257
|
+
expect(live[1]!.metadata.isOptimistic).toBe(true);
|
|
258
|
+
|
|
259
|
+
// The store now yields the real assistant message; the placeholder (whose
|
|
260
|
+
// synthetic id never appears in the snapshot) must be gone, leaving a
|
|
261
|
+
// single child under the user message.
|
|
262
|
+
runtime.__internal_setAdapter(
|
|
263
|
+
makeStore({
|
|
264
|
+
messages: [u, { id: "a", role: "assistant", text: "done" }],
|
|
265
|
+
convertMessage,
|
|
266
|
+
isRunning: false,
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
203
269
|
|
|
204
270
|
expect(runtime.export().messages.map((m) => m.message.id)).toEqual([
|
|
205
271
|
"u",
|
|
206
272
|
"a",
|
|
207
273
|
]);
|
|
274
|
+
expect(runtime.getBranches("a")).toEqual(["a"]);
|
|
208
275
|
});
|
|
209
276
|
|
|
210
|
-
it("
|
|
211
|
-
const
|
|
212
|
-
id: "u",
|
|
213
|
-
role: "user" as const,
|
|
214
|
-
content: [{ type: "text" as const, text: "hi" }],
|
|
215
|
-
};
|
|
216
|
-
|
|
277
|
+
it("keeps real sibling branches that were never flagged optimistic", () => {
|
|
278
|
+
const u: Raw = { id: "u", role: "user", text: "hi" };
|
|
217
279
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
218
280
|
mockContextProvider,
|
|
219
281
|
makeStore({
|
|
220
|
-
messages: [
|
|
221
|
-
|
|
222
|
-
isRunning: true,
|
|
223
|
-
}),
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
runtime.cancelRun();
|
|
227
|
-
|
|
228
|
-
expect(() => {
|
|
229
|
-
runtime.__internal_setAdapter(makeStore({ messages: [] }));
|
|
230
|
-
}).not.toThrow();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("drops phantom sibling when convertMessage swaps the assistant id", () => {
|
|
234
|
-
type Raw = { id: string; role: "user" | "assistant"; text: string };
|
|
235
|
-
const rawU: Raw = { id: "u", role: "user", text: "hi" };
|
|
236
|
-
const rawA1: Raw = { id: "client_id", role: "assistant", text: "" };
|
|
237
|
-
const rawA2: Raw = { id: "server_id", role: "assistant", text: "" };
|
|
238
|
-
|
|
239
|
-
const convertMessage = (m: Raw) => ({
|
|
240
|
-
id: m.id,
|
|
241
|
-
role: m.role,
|
|
242
|
-
content: [{ type: "text" as const, text: m.text }],
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
246
|
-
mockContextProvider,
|
|
247
|
-
makeStore({
|
|
248
|
-
messages: [rawU, rawA1] as any,
|
|
249
|
-
convertMessage: convertMessage as any,
|
|
282
|
+
messages: [u, { id: "a1", role: "assistant", text: "first" }],
|
|
283
|
+
convertMessage,
|
|
250
284
|
}),
|
|
251
285
|
);
|
|
252
286
|
|
|
287
|
+
// Simulates onEdit/onReload producing a new branch under the same parent;
|
|
288
|
+
// the prior branch must survive (regression guard for #4131).
|
|
253
289
|
runtime.__internal_setAdapter(
|
|
254
290
|
makeStore({
|
|
255
|
-
messages: [
|
|
256
|
-
convertMessage
|
|
291
|
+
messages: [u, { id: "a2", role: "assistant", text: "second" }],
|
|
292
|
+
convertMessage,
|
|
257
293
|
}),
|
|
258
294
|
);
|
|
259
295
|
|
|
260
|
-
|
|
261
|
-
.export()
|
|
262
|
-
.messages.filter((m) => m.parentId === "u")
|
|
263
|
-
.map((m) => m.message.id);
|
|
264
|
-
expect(userChildren).toEqual(["server_id"]);
|
|
296
|
+
expect(childrenOf(runtime, "u")).toEqual(["a1", "a2"]);
|
|
265
297
|
});
|
|
266
298
|
});
|
|
267
299
|
|
|
@@ -160,6 +160,76 @@ describe("groupPartByType", () => {
|
|
|
160
160
|
expect(fn(notMcp)).toEqual(["group-tool"]);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
const standaloneContext = (...names: string[]) => ({
|
|
164
|
+
toolUIs: Object.fromEntries(
|
|
165
|
+
names.map((name) => [name, [{ render: () => null, standalone: true }]]),
|
|
166
|
+
),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("routes context-standalone tool calls through the 'standalone-tool-call' entry", () => {
|
|
170
|
+
const fn = groupPartByType({
|
|
171
|
+
"tool-call": ["group-tool"],
|
|
172
|
+
"standalone-tool-call": [],
|
|
173
|
+
});
|
|
174
|
+
const standalone = part({
|
|
175
|
+
type: "tool-call",
|
|
176
|
+
toolName: "ask_user",
|
|
177
|
+
} as Partial<PartState>);
|
|
178
|
+
const regular = part({
|
|
179
|
+
type: "tool-call",
|
|
180
|
+
toolName: "search",
|
|
181
|
+
} as Partial<PartState>);
|
|
182
|
+
expect(fn(standalone, standaloneContext("ask_user"))).toEqual([]);
|
|
183
|
+
expect(fn(regular, standaloneContext("ask_user"))).toEqual(["group-tool"]);
|
|
184
|
+
// No context → not standalone, falls through to "tool-call".
|
|
185
|
+
expect(fn(standalone)).toEqual(["group-tool"]);
|
|
186
|
+
// Registered but not standalone → also falls through to "tool-call".
|
|
187
|
+
const inlineCtx = {
|
|
188
|
+
toolUIs: { ask_user: [{ render: () => null, standalone: false }] },
|
|
189
|
+
};
|
|
190
|
+
expect(fn(standalone, inlineCtx)).toEqual(["group-tool"]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("routes MCP-app parts through 'standalone-tool-call' from the part alone", () => {
|
|
194
|
+
const fn = groupPartByType({
|
|
195
|
+
"tool-call": ["group-tool"],
|
|
196
|
+
"standalone-tool-call": [],
|
|
197
|
+
});
|
|
198
|
+
const mcpApp = part({
|
|
199
|
+
type: "tool-call",
|
|
200
|
+
toolName: "render",
|
|
201
|
+
mcp: { app: { resourceUri: "ui://my-app" } },
|
|
202
|
+
} as Partial<PartState>);
|
|
203
|
+
expect(fn(mcpApp)).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("routes MCP-app parts through the deprecated 'mcp-app' entry", () => {
|
|
207
|
+
const fn = groupPartByType({
|
|
208
|
+
"tool-call": ["group-tool"],
|
|
209
|
+
"mcp-app": ["group-mcp"],
|
|
210
|
+
});
|
|
211
|
+
const mcpApp = part({
|
|
212
|
+
type: "tool-call",
|
|
213
|
+
toolName: "render",
|
|
214
|
+
mcp: { app: { resourceUri: "ui://my-app" } },
|
|
215
|
+
} as Partial<PartState>);
|
|
216
|
+
expect(fn(mcpApp)).toEqual(["group-mcp"]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("prefers 'standalone-tool-call' over the deprecated 'mcp-app' entry", () => {
|
|
220
|
+
const fn = groupPartByType({
|
|
221
|
+
"tool-call": ["group-tool"],
|
|
222
|
+
"standalone-tool-call": ["group-standalone"],
|
|
223
|
+
"mcp-app": ["group-mcp"],
|
|
224
|
+
});
|
|
225
|
+
const mcpApp = part({
|
|
226
|
+
type: "tool-call",
|
|
227
|
+
toolName: "render",
|
|
228
|
+
mcp: { app: { resourceUri: "ui://x" } },
|
|
229
|
+
} as Partial<PartState>);
|
|
230
|
+
expect(fn(mcpApp)).toEqual(["group-standalone"]);
|
|
231
|
+
});
|
|
232
|
+
|
|
163
233
|
it("tags the function with a GROUPBY_MEMO_KEY fingerprint", () => {
|
|
164
234
|
const fn = groupPartByType({ reasoning: ["group-r"] });
|
|
165
235
|
const memoKey = (fn as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
@@ -86,7 +86,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
|
|
|
86
86
|
state.optimisticUpdate({
|
|
87
87
|
execute: () => d.promise,
|
|
88
88
|
loading: (s) => ({ ...s, isLoading: true }),
|
|
89
|
-
// biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
|
|
90
89
|
then: applyListResult,
|
|
91
90
|
});
|
|
92
91
|
|
|
@@ -100,7 +99,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
|
|
|
100
99
|
const promise = state.optimisticUpdate({
|
|
101
100
|
execute: () => d.promise,
|
|
102
101
|
loading: (s) => ({ ...s, isLoading: true }),
|
|
103
|
-
// biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
|
|
104
102
|
then: applyListResult,
|
|
105
103
|
});
|
|
106
104
|
|
|
@@ -117,7 +115,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
|
|
|
117
115
|
const promise = state.optimisticUpdate({
|
|
118
116
|
execute: () => d.promise,
|
|
119
117
|
loading: (s) => ({ ...s, isLoading: true }),
|
|
120
|
-
// biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
|
|
121
118
|
then: applyListResult,
|
|
122
119
|
});
|
|
123
120
|
|
|
@@ -142,7 +139,6 @@ describe("RemoteThreadList isLoading lifecycle", () => {
|
|
|
142
139
|
const promise = state.optimisticUpdate({
|
|
143
140
|
execute: () => d.promise,
|
|
144
141
|
loading: (s) => ({ ...s, isLoading: true }),
|
|
145
|
-
// biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
|
|
146
142
|
then: applyListResult,
|
|
147
143
|
});
|
|
148
144
|
|
|
@@ -166,7 +162,6 @@ describe("RemoteThreadList isLoading error path", () => {
|
|
|
166
162
|
.optimisticUpdate({
|
|
167
163
|
execute: () => d.promise,
|
|
168
164
|
loading: (s) => ({ ...s, isLoading: true }),
|
|
169
|
-
// biome-ignore lint/suspicious/noThenProperty: OptimisticState reducer pattern
|
|
170
165
|
then: applyListResult,
|
|
171
166
|
})
|
|
172
167
|
.catch(() => {
|
package/src/types/message.ts
CHANGED
|
@@ -314,6 +314,11 @@ export type ThreadAssistantMessage = MessageCommonProps & {
|
|
|
314
314
|
readonly steps: readonly ThreadStep[];
|
|
315
315
|
readonly submittedFeedback?: { readonly type: "positive" | "negative" };
|
|
316
316
|
readonly timing?: MessageTiming;
|
|
317
|
+
/**
|
|
318
|
+
* Marks a client-side optimistic placeholder. Such messages are evicted
|
|
319
|
+
* once off the head branch and are never persisted.
|
|
320
|
+
*/
|
|
321
|
+
readonly isOptimistic?: boolean;
|
|
317
322
|
readonly custom: Record<string, unknown>;
|
|
318
323
|
};
|
|
319
324
|
};
|
|
@@ -327,6 +332,7 @@ type BaseThreadMessage = {
|
|
|
327
332
|
readonly steps?: readonly ThreadStep[];
|
|
328
333
|
readonly submittedFeedback?: { readonly type: "positive" | "negative" };
|
|
329
334
|
readonly timing?: MessageTiming;
|
|
335
|
+
readonly isOptimistic?: boolean;
|
|
330
336
|
readonly custom: Record<string, unknown>;
|
|
331
337
|
};
|
|
332
338
|
readonly attachments?: ThreadUserMessage["attachments"];
|
package/src/utils/id.ts
CHANGED
|
@@ -5,10 +5,6 @@ export const generateId = customAlphabet(
|
|
|
5
5
|
7,
|
|
6
6
|
);
|
|
7
7
|
|
|
8
|
-
const optimisticPrefix = "__optimistic__";
|
|
9
|
-
export const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
10
|
-
export const isOptimisticId = (id: string) => id.startsWith(optimisticPrefix);
|
|
11
|
-
|
|
12
8
|
const errorPrefix = "__error__";
|
|
13
9
|
export const generateErrorMessageId = () => `${errorPrefix}${generateId()}`;
|
|
14
10
|
export const isErrorMessageId = (id: string) => id.startsWith(errorPrefix);
|