@assistant-ui/core 0.3.9 → 0.3.11
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/model-context/frame/provider.js +1 -3
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/types.js +4 -2
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/AssistantProvider.js +5 -4
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +2 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js +12 -10
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/model-context/useInteractable.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +11 -8
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +27 -22
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -14
- 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 +18 -3
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +2 -1
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/runtime/api/message-runtime.js +4 -2
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +8 -4
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +9 -6
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +31 -28
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/subscribable/subscribable.js +6 -4
- package/dist/subscribable/subscribable.js.map +1 -1
- package/package.json +10 -10
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +49 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +11 -2
- package/src/react/runtimes/external-message-converter.test.ts +71 -0
- package/src/react/runtimes/external-message-converter.test.tsx +9 -0
- package/src/react/runtimes/external-message-converter.ts +35 -2
- package/src/runtimes/local/local-thread-runtime-core.test.ts +18 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "Framework-agnostic core runtime for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
],
|
|
60
60
|
"sideEffects": false,
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"assistant-stream": "^0.3.
|
|
63
|
-
"nanoid": "^6.0.
|
|
62
|
+
"assistant-stream": "^0.3.37",
|
|
63
|
+
"nanoid": "^6.0.1"
|
|
64
64
|
},
|
|
65
65
|
"optionalDevDependencies": {
|
|
66
66
|
"@assistant-ui/x-generative-compiler": ">=0.0.3"
|
|
@@ -89,17 +89,17 @@
|
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@testing-library/react": "^16.3.2",
|
|
92
|
-
"@types/react": "^19.2.
|
|
93
|
-
"jsdom": "^
|
|
92
|
+
"@types/react": "^19.2.18",
|
|
93
|
+
"jsdom": "^30.0.1",
|
|
94
94
|
"react": "^19.2.8",
|
|
95
95
|
"react-dom": "^19.2.8",
|
|
96
96
|
"vitest": "^4.1.10",
|
|
97
97
|
"zustand": "^5.0.14",
|
|
98
|
-
"@assistant-ui/
|
|
99
|
-
"@assistant-ui/
|
|
100
|
-
"@assistant-ui/
|
|
101
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
102
|
-
"assistant-cloud": "0.1.
|
|
98
|
+
"@assistant-ui/store": "0.3.8",
|
|
99
|
+
"@assistant-ui/tap": "0.9.11",
|
|
100
|
+
"@assistant-ui/vite": "0.0.12",
|
|
101
|
+
"@assistant-ui/x-buildutils": "0.0.22",
|
|
102
|
+
"assistant-cloud": "0.1.39"
|
|
103
103
|
},
|
|
104
104
|
"publishConfig": {
|
|
105
105
|
"access": "public",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import type { AssistantCloud } from "assistant-cloud";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { useCloudThreadListAdapter } from "./useCloudThreadListAdapter";
|
|
7
|
+
|
|
8
|
+
const makeThread = (id: string) => ({
|
|
9
|
+
id,
|
|
10
|
+
title: id,
|
|
11
|
+
is_archived: false,
|
|
12
|
+
external_id: null,
|
|
13
|
+
metadata: null,
|
|
14
|
+
last_message_at: new Date(0),
|
|
15
|
+
created_at: new Date(0),
|
|
16
|
+
updated_at: new Date(0),
|
|
17
|
+
project_id: "project-1",
|
|
18
|
+
workspace_id: "workspace-1",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("useCloudThreadListAdapter", () => {
|
|
22
|
+
it("exposes full Cloud pages through the remote thread cursor", async () => {
|
|
23
|
+
const firstPage = Array.from({ length: 20 }, (_, index) =>
|
|
24
|
+
makeThread(`thread-${index + 1}`),
|
|
25
|
+
);
|
|
26
|
+
const secondPage = [makeThread("thread-21")];
|
|
27
|
+
const list = vi
|
|
28
|
+
.fn()
|
|
29
|
+
.mockResolvedValueOnce({ threads: firstPage })
|
|
30
|
+
.mockResolvedValueOnce({ threads: secondPage });
|
|
31
|
+
const cloud = { threads: { list } } as unknown as AssistantCloud;
|
|
32
|
+
const { result } = renderHook(() => useCloudThreadListAdapter({ cloud }));
|
|
33
|
+
|
|
34
|
+
const first = await result.current.list();
|
|
35
|
+
expect(list).toHaveBeenNthCalledWith(1, { limit: 20 });
|
|
36
|
+
expect(first.threads).toHaveLength(20);
|
|
37
|
+
expect(first.nextCursor).toBe("thread-20");
|
|
38
|
+
|
|
39
|
+
const second = await result.current.list({ after: first.nextCursor });
|
|
40
|
+
expect(list).toHaveBeenNthCalledWith(2, {
|
|
41
|
+
limit: 20,
|
|
42
|
+
after: "thread-20",
|
|
43
|
+
});
|
|
44
|
+
expect(second.threads.map((thread) => thread.remoteId)).toEqual([
|
|
45
|
+
"thread-21",
|
|
46
|
+
]);
|
|
47
|
+
expect(second.nextCursor).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -37,6 +37,8 @@ const autoCloud = baseUrl
|
|
|
37
37
|
? new AssistantCloud({ baseUrl, anonymous: true })
|
|
38
38
|
: undefined;
|
|
39
39
|
|
|
40
|
+
const CLOUD_THREAD_PAGE_SIZE = 20;
|
|
41
|
+
|
|
40
42
|
export const useCloudThreadListAdapter = (
|
|
41
43
|
adapter: CloudThreadListAdapterOptions,
|
|
42
44
|
): RemoteThreadListAdapter => {
|
|
@@ -88,8 +90,11 @@ export const useCloudThreadListAdapter = (
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
return {
|
|
91
|
-
list: async () => {
|
|
92
|
-
const { threads } = await cloud.threads.list(
|
|
93
|
+
list: async ({ after } = {}) => {
|
|
94
|
+
const { threads } = await cloud.threads.list({
|
|
95
|
+
limit: CLOUD_THREAD_PAGE_SIZE,
|
|
96
|
+
...(after ? { after } : {}),
|
|
97
|
+
});
|
|
93
98
|
return {
|
|
94
99
|
threads: threads.map((t) => ({
|
|
95
100
|
status: t.is_archived ? "archived" : "regular",
|
|
@@ -101,6 +106,10 @@ export const useCloudThreadListAdapter = (
|
|
|
101
106
|
externalId: t.external_id ?? undefined,
|
|
102
107
|
custom: toCustom(t.metadata),
|
|
103
108
|
})),
|
|
109
|
+
nextCursor:
|
|
110
|
+
threads.length === CLOUD_THREAD_PAGE_SIZE
|
|
111
|
+
? threads.at(-1)?.id
|
|
112
|
+
: undefined,
|
|
104
113
|
};
|
|
105
114
|
},
|
|
106
115
|
|
|
@@ -343,4 +343,75 @@ describe("convertExternalMessages", () => {
|
|
|
343
343
|
expect(result[0]!.role).toBe("user");
|
|
344
344
|
});
|
|
345
345
|
});
|
|
346
|
+
|
|
347
|
+
describe("invalid converter output", () => {
|
|
348
|
+
it("throws a descriptive error when the callback returns undefined", () => {
|
|
349
|
+
const messages = [{ id: "m1", type: "remove" }];
|
|
350
|
+
const callback = (() =>
|
|
351
|
+
undefined) as unknown as useExternalMessageConverter.Callback<
|
|
352
|
+
(typeof messages)[number]
|
|
353
|
+
>;
|
|
354
|
+
|
|
355
|
+
expect(() =>
|
|
356
|
+
convertExternalMessages(messages, callback, false, {}),
|
|
357
|
+
).toThrowError(
|
|
358
|
+
/returned an invalid message \(undefined\) for input \{"id":"m1","type":"remove"\}/,
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("throws a descriptive error when the callback returns an array containing undefined", () => {
|
|
363
|
+
const messages = [{ id: "m1", role: "user" as const, content: "hi" }];
|
|
364
|
+
const callback = ((msg: (typeof messages)[number]) => [
|
|
365
|
+
msg,
|
|
366
|
+
undefined,
|
|
367
|
+
]) as unknown as useExternalMessageConverter.Callback<
|
|
368
|
+
(typeof messages)[number]
|
|
369
|
+
>;
|
|
370
|
+
|
|
371
|
+
expect(() =>
|
|
372
|
+
convertExternalMessages(messages, callback, false, {}),
|
|
373
|
+
).toThrowError(/returned an invalid message \(undefined\)/);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("throws a descriptive error when the callback returns an unsupported role", () => {
|
|
377
|
+
const messages = [{ id: "m1", role: "user" as const, content: "hi" }];
|
|
378
|
+
const callback = (() => ({
|
|
379
|
+
role: "other",
|
|
380
|
+
content: "hi",
|
|
381
|
+
})) as unknown as useExternalMessageConverter.Callback<
|
|
382
|
+
(typeof messages)[number]
|
|
383
|
+
>;
|
|
384
|
+
|
|
385
|
+
expect(() =>
|
|
386
|
+
convertExternalMessages(messages, callback, false, {}),
|
|
387
|
+
).toThrowError(/returned an invalid message \(\{"role":"other"/);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("throws a descriptive error when the callback returns a message without content", () => {
|
|
391
|
+
const messages = [{ id: "m1", role: "user" as const, content: "hi" }];
|
|
392
|
+
const callback = (() => ({
|
|
393
|
+
role: "user",
|
|
394
|
+
})) as unknown as useExternalMessageConverter.Callback<
|
|
395
|
+
(typeof messages)[number]
|
|
396
|
+
>;
|
|
397
|
+
|
|
398
|
+
expect(() =>
|
|
399
|
+
convertExternalMessages(messages, callback, false, {}),
|
|
400
|
+
).toThrowError(/returned an invalid message \(\{"role":"user"\}\)/);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("tolerates a tool message without toolCallId as an orphaned tool result", () => {
|
|
404
|
+
const messages = [{ id: "m1", role: "user" as const, content: "hi" }];
|
|
405
|
+
const callback = ((msg: (typeof messages)[number]) => [
|
|
406
|
+
msg,
|
|
407
|
+
{ role: "tool", result: "ok" },
|
|
408
|
+
]) as unknown as useExternalMessageConverter.Callback<
|
|
409
|
+
(typeof messages)[number]
|
|
410
|
+
>;
|
|
411
|
+
|
|
412
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
413
|
+
expect(result[0]!.role).toBe("user");
|
|
414
|
+
expect(result[1]!.content).toHaveLength(0);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
346
417
|
});
|
|
@@ -100,6 +100,15 @@ describe("useExternalMessageConverter", () => {
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it("throws the descriptive converter error when the callback returns an invalid message", () => {
|
|
104
|
+
const bad = (() =>
|
|
105
|
+
undefined) as unknown as useExternalMessageConverter.Callback<TestMessage>;
|
|
106
|
+
|
|
107
|
+
expect(() => renderConverter({ callback: bad })).toThrowError(
|
|
108
|
+
/External message converter: the converter callback returned an invalid message \(undefined\) for input \{"id":"u1"/,
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
103
112
|
it.each([
|
|
104
113
|
["false", false],
|
|
105
114
|
["zero", 0],
|
|
@@ -76,6 +76,39 @@ type CallbackResult<T> = {
|
|
|
76
76
|
outputs: useExternalMessageConverter.Message[];
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const stringifyForError = (value: unknown) => {
|
|
80
|
+
let text;
|
|
81
|
+
try {
|
|
82
|
+
text = value instanceof Error ? String(value) : JSON.stringify(value);
|
|
83
|
+
} catch {
|
|
84
|
+
/* circular */
|
|
85
|
+
}
|
|
86
|
+
text ??= String(value);
|
|
87
|
+
return text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const toCallbackOutputs = (
|
|
91
|
+
output:
|
|
92
|
+
| useExternalMessageConverter.Message
|
|
93
|
+
| useExternalMessageConverter.Message[],
|
|
94
|
+
input: unknown,
|
|
95
|
+
): useExternalMessageConverter.Message[] => {
|
|
96
|
+
const outputs = Array.isArray(output) ? output : [output];
|
|
97
|
+
for (const o of outputs) {
|
|
98
|
+
const valid =
|
|
99
|
+
typeof o === "object" &&
|
|
100
|
+
o !== null &&
|
|
101
|
+
(o.role === "tool" ||
|
|
102
|
+
((o.role === "assistant" || o.role === "user" || o.role === "system") &&
|
|
103
|
+
(typeof o.content === "string" || Array.isArray(o.content))));
|
|
104
|
+
if (!valid)
|
|
105
|
+
throw new Error(
|
|
106
|
+
`External message converter: the converter callback returned an invalid message (${stringifyForError(o)}) for input ${stringifyForError(input)}. Return an empty array to skip a message.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return outputs;
|
|
110
|
+
};
|
|
111
|
+
|
|
79
112
|
type CallbackCacheEntry<T> = CallbackResult<T> & {
|
|
80
113
|
metadata: useExternalMessageConverter.Metadata;
|
|
81
114
|
callback: useExternalMessageConverter.Callback<T>;
|
|
@@ -354,7 +387,7 @@ export const convertExternalMessages = <T extends WeakKey>(
|
|
|
354
387
|
const callbackResults: CallbackResult<T>[] = [];
|
|
355
388
|
for (const message of messages) {
|
|
356
389
|
const output = callback(message, metadata);
|
|
357
|
-
const outputs =
|
|
390
|
+
const outputs = toCallbackOutputs(output, message);
|
|
358
391
|
const result = { input: message, outputs };
|
|
359
392
|
callbackResults.push(result);
|
|
360
393
|
}
|
|
@@ -444,7 +477,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
|
|
|
444
477
|
result.callback !== state.callback
|
|
445
478
|
) {
|
|
446
479
|
const output = state.callback(message, state.metadata);
|
|
447
|
-
const outputs =
|
|
480
|
+
const outputs = toCallbackOutputs(output, message);
|
|
448
481
|
result = {
|
|
449
482
|
input: message,
|
|
450
483
|
outputs,
|
|
@@ -1186,12 +1186,10 @@ describe("LocalThreadRuntimeCore runs", () => {
|
|
|
1186
1186
|
};
|
|
1187
1187
|
|
|
1188
1188
|
it("appends the user message and the adapter result", async () => {
|
|
1189
|
-
const run = vi.fn(
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}),
|
|
1194
|
-
);
|
|
1189
|
+
const run = vi.fn(async (): Promise<ChatModelRunResult> => ({
|
|
1190
|
+
content: [{ type: "text", text: "Hello!" }],
|
|
1191
|
+
status: { type: "complete", reason: "stop" },
|
|
1192
|
+
}));
|
|
1195
1193
|
const thread = createPlainThread({ run });
|
|
1196
1194
|
|
|
1197
1195
|
await thread.append(userMessage("Hi"));
|
|
@@ -1247,23 +1245,21 @@ describe("LocalThreadRuntimeCore runs", () => {
|
|
|
1247
1245
|
});
|
|
1248
1246
|
|
|
1249
1247
|
it("does not run again after a tool result once maxSteps is reached", async () => {
|
|
1250
|
-
const run = vi.fn(
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
argsText: "{}",
|
|
1259
|
-
},
|
|
1260
|
-
],
|
|
1261
|
-
status: { type: "requires-action", reason: "tool-calls" },
|
|
1262
|
-
metadata: {
|
|
1263
|
-
steps: [{ usage: { promptTokens: 10, completionTokens: 5 } }],
|
|
1248
|
+
const run = vi.fn(async (): Promise<ChatModelRunResult> => ({
|
|
1249
|
+
content: [
|
|
1250
|
+
{
|
|
1251
|
+
type: "tool-call",
|
|
1252
|
+
toolCallId: "tc1",
|
|
1253
|
+
toolName: "myTool",
|
|
1254
|
+
args: {},
|
|
1255
|
+
argsText: "{}",
|
|
1264
1256
|
},
|
|
1265
|
-
|
|
1266
|
-
|
|
1257
|
+
],
|
|
1258
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
1259
|
+
metadata: {
|
|
1260
|
+
steps: [{ usage: { promptTokens: 10, completionTokens: 5 } }],
|
|
1261
|
+
},
|
|
1262
|
+
}));
|
|
1267
1263
|
const thread = createPlainThread({ run }, { maxSteps: 1 });
|
|
1268
1264
|
|
|
1269
1265
|
await thread.append(userMessage("Tool call"));
|