@assistant-ui/core 0.2.5 → 0.2.7
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/index.d.ts +4 -2
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/duplicate-detection.d.ts +5 -0
- package/dist/internal/duplicate-detection.d.ts.map +1 -0
- package/dist/internal/duplicate-detection.js +11 -0
- package/dist/internal/duplicate-detection.js.map +1 -0
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +25 -21
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +6 -7
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +9 -4
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +3 -0
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +7 -3
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +8 -0
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/react/utils/groupParts.d.ts +40 -12
- package/dist/react/utils/groupParts.d.ts.map +1 -1
- package/dist/react/utils/groupParts.js +51 -9
- package/dist/react/utils/groupParts.js.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.js.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts +8 -0
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.js +13 -0
- package/dist/runtime/api/message-part-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +2 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts +10 -0
- 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-adapter.d.ts +33 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +27 -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 +98 -3
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +2 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +3 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +168 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +449 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -0
- package/dist/store/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +3 -0
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.js +1 -0
- package/dist/store/runtime-clients/message-part-runtime-client.js.map +1 -1
- package/dist/store/scopes/part.d.ts +7 -0
- package/dist/store/scopes/part.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js.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/package.json +4 -4
- package/src/adapters/index.ts +1 -4
- package/src/index.ts +11 -0
- package/src/internal/duplicate-detection.ts +26 -0
- package/src/react/AssistantProvider.tsx +2 -3
- package/src/react/index.ts +2 -6
- package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -2
- package/src/react/primitives/message/MessageAttachments.test.tsx +1 -1
- package/src/react/primitives/message/MessageGroupedParts.tsx +38 -31
- package/src/react/primitives/message/MessageParts.tsx +14 -1
- package/src/react/providers/TextMessagePartProvider.tsx +3 -0
- package/src/react/runtimes/external-message-converter.ts +26 -13
- package/src/react/types/MessagePartComponentTypes.ts +8 -0
- package/src/react/utils/groupParts.ts +67 -22
- package/src/runtime/api/attachment-runtime.ts +1 -2
- package/src/runtime/api/message-part-runtime.ts +26 -0
- package/src/runtime/base/base-thread-runtime-core.ts +4 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +15 -0
- package/src/runtime/internal.ts +1 -4
- package/src/runtime/utils/thread-message-like.ts +7 -0
- package/src/runtimes/external-store/external-store-adapter.ts +37 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +1 -3
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +168 -4
- package/src/runtimes/local/local-thread-runtime-core.ts +5 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +194 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +1054 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +783 -0
- package/src/store/clients/thread-message-client.ts +3 -0
- package/src/store/runtime-clients/message-part-runtime-client.ts +2 -0
- package/src/store/scopes/part.ts +4 -0
- package/src/subscribable/subscribable.ts +3 -3
- package/src/tests/OptimisticState-delete-crash.test.ts +2 -0
- package/src/tests/OptimisticState-list-race.test.ts +2 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +5 -5
- package/src/tests/auiV0Encode.test.ts +1 -1
- package/src/tests/composer-can-send.test.ts +8 -4
- package/src/tests/duplicate-detection.test.ts +34 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +1 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +7 -6
- package/src/tests/groupParts.test.ts +118 -32
- package/src/tests/no-unsafe-process-env.test.ts +1 -0
- package/src/tests/remote-thread-list-isLoading.test.ts +2 -0
- package/src/tests/thread-message-like.test.ts +4 -1
- package/src/types/index.ts +1 -4
- package/src/types/message.ts +7 -0
- package/dist/react/runtimes/useToolInvocations.d.ts +0 -53
- package/dist/react/runtimes/useToolInvocations.d.ts.map +0 -1
- package/dist/react/runtimes/useToolInvocations.js +0 -380
- package/dist/react/runtimes/useToolInvocations.js.map +0 -1
- package/src/react/runtimes/useToolInvocations.ts +0 -694
|
@@ -11,6 +11,8 @@ export const MessagePartClient = resource(
|
|
|
11
11
|
getState: () => state,
|
|
12
12
|
addToolResult: (result) => runtime.addToolResult(result),
|
|
13
13
|
resumeToolCall: (payload) => runtime.resumeToolCall(payload),
|
|
14
|
+
respondToToolApproval: (response) =>
|
|
15
|
+
runtime.respondToToolApproval(response),
|
|
14
16
|
__internal_getRuntime: () => runtime,
|
|
15
17
|
};
|
|
16
18
|
},
|
package/src/store/scopes/part.ts
CHANGED
|
@@ -26,6 +26,10 @@ export type PartMethods = {
|
|
|
26
26
|
* This is useful when a tool has requested human input and is waiting for a response.
|
|
27
27
|
*/
|
|
28
28
|
resumeToolCall(payload: unknown): void;
|
|
29
|
+
/**
|
|
30
|
+
* Respond to a server-side tool approval gate. The approval id is read from the part.
|
|
31
|
+
*/
|
|
32
|
+
respondToToolApproval(response: { approved: boolean; reason?: string }): void;
|
|
29
33
|
__internal_getRuntime?(): MessagePartRuntime;
|
|
30
34
|
};
|
|
31
35
|
|
|
@@ -207,9 +207,9 @@ export class LazyMemoizeSubject<TState extends object, TPath>
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
export class NestedSubscriptionSubject<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
TState extends Subscribable | undefined,
|
|
211
|
+
TPath,
|
|
212
|
+
>
|
|
213
213
|
extends BaseSubject
|
|
214
214
|
implements
|
|
215
215
|
SubscribableWithState<TState, TPath>,
|
|
@@ -24,6 +24,8 @@ const createThreadState = (
|
|
|
24
24
|
const mappingId = createThreadMappingId(threadId);
|
|
25
25
|
return {
|
|
26
26
|
isLoading: false,
|
|
27
|
+
isLoadingMore: false,
|
|
28
|
+
cursor: undefined,
|
|
27
29
|
newThreadId: undefined,
|
|
28
30
|
threadIds: status === "regular" ? [threadId] : [],
|
|
29
31
|
archivedThreadIds: status === "archived" ? [threadId] : [],
|
|
@@ -20,7 +20,7 @@ describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
|
20
20
|
|
|
21
21
|
it("initial list response with nextCursor sets hasMore=true", async () => {
|
|
22
22
|
const adapter = makeAdapter({
|
|
23
|
-
list: vi.fn(async () => ({
|
|
23
|
+
list: vi.fn<ListFn>(async () => ({
|
|
24
24
|
threads: [
|
|
25
25
|
{
|
|
26
26
|
status: "regular",
|
|
@@ -41,7 +41,7 @@ describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
|
41
41
|
|
|
42
42
|
it("absent nextCursor leaves hasMore=false", async () => {
|
|
43
43
|
const adapter = makeAdapter({
|
|
44
|
-
list: vi.fn(async () => ({
|
|
44
|
+
list: vi.fn<ListFn>(async () => ({
|
|
45
45
|
threads: [
|
|
46
46
|
{
|
|
47
47
|
status: "regular",
|
|
@@ -243,7 +243,7 @@ describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
|
243
243
|
|
|
244
244
|
it("__internal_setOptions clears cursor and dedup handles on adapter swap, then refetches via the new adapter", async () => {
|
|
245
245
|
const firstAdapter = makeAdapter({
|
|
246
|
-
list: vi.fn(async () => ({
|
|
246
|
+
list: vi.fn<ListFn>(async () => ({
|
|
247
247
|
threads: [{ status: "regular", remoteId: "old", externalId: "old" }],
|
|
248
248
|
nextCursor: "old-cursor",
|
|
249
249
|
})),
|
|
@@ -253,7 +253,7 @@ describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
|
253
253
|
expect(core.threadIds).toEqual(["old"]);
|
|
254
254
|
expect(core.hasMore).toBe(true);
|
|
255
255
|
|
|
256
|
-
const secondList = vi.fn(async () => ({
|
|
256
|
+
const secondList = vi.fn<ListFn>(async () => ({
|
|
257
257
|
threads: [{ status: "regular", remoteId: "new", externalId: "new" }],
|
|
258
258
|
}));
|
|
259
259
|
const secondAdapter = makeAdapter({ list: secondList });
|
|
@@ -290,7 +290,7 @@ describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
|
290
290
|
const stale = core.loadMore();
|
|
291
291
|
|
|
292
292
|
const secondAdapter = makeAdapter({
|
|
293
|
-
list: vi.fn(async () => ({
|
|
293
|
+
list: vi.fn<ListFn>(async () => ({
|
|
294
294
|
threads: [{ status: "regular", remoteId: "ignored", externalId: "x" }],
|
|
295
295
|
})),
|
|
296
296
|
});
|
|
@@ -102,10 +102,14 @@ describe("BaseComposerRuntimeCore.send", () => {
|
|
|
102
102
|
describe("DefaultEditComposerRuntimeCore.canSend", () => {
|
|
103
103
|
it("ignores runtime.isSendDisabled (thread-scoped flag does not block edits)", () => {
|
|
104
104
|
const stub = makeRuntimeStub({ isSendDisabled: true });
|
|
105
|
-
const composer = new DefaultEditComposerRuntimeCore(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const composer = new DefaultEditComposerRuntimeCore(
|
|
106
|
+
stub as unknown as ThreadRuntimeCore,
|
|
107
|
+
() => {},
|
|
108
|
+
{
|
|
109
|
+
parentId: null,
|
|
110
|
+
message: makeUserMessage("seed"),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
109
113
|
|
|
110
114
|
expect(composer.canSend).toBe(true);
|
|
111
115
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { checkDuplicateCore } from "../internal/duplicate-detection";
|
|
3
|
+
|
|
4
|
+
const KEY = Symbol.for("@assistant-ui/core.loaded");
|
|
5
|
+
|
|
6
|
+
function reset(): void {
|
|
7
|
+
delete (globalThis as unknown as Record<symbol, unknown>)[KEY];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("checkDuplicateCore", () => {
|
|
11
|
+
let warn: ReturnType<typeof vi.spyOn>;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
reset();
|
|
15
|
+
warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
warn.mockRestore();
|
|
20
|
+
reset();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("does not warn on the first load", () => {
|
|
24
|
+
checkDuplicateCore();
|
|
25
|
+
expect(warn).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("warns when a second copy registers in the same runtime", () => {
|
|
29
|
+
checkDuplicateCore();
|
|
30
|
+
checkDuplicateCore();
|
|
31
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
32
|
+
expect(warn.mock.calls[0]![0]).toMatch(/npx assistant-ui doctor/);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -184,7 +184,7 @@ describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl"
|
|
|
184
184
|
const core = new ExternalStoreThreadListRuntimeCore(adapter, makeFactory());
|
|
185
185
|
return new ThreadListRuntimeImpl(
|
|
186
186
|
core,
|
|
187
|
-
NoopThreadRuntime as unknown as
|
|
187
|
+
NoopThreadRuntime as unknown as ConstructorParameters<
|
|
188
188
|
typeof ThreadListRuntimeImpl
|
|
189
189
|
>[1],
|
|
190
190
|
);
|
|
@@ -8,12 +8,13 @@ const mockContextProvider: ModelContextProvider = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
const makeStore = (
|
|
11
|
-
overrides?: Partial<ExternalStoreAdapter>,
|
|
12
|
-
): ExternalStoreAdapter =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
overrides?: Partial<ExternalStoreAdapter> | Record<string, unknown>,
|
|
12
|
+
): ExternalStoreAdapter =>
|
|
13
|
+
({
|
|
14
|
+
messages: [],
|
|
15
|
+
onNew: vi.fn(),
|
|
16
|
+
...overrides,
|
|
17
|
+
}) as ExternalStoreAdapter;
|
|
17
18
|
|
|
18
19
|
describe("ExternalStoreThreadRuntimeCore - state reference stability", () => {
|
|
19
20
|
describe("capabilities", () => {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { PartState } from "../store/scopes/part";
|
|
2
3
|
import {
|
|
3
4
|
buildGroupTree,
|
|
4
|
-
|
|
5
|
+
GROUPBY_MEMO_KEY,
|
|
6
|
+
groupPartByType,
|
|
5
7
|
type GroupNode,
|
|
6
8
|
} from "../react/utils/groupParts";
|
|
7
9
|
|
|
8
|
-
const asPaths = (keys: readonly (
|
|
9
|
-
keys.map((k) => normalizeGroupKey(k));
|
|
10
|
+
const asPaths = (keys: readonly (readonly string[])[]) => keys;
|
|
10
11
|
|
|
11
12
|
// Compact tree dump: "G:key#nodeKey[i,j]{...}" | "P:#nodeKey(i)"
|
|
12
13
|
const dump = (nodes: readonly GroupNode[]): string =>
|
|
@@ -20,39 +21,23 @@ const dump = (nodes: readonly GroupNode[]): string =>
|
|
|
20
21
|
})
|
|
21
22
|
.join(",");
|
|
22
23
|
|
|
23
|
-
describe("normalizeGroupKey", () => {
|
|
24
|
-
it("maps null/undefined/[] to []", () => {
|
|
25
|
-
expect(normalizeGroupKey(null)).toEqual([]);
|
|
26
|
-
expect(normalizeGroupKey(undefined)).toEqual([]);
|
|
27
|
-
expect(normalizeGroupKey([])).toEqual([]);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("wraps a string into a single-element array", () => {
|
|
31
|
-
expect(normalizeGroupKey("foo")).toEqual(["foo"]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("passes arrays through", () => {
|
|
35
|
-
expect(normalizeGroupKey(["a", "b"])).toEqual(["a", "b"]);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
24
|
describe("buildGroupTree", () => {
|
|
40
25
|
it("returns an empty list for no parts", () => {
|
|
41
26
|
expect(buildGroupTree([])).toEqual([]);
|
|
42
27
|
});
|
|
43
28
|
|
|
44
29
|
it("emits one part leaf per ungrouped part (no coalescing)", () => {
|
|
45
|
-
const tree = buildGroupTree(asPaths([
|
|
30
|
+
const tree = buildGroupTree(asPaths([[], [], []]));
|
|
46
31
|
expect(dump(tree)).toBe("P:#0(0),P:#1(1),P:#2(2)");
|
|
47
32
|
});
|
|
48
33
|
|
|
49
34
|
it("wraps adjacent same-key parts in one group with one part child each", () => {
|
|
50
|
-
const tree = buildGroupTree(asPaths(["a", "a", "a"]));
|
|
35
|
+
const tree = buildGroupTree(asPaths([["a"], ["a"], ["a"]]));
|
|
51
36
|
expect(dump(tree)).toBe("G:a#0[0,1,2]{P:#0.0(0),P:#0.1(1),P:#0.2(2)}");
|
|
52
37
|
});
|
|
53
38
|
|
|
54
39
|
it("splits non-adjacent runs of the same key into separate groups", () => {
|
|
55
|
-
const tree = buildGroupTree(asPaths(["a",
|
|
40
|
+
const tree = buildGroupTree(asPaths([["a"], [], ["a"]]));
|
|
56
41
|
expect(dump(tree)).toBe("G:a#0[0]{P:#0.0(0)},P:#1(1),G:a#2[2]{P:#2.0(2)}");
|
|
57
42
|
});
|
|
58
43
|
|
|
@@ -95,20 +80,121 @@ describe("buildGroupTree", () => {
|
|
|
95
80
|
);
|
|
96
81
|
});
|
|
97
82
|
|
|
98
|
-
it("accepts strings and arrays interchangeably via normalizeGroupKey", () => {
|
|
99
|
-
const tree = buildGroupTree([
|
|
100
|
-
normalizeGroupKey("A"),
|
|
101
|
-
normalizeGroupKey(["A"]),
|
|
102
|
-
]);
|
|
103
|
-
expect(dump(tree)).toBe("G:A#0[0,1]{P:#0.0(0),P:#0.1(1)}");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
83
|
it("assigns stable nodeKeys under append (existing keys do not shift)", () => {
|
|
107
|
-
const before = buildGroupTree(asPaths([["A"],
|
|
108
|
-
const after = buildGroupTree(asPaths([["A"],
|
|
84
|
+
const before = buildGroupTree(asPaths([["A"], []]));
|
|
85
|
+
const after = buildGroupTree(asPaths([["A"], [], ["B"]]));
|
|
109
86
|
|
|
110
87
|
expect(before[0]!.nodeKey).toBe(after[0]!.nodeKey);
|
|
111
88
|
expect(before[1]!.nodeKey).toBe(after[1]!.nodeKey);
|
|
112
89
|
expect(after[2]!.nodeKey).toBe("2");
|
|
113
90
|
});
|
|
114
91
|
});
|
|
92
|
+
|
|
93
|
+
const part = (overrides: Partial<PartState>): PartState =>
|
|
94
|
+
({
|
|
95
|
+
type: "text",
|
|
96
|
+
text: "",
|
|
97
|
+
status: { type: "complete" },
|
|
98
|
+
...overrides,
|
|
99
|
+
}) as PartState;
|
|
100
|
+
|
|
101
|
+
describe("groupPartByType", () => {
|
|
102
|
+
it("maps part.type to the configured path", () => {
|
|
103
|
+
const fn = groupPartByType({
|
|
104
|
+
reasoning: ["group-thought", "group-reasoning"],
|
|
105
|
+
"tool-call": ["group-thought", "group-tool"],
|
|
106
|
+
});
|
|
107
|
+
expect(fn(part({ type: "reasoning" }))).toEqual([
|
|
108
|
+
"group-thought",
|
|
109
|
+
"group-reasoning",
|
|
110
|
+
]);
|
|
111
|
+
expect(fn(part({ type: "tool-call" }))).toEqual([
|
|
112
|
+
"group-thought",
|
|
113
|
+
"group-tool",
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns [] for part types not in the map", () => {
|
|
118
|
+
const fn = groupPartByType({ reasoning: ["group-r"] });
|
|
119
|
+
expect(fn(part({ type: "text" }))).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("routes MCP-app tool calls through the 'mcp-app' entry when present", () => {
|
|
123
|
+
const fn = groupPartByType({
|
|
124
|
+
"tool-call": ["group-tool"],
|
|
125
|
+
"mcp-app": [],
|
|
126
|
+
});
|
|
127
|
+
const mcpApp = part({
|
|
128
|
+
type: "tool-call",
|
|
129
|
+
toolName: "render",
|
|
130
|
+
mcp: { app: { resourceUri: "ui://my-app" } },
|
|
131
|
+
} as Partial<PartState>);
|
|
132
|
+
const regular = part({
|
|
133
|
+
type: "tool-call",
|
|
134
|
+
toolName: "search",
|
|
135
|
+
} as Partial<PartState>);
|
|
136
|
+
expect(fn(mcpApp)).toEqual([]);
|
|
137
|
+
expect(fn(regular)).toEqual(["group-tool"]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("falls back to 'tool-call' for MCP-app parts when 'mcp-app' is absent", () => {
|
|
141
|
+
const fn = groupPartByType({ "tool-call": ["group-tool"] });
|
|
142
|
+
const mcpApp = part({
|
|
143
|
+
type: "tool-call",
|
|
144
|
+
toolName: "render",
|
|
145
|
+
mcp: { app: { resourceUri: "ui://x" } },
|
|
146
|
+
} as Partial<PartState>);
|
|
147
|
+
expect(fn(mcpApp)).toEqual(["group-tool"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("does not route non-`ui://` tool calls through 'mcp-app'", () => {
|
|
151
|
+
const fn = groupPartByType({
|
|
152
|
+
"tool-call": ["group-tool"],
|
|
153
|
+
"mcp-app": ["group-mcp"],
|
|
154
|
+
});
|
|
155
|
+
const notMcp = part({
|
|
156
|
+
type: "tool-call",
|
|
157
|
+
toolName: "x",
|
|
158
|
+
mcp: { app: { resourceUri: "http://example.com" } },
|
|
159
|
+
} as Partial<PartState>);
|
|
160
|
+
expect(fn(notMcp)).toEqual(["group-tool"]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("tags the function with a GROUPBY_MEMO_KEY fingerprint", () => {
|
|
164
|
+
const fn = groupPartByType({ reasoning: ["group-r"] });
|
|
165
|
+
const memoKey = (fn as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
166
|
+
GROUPBY_MEMO_KEY
|
|
167
|
+
];
|
|
168
|
+
expect(memoKey).toMatch(/^groupPartByType:/);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("produces the same fingerprint regardless of map key order", () => {
|
|
172
|
+
const a = groupPartByType({
|
|
173
|
+
reasoning: ["group-r"],
|
|
174
|
+
"tool-call": ["group-t"],
|
|
175
|
+
});
|
|
176
|
+
const b = groupPartByType({
|
|
177
|
+
"tool-call": ["group-t"],
|
|
178
|
+
reasoning: ["group-r"],
|
|
179
|
+
});
|
|
180
|
+
const keyA = (a as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
181
|
+
GROUPBY_MEMO_KEY
|
|
182
|
+
];
|
|
183
|
+
const keyB = (b as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
184
|
+
GROUPBY_MEMO_KEY
|
|
185
|
+
];
|
|
186
|
+
expect(keyA).toBe(keyB);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("produces different fingerprints for different configs", () => {
|
|
190
|
+
const a = groupPartByType({ reasoning: ["group-r"] });
|
|
191
|
+
const b = groupPartByType({ reasoning: ["group-r2"] });
|
|
192
|
+
const keyA = (a as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
193
|
+
GROUPBY_MEMO_KEY
|
|
194
|
+
];
|
|
195
|
+
const keyB = (b as unknown as { [GROUPBY_MEMO_KEY]: string })[
|
|
196
|
+
GROUPBY_MEMO_KEY
|
|
197
|
+
];
|
|
198
|
+
expect(keyA).not.toBe(keyB);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -2,7 +2,10 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { fromThreadMessageLike } from "../runtime/utils/thread-message-like";
|
|
3
3
|
|
|
4
4
|
const fallbackId = "test-id";
|
|
5
|
-
const fallbackStatus = {
|
|
5
|
+
const fallbackStatus = {
|
|
6
|
+
type: "complete" as const,
|
|
7
|
+
reason: "stop" as const,
|
|
8
|
+
};
|
|
6
9
|
|
|
7
10
|
describe("fromThreadMessageLike", () => {
|
|
8
11
|
describe("data-* prefixed types", () => {
|
package/src/types/index.ts
CHANGED
package/src/types/message.ts
CHANGED
|
@@ -165,6 +165,13 @@ export type ToolCallMessagePart<
|
|
|
165
165
|
readonly modelContent?: readonly ToolModelContentPart[] | undefined;
|
|
166
166
|
/** Human-input request that must be resolved before the run can continue. */
|
|
167
167
|
readonly interrupt?: { type: "human"; payload: unknown };
|
|
168
|
+
/** Server-side approval gate. `approved === undefined` is the only state in which `respondToApproval` may be called. */
|
|
169
|
+
readonly approval?: {
|
|
170
|
+
readonly id: string;
|
|
171
|
+
readonly approved?: boolean;
|
|
172
|
+
readonly reason?: string;
|
|
173
|
+
readonly isAutomatic?: boolean;
|
|
174
|
+
};
|
|
168
175
|
/** Parent message-part ID when this part belongs to a nested structure. */
|
|
169
176
|
readonly parentId?: string;
|
|
170
177
|
/**
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { ThreadMessage } from "../../types/message.js";
|
|
2
|
-
import { Tool, ToolModelContentPart } from "assistant-stream";
|
|
3
|
-
import { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
4
|
-
|
|
5
|
-
//#region src/react/runtimes/useToolInvocations.d.ts
|
|
6
|
-
type AssistantTransportState = {
|
|
7
|
-
readonly messages: readonly ThreadMessage[];
|
|
8
|
-
readonly state?: ReadonlyJSONValue;
|
|
9
|
-
readonly isRunning: boolean;
|
|
10
|
-
};
|
|
11
|
-
type AddToolResultCommand = {
|
|
12
|
-
readonly type: "add-tool-result";
|
|
13
|
-
readonly toolCallId: string;
|
|
14
|
-
readonly toolName: string;
|
|
15
|
-
readonly result: ReadonlyJSONValue;
|
|
16
|
-
readonly isError: boolean;
|
|
17
|
-
readonly artifact?: ReadonlyJSONValue;
|
|
18
|
-
readonly modelContent?: readonly ToolModelContentPart[];
|
|
19
|
-
};
|
|
20
|
-
type UseToolInvocationsParams = {
|
|
21
|
-
state: AssistantTransportState;
|
|
22
|
-
getTools: () => Record<string, Tool> | undefined;
|
|
23
|
-
onResult: (command: AddToolResultCommand) => void;
|
|
24
|
-
setToolStatuses: (updater: Record<string, ToolExecutionStatus> | ((prev: Record<string, ToolExecutionStatus>) => Record<string, ToolExecutionStatus>)) => void;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Streaming execution state for a frontend tool.
|
|
28
|
-
*
|
|
29
|
-
* Custom runtime integrations use this to mirror in-flight tool calls while
|
|
30
|
-
* `useToolInvocations` executes tools in the browser.
|
|
31
|
-
*/
|
|
32
|
-
type ToolExecutionStatus = {
|
|
33
|
-
/** The tool's execute function is currently running. */type: "executing";
|
|
34
|
-
} | {
|
|
35
|
-
/** The tool is waiting for a human input payload before continuing. */type: "interrupt"; /** Human input request emitted by the tool execution context. */
|
|
36
|
-
payload: {
|
|
37
|
-
type: "human";
|
|
38
|
-
payload: unknown;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
declare function useToolInvocations({
|
|
42
|
-
state,
|
|
43
|
-
getTools,
|
|
44
|
-
onResult,
|
|
45
|
-
setToolStatuses
|
|
46
|
-
}: UseToolInvocationsParams): {
|
|
47
|
-
reset: () => void;
|
|
48
|
-
abort: () => Promise<void>;
|
|
49
|
-
resume: (toolCallId: string, payload: unknown) => void;
|
|
50
|
-
};
|
|
51
|
-
//#endregion
|
|
52
|
-
export { AddToolResultCommand, AssistantTransportState, ToolExecutionStatus, useToolInvocations };
|
|
53
|
-
//# sourceMappingURL=useToolInvocations.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useToolInvocations.d.ts","names":[],"sources":["../../../src/react/runtimes/useToolInvocations.ts"],"mappings":";;;;;KAkBY,uBAAA;EAAA,SACD,QAAA,WAAmB,aAAA;EAAA,SACnB,KAAA,GAAQ,iBAAiB;EAAA,SACzB,SAAA;AAAA;AAAA,KAGC,oBAAA;EAAA,SACD,IAAA;EAAA,SACA,UAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,EAAQ,iBAAA;EAAA,SACR,OAAA;EAAA,SACA,QAAA,GAAW,iBAAA;EAAA,SACX,YAAA,YAAwB,oBAAA;AAAA;AAAA,KA2B9B,wBAAA;EACH,KAAA,EAAO,uBAAA;EACP,QAAA,QAAgB,MAAA,SAAe,IAAA;EAC/B,QAAA,GAAW,OAAA,EAAS,oBAAA;EACpB,eAAA,GACE,OAAA,EACI,MAAA,SAAe,mBAAA,MAEb,IAAA,EAAM,MAAA,SAAe,mBAAA,MAClB,MAAA,SAAe,mBAAA;AAAA;;;;;;;KAUhB,mBAAA;EA/CD,wDAkDL,IAAA;AAAA;EAjD6B,uEAqD7B,IAAA,eArDiD;EAuDjD,OAAA;IAAW,IAAA;IAAe,OAAA;EAAA;AAAA;AAAA,iBAyChB,kBAAA,CAAA;EACd,KAAA;EACA,QAAA;EACA,QAAA;EACA;AAAA,GACC,wBAAA;;eA+fiB,OAAA;+BAkCW,OAAA;AAAA"}
|