@assistant-ui/core 0.2.2 → 0.2.3
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/tool.d.ts +25 -0
- package/dist/model-context/tool.d.ts.map +1 -1
- package/dist/model-context/tool.js +25 -0
- package/dist/model-context/tool.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +33 -0
- package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/react/AssistantRuntimeProvider.js +22 -0
- package/dist/react/AssistantRuntimeProvider.js.map +1 -1
- package/dist/react/client/DataRenderers.d.ts +7 -0
- package/dist/react/client/DataRenderers.d.ts.map +1 -1
- package/dist/react/client/DataRenderers.js +7 -0
- package/dist/react/client/DataRenderers.js.map +1 -1
- package/dist/react/client/Tools.d.ts +12 -0
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +8 -0
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/model-context/makeAssistantDataUI.d.ts +13 -0
- package/dist/react/model-context/makeAssistantDataUI.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantDataUI.js +6 -0
- package/dist/react/model-context/makeAssistantDataUI.js.map +1 -1
- package/dist/react/model-context/makeAssistantTool.d.ts +15 -0
- package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantTool.js +8 -0
- package/dist/react/model-context/makeAssistantTool.js.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.d.ts +15 -0
- package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.js +8 -0
- package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/toolbox.d.ts +29 -0
- package/dist/react/model-context/toolbox.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantDataUI.d.ts +9 -0
- package/dist/react/model-context/useAssistantDataUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantDataUI.js +6 -0
- package/dist/react/model-context/useAssistantDataUI.js.map +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +34 -0
- package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantTool.js +30 -0
- package/dist/react/model-context/useAssistantTool.js.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.d.ts +12 -0
- package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +9 -0
- package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.d.ts +29 -0
- package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.js +24 -0
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +4 -3
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts +6 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.js +7 -0
- package/dist/react/primitives/messagePart/MessagePartInProgress.js.map +1 -0
- package/dist/react/runtimes/useToolInvocations.d.ts +9 -0
- package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
- package/dist/react/runtimes/useToolInvocations.js +318 -264
- package/dist/react/runtimes/useToolInvocations.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +11 -0
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
- 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 +11 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/store/clients/model-context-client.d.ts.map +1 -1
- package/dist/store/clients/model-context-client.js +24 -4
- package/dist/store/clients/model-context-client.js.map +1 -1
- package/dist/store/scopes/model-context.d.ts +4 -1
- package/dist/store/scopes/model-context.d.ts.map +1 -1
- package/dist/types/message.d.ts +22 -0
- package/dist/types/message.d.ts.map +1 -1
- package/package.json +10 -9
- package/src/model-context/tool.ts +25 -0
- package/src/react/AssistantRuntimeProvider.tsx +33 -0
- package/src/react/client/DataRenderers.ts +7 -0
- package/src/react/client/Tools.ts +10 -0
- package/src/react/index.ts +1 -0
- package/src/react/model-context/makeAssistantDataUI.ts +13 -0
- package/src/react/model-context/makeAssistantTool.ts +15 -0
- package/src/react/model-context/makeAssistantToolUI.ts +15 -0
- package/src/react/model-context/toolbox.ts +32 -1
- package/src/react/model-context/useAssistantDataUI.ts +9 -0
- package/src/react/model-context/useAssistantTool.ts +34 -0
- package/src/react/model-context/useAssistantToolUI.ts +12 -0
- package/src/react/model-context/useToolArgsStatus.ts +29 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +9 -5
- package/src/react/primitives/messagePart/MessagePartInProgress.ts +15 -0
- package/src/react/runtimes/useToolInvocations.ts +410 -341
- package/src/react/types/MessagePartComponentTypes.ts +11 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +11 -0
- package/src/store/clients/model-context-client.test.ts +108 -0
- package/src/store/clients/model-context-client.ts +36 -6
- package/src/store/scopes/model-context.ts +4 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +113 -0
- package/src/types/message.ts +22 -0
|
@@ -57,10 +57,21 @@ export type ToolCallMessagePartProps<
|
|
|
57
57
|
TResult = unknown,
|
|
58
58
|
> = MessagePartState &
|
|
59
59
|
ToolCallMessagePart<TArgs, TResult> & {
|
|
60
|
+
/**
|
|
61
|
+
* Sets the result for this tool-call message part.
|
|
62
|
+
*
|
|
63
|
+
* Use when the renderer, rather than a tool `execute` function, is the
|
|
64
|
+
* source of the result.
|
|
65
|
+
*/
|
|
60
66
|
addResult: (result: TResult | ToolResponse<TResult>) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Supplies the payload requested by `context.human(...)` and resumes the
|
|
69
|
+
* paused frontend tool execution.
|
|
70
|
+
*/
|
|
61
71
|
resume: (payload: unknown) => void;
|
|
62
72
|
};
|
|
63
73
|
|
|
74
|
+
/** Component used to render a tool-call message part. */
|
|
64
75
|
export type ToolCallMessagePartComponent<
|
|
65
76
|
TArgs = any,
|
|
66
77
|
TResult = any,
|
|
@@ -53,6 +53,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
53
53
|
implements ThreadRuntimeCore
|
|
54
54
|
{
|
|
55
55
|
private _assistantOptimisticId: string | null = null;
|
|
56
|
+
private _lastSyncedMessageIds = new Set<string>();
|
|
56
57
|
|
|
57
58
|
private _capabilities: RuntimeCapabilities = {
|
|
58
59
|
switchToBranch: false,
|
|
@@ -170,6 +171,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
170
171
|
// Clear and import the message repository
|
|
171
172
|
this.repository.clear();
|
|
172
173
|
this._assistantOptimisticId = null;
|
|
174
|
+
this._lastSyncedMessageIds = new Set();
|
|
173
175
|
this.repository.import(store.messageRepository);
|
|
174
176
|
|
|
175
177
|
messages = this.repository.getMessages();
|
|
@@ -222,6 +224,12 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
222
224
|
return newMessage;
|
|
223
225
|
});
|
|
224
226
|
|
|
227
|
+
const nextIds = new Set(messages.map((m) => m.id));
|
|
228
|
+
for (const prevId of this._lastSyncedMessageIds) {
|
|
229
|
+
if (!nextIds.has(prevId)) this.repository.deleteMessage(prevId);
|
|
230
|
+
}
|
|
231
|
+
this._lastSyncedMessageIds = nextIds;
|
|
232
|
+
|
|
225
233
|
for (let i = 0; i < messages.length; i++) {
|
|
226
234
|
const message = messages[i]!;
|
|
227
235
|
const parent = messages[i - 1];
|
|
@@ -336,6 +344,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
336
344
|
previousMessage.id === messages.at(-1)?.id // ensure the previous message is a leaf node
|
|
337
345
|
) {
|
|
338
346
|
this.repository.deleteMessage(previousMessage.id);
|
|
347
|
+
this._lastSyncedMessageIds.delete(previousMessage.id);
|
|
339
348
|
if (!this.composer.text.trim()) {
|
|
340
349
|
this.composer.setText(getThreadMessageText(previousMessage));
|
|
341
350
|
}
|
|
@@ -364,6 +373,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
364
373
|
}
|
|
365
374
|
|
|
366
375
|
public override reset(initialMessages?: readonly ThreadMessageLike[]) {
|
|
376
|
+
this._lastSyncedMessageIds = new Set();
|
|
367
377
|
const repo = new MessageRepository();
|
|
368
378
|
repo.import(ExportedMessageRepository.fromArray(initialMessages ?? []));
|
|
369
379
|
this.updateMessages(repo.getMessages());
|
|
@@ -371,6 +381,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
371
381
|
|
|
372
382
|
public override import(data: ExportedMessageRepository) {
|
|
373
383
|
this._assistantOptimisticId = null;
|
|
384
|
+
this._lastSyncedMessageIds = new Set();
|
|
374
385
|
|
|
375
386
|
super.import(data);
|
|
376
387
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createResourceRoot } from "@assistant-ui/tap";
|
|
3
|
+
import type { Tool } from "assistant-stream";
|
|
4
|
+
import { ModelContext } from "./model-context-client";
|
|
5
|
+
import type {
|
|
6
|
+
ModelContext as ModelContextValue,
|
|
7
|
+
ModelContextProvider,
|
|
8
|
+
} from "../../model-context/types";
|
|
9
|
+
|
|
10
|
+
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
11
|
+
|
|
12
|
+
const provider = (ctx: ModelContextValue): ModelContextProvider => ({
|
|
13
|
+
getModelContext: () => ctx,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const stubTool = (): Tool<any, any> =>
|
|
17
|
+
({ description: "", parameters: {} as any }) as unknown as Tool<any, any>;
|
|
18
|
+
|
|
19
|
+
const render = () => {
|
|
20
|
+
const root = createResourceRoot();
|
|
21
|
+
const sub = root.render(ModelContext());
|
|
22
|
+
return { sub, unmount: () => root.unmount() };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("ModelContext", () => {
|
|
26
|
+
it("starts with undefined modelName and an empty toolNames array", () => {
|
|
27
|
+
const { sub, unmount } = render();
|
|
28
|
+
try {
|
|
29
|
+
const state = sub.getValue().getState();
|
|
30
|
+
expect(state.modelName).toBeUndefined();
|
|
31
|
+
expect(state.toolNames).toEqual([]);
|
|
32
|
+
} finally {
|
|
33
|
+
unmount();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reflects modelName from a registered provider", async () => {
|
|
38
|
+
const { sub, unmount } = render();
|
|
39
|
+
try {
|
|
40
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
41
|
+
await tick();
|
|
42
|
+
|
|
43
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
44
|
+
} finally {
|
|
45
|
+
unmount();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("reflects tool names from a registered provider", async () => {
|
|
50
|
+
const { sub, unmount } = render();
|
|
51
|
+
try {
|
|
52
|
+
sub
|
|
53
|
+
.getValue()
|
|
54
|
+
.register(provider({ tools: { foo: stubTool(), bar: stubTool() } }));
|
|
55
|
+
await tick();
|
|
56
|
+
|
|
57
|
+
expect(sub.getValue().getState().toolNames).toEqual(["bar", "foo"]);
|
|
58
|
+
} finally {
|
|
59
|
+
unmount();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("keeps the same state reference when an extra provider does not change the merged values", async () => {
|
|
64
|
+
const { sub, unmount } = render();
|
|
65
|
+
try {
|
|
66
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
67
|
+
await tick();
|
|
68
|
+
const before = sub.getValue().getState();
|
|
69
|
+
|
|
70
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
71
|
+
await tick();
|
|
72
|
+
const after = sub.getValue().getState();
|
|
73
|
+
|
|
74
|
+
expect(after).toBe(before);
|
|
75
|
+
} finally {
|
|
76
|
+
unmount();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("clears modelName when the last contributing provider unsubscribes", async () => {
|
|
81
|
+
const { sub, unmount } = render();
|
|
82
|
+
try {
|
|
83
|
+
const unsubscribe = sub
|
|
84
|
+
.getValue()
|
|
85
|
+
.register(provider({ config: { modelName: "gpt-4" } }));
|
|
86
|
+
await tick();
|
|
87
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
88
|
+
|
|
89
|
+
unsubscribe();
|
|
90
|
+
await tick();
|
|
91
|
+
expect(sub.getValue().getState().modelName).toBeUndefined();
|
|
92
|
+
expect(sub.getValue().getState().toolNames).toEqual([]);
|
|
93
|
+
} finally {
|
|
94
|
+
unmount();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("reflects modelName synchronously after register without awaiting", () => {
|
|
99
|
+
const { sub, unmount } = render();
|
|
100
|
+
try {
|
|
101
|
+
sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
|
|
102
|
+
|
|
103
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-4");
|
|
104
|
+
} finally {
|
|
105
|
+
unmount();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,18 +1,48 @@
|
|
|
1
|
-
import { resource, tapMemo, tapState } from "@assistant-ui/tap";
|
|
1
|
+
import { resource, tapEffect, tapMemo, tapState } from "@assistant-ui/tap";
|
|
2
2
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
3
3
|
import { CompositeContextProvider } from "../../utils/composite-context-provider";
|
|
4
4
|
import type { ModelContextState } from "../scopes/model-context";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const EMPTY_TOOL_NAMES: readonly string[] = [];
|
|
7
|
+
|
|
8
|
+
const INITIAL_STATE: ModelContextState = {
|
|
9
|
+
modelName: undefined,
|
|
10
|
+
toolNames: EMPTY_TOOL_NAMES,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const toolNamesEqual = (a: readonly string[], b: readonly string[]): boolean =>
|
|
14
|
+
a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
|
|
15
|
+
|
|
16
|
+
const deriveState = (
|
|
17
|
+
composite: CompositeContextProvider,
|
|
18
|
+
prev: ModelContextState,
|
|
19
|
+
): ModelContextState => {
|
|
20
|
+
const ctx = composite.getModelContext();
|
|
21
|
+
const modelName = ctx.config?.modelName;
|
|
22
|
+
const keys = ctx.tools ? Object.keys(ctx.tools).sort() : EMPTY_TOOL_NAMES;
|
|
23
|
+
const toolNames = keys.length ? keys : EMPTY_TOOL_NAMES;
|
|
24
|
+
|
|
25
|
+
if (modelName === prev.modelName && toolNamesEqual(toolNames, prev.toolNames))
|
|
26
|
+
return prev;
|
|
27
|
+
|
|
28
|
+
return { modelName, toolNames };
|
|
29
|
+
};
|
|
7
30
|
|
|
8
31
|
export const ModelContext = resource((): ClientOutput<"modelContext"> => {
|
|
9
|
-
const [state] = tapState<ModelContextState>(
|
|
10
|
-
() => ({ version: version + 1 }) as unknown as ModelContextState,
|
|
11
|
-
);
|
|
12
32
|
const composite = tapMemo(() => new CompositeContextProvider(), []);
|
|
33
|
+
const [state, setState] = tapState<ModelContextState>(() =>
|
|
34
|
+
deriveState(composite, INITIAL_STATE),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
tapEffect(() => {
|
|
38
|
+
setState((prev) => deriveState(composite, prev));
|
|
39
|
+
return composite.subscribe(() => {
|
|
40
|
+
setState((prev) => deriveState(composite, prev));
|
|
41
|
+
});
|
|
42
|
+
}, [composite]);
|
|
13
43
|
|
|
14
44
|
return {
|
|
15
|
-
getState: () => state,
|
|
45
|
+
getState: () => deriveState(composite, state),
|
|
16
46
|
getModelContext: () => composite.getModelContext(),
|
|
17
47
|
subscribe: (callback) => composite.subscribe(callback),
|
|
18
48
|
register: (provider) => composite.registerModelContextProvider(provider),
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
2
2
|
import type { ModelContextProvider } from "../../model-context/types";
|
|
3
3
|
|
|
4
|
-
export type ModelContextState =
|
|
4
|
+
export type ModelContextState = {
|
|
5
|
+
readonly modelName?: string | undefined;
|
|
6
|
+
readonly toolNames: readonly string[];
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
export type ModelContextMethods = ModelContextProvider & {
|
|
7
10
|
getState(): ModelContextState;
|
|
@@ -150,3 +150,116 @@ describe("ExternalStoreThreadRuntimeCore - state reference stability", () => {
|
|
|
150
150
|
expect(runtime.capabilities).toBe(capsBefore);
|
|
151
151
|
});
|
|
152
152
|
});
|
|
153
|
+
|
|
154
|
+
describe("ExternalStoreThreadRuntimeCore - messages reconciliation", () => {
|
|
155
|
+
const user = { id: "u", role: "user" as const, content: [] };
|
|
156
|
+
|
|
157
|
+
it("drops ids that disappear between syncs (same length, swapped assistant id)", () => {
|
|
158
|
+
const a1 = { id: "a1", role: "assistant" as const, content: [] };
|
|
159
|
+
const a2 = { id: "a2", role: "assistant" as const, content: [] };
|
|
160
|
+
|
|
161
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
162
|
+
mockContextProvider,
|
|
163
|
+
makeStore({ messages: [user, a1] }),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
runtime.__internal_setAdapter(makeStore({ messages: [user, a2] }));
|
|
167
|
+
|
|
168
|
+
const exported = runtime.export();
|
|
169
|
+
expect(exported.messages.map((m) => m.message.id)).toEqual(["u", "a2"]);
|
|
170
|
+
const userChildren = exported.messages
|
|
171
|
+
.filter((m) => m.parentId === "u")
|
|
172
|
+
.map((m) => m.message.id);
|
|
173
|
+
expect(userChildren).toEqual(["a2"]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("keeps prior ids when they remain in the new sync", () => {
|
|
177
|
+
const a = { id: "a", role: "assistant" as const, content: [] };
|
|
178
|
+
|
|
179
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
180
|
+
mockContextProvider,
|
|
181
|
+
makeStore({ messages: [user, a] }),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
runtime.__internal_setAdapter(makeStore({ messages: [user, a] }));
|
|
185
|
+
|
|
186
|
+
expect(runtime.export().messages.map((m) => m.message.id)).toEqual([
|
|
187
|
+
"u",
|
|
188
|
+
"a",
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("removes trailing messages dropped from the new sync", () => {
|
|
193
|
+
const a = { id: "a", role: "assistant" as const, content: [] };
|
|
194
|
+
const u2 = { id: "u2", role: "user" as const, content: [] };
|
|
195
|
+
|
|
196
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
197
|
+
mockContextProvider,
|
|
198
|
+
makeStore({ messages: [user, a, u2] }),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
runtime.__internal_setAdapter(makeStore({ messages: [user, a] }));
|
|
202
|
+
|
|
203
|
+
expect(runtime.export().messages.map((m) => m.message.id)).toEqual([
|
|
204
|
+
"u",
|
|
205
|
+
"a",
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("does not crash on the next sync after cancelRun removes a leaf user", () => {
|
|
210
|
+
const userWithText = {
|
|
211
|
+
id: "u",
|
|
212
|
+
role: "user" as const,
|
|
213
|
+
content: [{ type: "text" as const, text: "hi" }],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
217
|
+
mockContextProvider,
|
|
218
|
+
makeStore({
|
|
219
|
+
messages: [userWithText],
|
|
220
|
+
onCancel: vi.fn(),
|
|
221
|
+
isRunning: true,
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
runtime.cancelRun();
|
|
226
|
+
|
|
227
|
+
expect(() => {
|
|
228
|
+
runtime.__internal_setAdapter(makeStore({ messages: [] }));
|
|
229
|
+
}).not.toThrow();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("drops phantom sibling when convertMessage swaps the assistant id", () => {
|
|
233
|
+
type Raw = { id: string; role: "user" | "assistant"; text: string };
|
|
234
|
+
const rawU: Raw = { id: "u", role: "user", text: "hi" };
|
|
235
|
+
const rawA1: Raw = { id: "client_id", role: "assistant", text: "" };
|
|
236
|
+
const rawA2: Raw = { id: "server_id", role: "assistant", text: "" };
|
|
237
|
+
|
|
238
|
+
const convertMessage = (m: Raw) => ({
|
|
239
|
+
id: m.id,
|
|
240
|
+
role: m.role,
|
|
241
|
+
content: [{ type: "text" as const, text: m.text }],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
245
|
+
mockContextProvider,
|
|
246
|
+
makeStore({
|
|
247
|
+
messages: [rawU, rawA1] as any,
|
|
248
|
+
convertMessage: convertMessage as any,
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
runtime.__internal_setAdapter(
|
|
253
|
+
makeStore({
|
|
254
|
+
messages: [rawU, rawA2] as any,
|
|
255
|
+
convertMessage: convertMessage as any,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const userChildren = runtime
|
|
260
|
+
.export()
|
|
261
|
+
.messages.filter((m) => m.parentId === "u")
|
|
262
|
+
.map((m) => m.message.id);
|
|
263
|
+
expect(userChildren).toEqual(["server_id"]);
|
|
264
|
+
});
|
|
265
|
+
});
|
package/src/types/message.ts
CHANGED
|
@@ -92,18 +92,38 @@ export type ToolCallMessagePart<
|
|
|
92
92
|
TArgs = ReadonlyJSONObject,
|
|
93
93
|
TResult = unknown,
|
|
94
94
|
> = {
|
|
95
|
+
/** Identifies this part as a tool call. */
|
|
95
96
|
readonly type: "tool-call";
|
|
97
|
+
/** Stable identifier for this invocation of the tool. */
|
|
96
98
|
readonly toolCallId: string;
|
|
99
|
+
/** Name of the tool requested by the model. */
|
|
97
100
|
readonly toolName: string;
|
|
101
|
+
/**
|
|
102
|
+
* Arguments supplied by the model. During streaming this is a partial parse:
|
|
103
|
+
* fields may be missing or incomplete. From a tool-call renderer, use
|
|
104
|
+
* `useToolArgsStatus` to detect which fields are still arriving.
|
|
105
|
+
*/
|
|
98
106
|
readonly args: TArgs;
|
|
107
|
+
/** Result returned by the tool, if it has completed. */
|
|
99
108
|
readonly result?: TResult | undefined;
|
|
109
|
+
/** Whether the result represents a tool execution error. */
|
|
100
110
|
readonly isError?: boolean | undefined;
|
|
111
|
+
/** Raw JSON argument text streamed by the model. */
|
|
101
112
|
readonly argsText: string;
|
|
113
|
+
/** UI-only artifact associated with the tool result. */
|
|
102
114
|
readonly artifact?: unknown;
|
|
115
|
+
/** MCP app metadata associated with this tool call, when present. */
|
|
103
116
|
readonly mcp?: ToolCallMessagePartMcpMetadata;
|
|
117
|
+
/** Content returned to the model for this tool result. */
|
|
104
118
|
readonly modelContent?: readonly ToolModelContentPart[] | undefined;
|
|
119
|
+
/** Human-input request that must be resolved before the run can continue. */
|
|
105
120
|
readonly interrupt?: { type: "human"; payload: unknown };
|
|
121
|
+
/** Parent message-part ID when this part belongs to a nested structure. */
|
|
106
122
|
readonly parentId?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Nested thread messages produced by this tool call, for example a sub-agent
|
|
125
|
+
* conversation.
|
|
126
|
+
*/
|
|
107
127
|
readonly messages?: readonly ThreadMessage[];
|
|
108
128
|
};
|
|
109
129
|
|
|
@@ -143,7 +163,9 @@ export type MessagePartStatus =
|
|
|
143
163
|
|
|
144
164
|
export type ToolCallMessagePartStatus =
|
|
145
165
|
| {
|
|
166
|
+
/** The tool call is waiting for UI or human input before continuing. */
|
|
146
167
|
readonly type: "requires-action";
|
|
168
|
+
/** Reason the tool call requires action. */
|
|
147
169
|
readonly reason: "interrupt";
|
|
148
170
|
}
|
|
149
171
|
| MessagePartStatus;
|