@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
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { isMcpAppUri } from "../../types/message";
|
|
2
|
+
import type { PartState } from "../../store/scopes/part";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Hierarchical adjacent-coalescing grouping for message parts.
|
|
3
6
|
*
|
|
@@ -11,16 +14,71 @@
|
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Symbol attached to memoizable `groupBy` functions (e.g. those returned
|
|
18
|
+
* by {@link groupPartByType}). Carries a string fingerprint of the config
|
|
19
|
+
* so `MessagePrimitive.GroupedParts` can memo the tree on
|
|
20
|
+
* `[parts, memoKey]` across renders — even when the helper call site
|
|
21
|
+
* reconstructs the function each render.
|
|
22
|
+
*/
|
|
23
|
+
export const GROUPBY_MEMO_KEY: unique symbol = Symbol.for(
|
|
24
|
+
"@assistant-ui/groupBy.memoKey",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Synthetic part-type key recognized by {@link groupPartByType}: a
|
|
29
|
+
* tool-call whose `mcp.app.resourceUri` points at an assistant-ui MCP
|
|
30
|
+
* app. Map this key to control how MCP-app tool calls are grouped —
|
|
31
|
+
* separately from regular `"tool-call"` parts.
|
|
32
|
+
*/
|
|
33
|
+
type GroupPartType = PartState["type"] | "mcp-app";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a `groupBy` from a `part.type → group-key path` lookup.
|
|
37
|
+
* Parts whose type isn't in the map are left ungrouped. The returned
|
|
38
|
+
* function carries a stable {@link GROUPBY_MEMO_KEY} fingerprint so
|
|
39
|
+
* `<MessagePrimitive.GroupedParts>` can memoize its tree across renders.
|
|
40
|
+
*
|
|
41
|
+
* Special key `"mcp-app"` matches tool-call parts that point at an
|
|
42
|
+
* assistant-ui MCP app resource (`ui://...`) and takes precedence over
|
|
43
|
+
* the `"tool-call"` entry for those parts.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <MessagePrimitive.GroupedParts
|
|
48
|
+
* groupBy={groupPartByType({
|
|
49
|
+
* reasoning: ["group-thought", "group-reasoning"],
|
|
50
|
+
* "tool-call": ["group-thought", "group-tool"],
|
|
51
|
+
* "mcp-app": [],
|
|
52
|
+
* })}
|
|
53
|
+
* >
|
|
54
|
+
* {({ part, children }) => { ... }}
|
|
55
|
+
* </MessagePrimitive.GroupedParts>
|
|
56
|
+
* ```
|
|
18
57
|
*/
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
|
23
|
-
|
|
58
|
+
export const groupPartByType = <TKey extends `group-${string}`>(
|
|
59
|
+
map: Partial<Readonly<Record<GroupPartType, readonly TKey[]>>>,
|
|
60
|
+
): ((part: PartState) => readonly TKey[]) => {
|
|
61
|
+
const lookup = map as Readonly<Record<string, readonly TKey[] | undefined>>;
|
|
62
|
+
const fn = ((part) => {
|
|
63
|
+
if (
|
|
64
|
+
part.type === "tool-call" &&
|
|
65
|
+
lookup["mcp-app"] !== undefined &&
|
|
66
|
+
isMcpAppUri(part.mcp?.app?.resourceUri)
|
|
67
|
+
) {
|
|
68
|
+
return lookup["mcp-app"]!;
|
|
69
|
+
}
|
|
70
|
+
return lookup[part.type] ?? [];
|
|
71
|
+
}) as ((part: PartState) => readonly TKey[]) & {
|
|
72
|
+
[GROUPBY_MEMO_KEY]?: string;
|
|
73
|
+
};
|
|
74
|
+
// Sort keys so the fingerprint is insensitive to map insertion order —
|
|
75
|
+
// two maps with the same key/value pairs but different declaration order
|
|
76
|
+
// would otherwise hash differently and invalidate the memo unnecessarily.
|
|
77
|
+
const sortedKeys = Object.keys(map).sort();
|
|
78
|
+
const sortedEntries = sortedKeys.map((k) => [k, map[k as keyof typeof map]]);
|
|
79
|
+
fn[GROUPBY_MEMO_KEY] = `groupPartByType:${JSON.stringify(sortedEntries)}`;
|
|
80
|
+
return fn;
|
|
81
|
+
};
|
|
24
82
|
|
|
25
83
|
export type GroupNode = GroupNodeGroup | GroupNodePart;
|
|
26
84
|
|
|
@@ -43,19 +101,6 @@ export interface GroupNodePart {
|
|
|
43
101
|
readonly nodeKey: string;
|
|
44
102
|
}
|
|
45
103
|
|
|
46
|
-
const EMPTY_PATH: readonly string[] = Object.freeze([]);
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Normalize a `groupBy` return value to a path array.
|
|
50
|
-
* `null`/`undefined`/`[]` → `[]` (ungrouped).
|
|
51
|
-
* `"foo"` → `["foo"]`. Arrays pass through.
|
|
52
|
-
*/
|
|
53
|
-
export const normalizeGroupKey = (key: GroupKey): readonly string[] => {
|
|
54
|
-
if (key == null) return EMPTY_PATH;
|
|
55
|
-
if (typeof key === "string") return [key];
|
|
56
|
-
return key;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
104
|
interface BuildFrame {
|
|
60
105
|
key: string;
|
|
61
106
|
nodeKey: string;
|
|
@@ -42,8 +42,7 @@ export type AttachmentRuntime<
|
|
|
42
42
|
|
|
43
43
|
export abstract class AttachmentRuntimeImpl<
|
|
44
44
|
Source extends AttachmentRuntimeSource = AttachmentRuntimeSource,
|
|
45
|
-
> implements AttachmentRuntime
|
|
46
|
-
{
|
|
45
|
+
> implements AttachmentRuntime {
|
|
47
46
|
public get path() {
|
|
48
47
|
return this._core.path;
|
|
49
48
|
}
|
|
@@ -26,6 +26,7 @@ type MessagePartSnapshotBinding = SubscribableWithState<
|
|
|
26
26
|
export type MessagePartRuntime = {
|
|
27
27
|
addToolResult(result: any | ToolResponse<any>): void;
|
|
28
28
|
resumeToolCall(payload: unknown): void;
|
|
29
|
+
respondToToolApproval(response: { approved: boolean; reason?: string }): void;
|
|
29
30
|
|
|
30
31
|
readonly path: MessagePartRuntimePath;
|
|
31
32
|
getState(): MessagePartState;
|
|
@@ -48,6 +49,7 @@ export class MessagePartRuntimeImpl implements MessagePartRuntime {
|
|
|
48
49
|
protected __internal_bindMethods() {
|
|
49
50
|
this.addToolResult = this.addToolResult.bind(this);
|
|
50
51
|
this.resumeToolCall = this.resumeToolCall.bind(this);
|
|
52
|
+
this.respondToToolApproval = this.respondToToolApproval.bind(this);
|
|
51
53
|
this.getState = this.getState.bind(this);
|
|
52
54
|
this.subscribe = this.subscribe.bind(this);
|
|
53
55
|
}
|
|
@@ -102,6 +104,30 @@ export class MessagePartRuntimeImpl implements MessagePartRuntime {
|
|
|
102
104
|
});
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
public respondToToolApproval(response: {
|
|
108
|
+
approved: boolean;
|
|
109
|
+
reason?: string;
|
|
110
|
+
}) {
|
|
111
|
+
const state = this.contentBinding.getState();
|
|
112
|
+
if (!state) throw new Error("Message part is not available");
|
|
113
|
+
|
|
114
|
+
if (state.type !== "tool-call")
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Tried to respond to tool approval on non-tool message part",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!state.approval || state.approval.approved !== undefined)
|
|
120
|
+
throw new Error("Tool call has no pending approval");
|
|
121
|
+
|
|
122
|
+
if (!this.threadApi) throw new Error("Thread API is not available");
|
|
123
|
+
|
|
124
|
+
this.threadApi.getState().respondToToolApproval({
|
|
125
|
+
approvalId: state.approval.id,
|
|
126
|
+
approved: response.approved,
|
|
127
|
+
...(response.reason != null && { reason: response.reason }),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
105
131
|
public subscribe(callback: () => void) {
|
|
106
132
|
return this.contentBinding.subscribe(callback);
|
|
107
133
|
}
|
|
@@ -15,6 +15,7 @@ import { DefaultThreadComposerRuntimeCore } from "./default-thread-composer-runt
|
|
|
15
15
|
import type {
|
|
16
16
|
AddToolResultOptions,
|
|
17
17
|
ResumeToolCallOptions,
|
|
18
|
+
RespondToToolApprovalOptions,
|
|
18
19
|
ThreadSuggestion,
|
|
19
20
|
SubmitFeedbackOptions,
|
|
20
21
|
ThreadRuntimeCore,
|
|
@@ -59,6 +60,9 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
59
60
|
public abstract resumeRun(config: ResumeRunConfig): void;
|
|
60
61
|
public abstract addToolResult(options: AddToolResultOptions): void;
|
|
61
62
|
public abstract resumeToolCall(options: ResumeToolCallOptions): void;
|
|
63
|
+
public abstract respondToToolApproval(
|
|
64
|
+
options: RespondToToolApprovalOptions,
|
|
65
|
+
): void;
|
|
62
66
|
public abstract cancelRun(): void;
|
|
63
67
|
public abstract exportExternalState(): any;
|
|
64
68
|
public abstract importExternalState(state: any): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolModelContentPart } from "assistant-stream";
|
|
1
2
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
2
3
|
import type { ModelContext } from "../../model-context/types";
|
|
3
4
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
@@ -38,6 +39,13 @@ export type AddToolResultOptions = {
|
|
|
38
39
|
result: ReadonlyJSONValue;
|
|
39
40
|
isError: boolean;
|
|
40
41
|
artifact?: ReadonlyJSONValue | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Optional model-content payload produced by the tool. Populated when a
|
|
44
|
+
* client-side `execute()` or `streamCall` returns a `ToolResponse` with
|
|
45
|
+
* `modelContent`. Forwarded through `adapter.onAddToolResult` so the
|
|
46
|
+
* adapter can include it when sending the result back to its backend.
|
|
47
|
+
*/
|
|
48
|
+
modelContent?: readonly ToolModelContentPart[] | undefined;
|
|
41
49
|
};
|
|
42
50
|
|
|
43
51
|
export type ResumeToolCallOptions = {
|
|
@@ -45,6 +53,12 @@ export type ResumeToolCallOptions = {
|
|
|
45
53
|
payload: unknown;
|
|
46
54
|
};
|
|
47
55
|
|
|
56
|
+
export type RespondToToolApprovalOptions = {
|
|
57
|
+
approvalId: string;
|
|
58
|
+
approved: boolean;
|
|
59
|
+
reason?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
48
62
|
export type SubmitFeedbackOptions = {
|
|
49
63
|
messageId: string;
|
|
50
64
|
type: "negative" | "positive";
|
|
@@ -137,6 +151,7 @@ export type ThreadRuntimeCore = Readonly<{
|
|
|
137
151
|
|
|
138
152
|
addToolResult: (options: AddToolResultOptions) => void;
|
|
139
153
|
resumeToolCall: (options: ResumeToolCallOptions) => void;
|
|
154
|
+
respondToToolApproval: (options: RespondToToolApprovalOptions) => void;
|
|
140
155
|
|
|
141
156
|
speak: (messageId: string) => void;
|
|
142
157
|
stopSpeaking: () => void;
|
package/src/runtime/internal.ts
CHANGED
|
@@ -18,10 +18,7 @@ export { DefaultEditComposerRuntimeCore } from "./base/default-edit-composer-run
|
|
|
18
18
|
// Runtime Impl Classes
|
|
19
19
|
export { AssistantRuntimeImpl } from "./api/assistant-runtime";
|
|
20
20
|
|
|
21
|
-
export {
|
|
22
|
-
getThreadState,
|
|
23
|
-
ThreadRuntimeImpl,
|
|
24
|
-
} from "./api/thread-runtime";
|
|
21
|
+
export { getThreadState, ThreadRuntimeImpl } from "./api/thread-runtime";
|
|
25
22
|
export type {
|
|
26
23
|
ThreadRuntimeCoreBinding,
|
|
27
24
|
ThreadListItemRuntimeBinding,
|
|
@@ -54,6 +54,13 @@ export type ThreadMessageLike = {
|
|
|
54
54
|
readonly isError?: boolean | undefined;
|
|
55
55
|
readonly parentId?: string | undefined;
|
|
56
56
|
readonly messages?: readonly ThreadMessage[] | undefined;
|
|
57
|
+
readonly interrupt?: { type: "human"; payload: unknown };
|
|
58
|
+
readonly approval?: {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly approved?: boolean;
|
|
61
|
+
readonly reason?: string;
|
|
62
|
+
readonly isAutomatic?: boolean;
|
|
63
|
+
};
|
|
57
64
|
}
|
|
58
65
|
)[];
|
|
59
66
|
readonly id?: string | undefined;
|
|
@@ -9,12 +9,14 @@ import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
|
9
9
|
import type { FeedbackAdapter } from "../../adapters/feedback";
|
|
10
10
|
import type {
|
|
11
11
|
AddToolResultOptions,
|
|
12
|
+
RespondToToolApprovalOptions,
|
|
12
13
|
StartRunConfig,
|
|
13
14
|
ResumeRunConfig,
|
|
14
15
|
ThreadSuggestion,
|
|
15
16
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
16
17
|
import type { ExportedMessageRepository } from "../../runtime/utils/message-repository";
|
|
17
18
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
19
|
+
import type { ToolExecutionStatus } from "../tool-invocations/ToolInvocationTracker";
|
|
18
20
|
|
|
19
21
|
export type ExternalStoreThreadData<TState extends "regular" | "archived"> = {
|
|
20
22
|
status: TState;
|
|
@@ -108,6 +110,9 @@ type ExternalStoreAdapterBase<T> = {
|
|
|
108
110
|
onResumeToolCall?:
|
|
109
111
|
| ((options: { toolCallId: string; payload: unknown }) => void)
|
|
110
112
|
| undefined;
|
|
113
|
+
onRespondToToolApproval?:
|
|
114
|
+
| ((options: RespondToToolApprovalOptions) => Promise<void> | void)
|
|
115
|
+
| undefined;
|
|
111
116
|
convertMessage?: ExternalStoreMessageConverter<T> | undefined;
|
|
112
117
|
adapters?:
|
|
113
118
|
| {
|
|
@@ -127,6 +132,38 @@ type ExternalStoreAdapterBase<T> = {
|
|
|
127
132
|
copy?: boolean | undefined;
|
|
128
133
|
}
|
|
129
134
|
| undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Opt in to the built-in client-side tool-invocations pipeline
|
|
137
|
+
* (`streamCall` / `execute` / tool-status tracking) for this thread.
|
|
138
|
+
*
|
|
139
|
+
* Defaults to `false` — the runtime does *not* drive client-side tool
|
|
140
|
+
* callbacks on its own. Set to `true` to have the runtime construct a
|
|
141
|
+
* `ToolInvocationTracker` and feed every snapshot through it, so tool
|
|
142
|
+
* callbacks fire automatically for tool-call parts in `messages`.
|
|
143
|
+
*
|
|
144
|
+
* Opt-in by default because most external-store runtimes either run
|
|
145
|
+
* tools entirely server-side, or already wire their own client-side
|
|
146
|
+
* dispatch path. Enabling the embedded tracker on top of an existing
|
|
147
|
+
* dispatch path would cause tool callbacks to run twice.
|
|
148
|
+
*
|
|
149
|
+
* When enabled, client-side tool results (from `execute()` returning,
|
|
150
|
+
* or from `streamCall` resolving) flow back through
|
|
151
|
+
* `adapter.onAddToolResult` like any other tool result, with
|
|
152
|
+
* `modelContent` populated when present.
|
|
153
|
+
*/
|
|
154
|
+
unstable_enableToolInvocations?: boolean | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* Receives the current per-tool-call execution status map whenever it
|
|
157
|
+
* changes. Only invoked when `unstable_enableToolInvocations` is `true`
|
|
158
|
+
* — the runtime maintains the map via the embedded tracker.
|
|
159
|
+
*
|
|
160
|
+
* Wire this into local React state and feed it into the converter's
|
|
161
|
+
* `metadata.toolStatuses` so the UI can render `executing` spinners
|
|
162
|
+
* and human-input prompts.
|
|
163
|
+
*/
|
|
164
|
+
setToolStatuses?:
|
|
165
|
+
| ((statuses: Record<string, ToolExecutionStatus>) => void)
|
|
166
|
+
| undefined;
|
|
130
167
|
};
|
|
131
168
|
|
|
132
169
|
export type ExternalStoreAdapter<T = ThreadMessage> =
|
|
@@ -22,9 +22,7 @@ const DEFAULT_THREAD_DATA = Object.freeze({
|
|
|
22
22
|
[DEFAULT_THREAD_ID]: DEFAULT_THREAD,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export class ExternalStoreThreadListRuntimeCore
|
|
26
|
-
implements ThreadListRuntimeCore
|
|
27
|
-
{
|
|
25
|
+
export class ExternalStoreThreadListRuntimeCore implements ThreadListRuntimeCore {
|
|
28
26
|
private _mainThreadId: string = DEFAULT_THREAD_ID;
|
|
29
27
|
private _threads: readonly string[] = DEFAULT_THREADS;
|
|
30
28
|
private _archivedThreads: readonly string[] = EMPTY_ARRAY;
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
AddToolResultOptions,
|
|
4
4
|
ResumeRunConfig,
|
|
5
5
|
ResumeToolCallOptions,
|
|
6
|
+
RespondToToolApprovalOptions,
|
|
6
7
|
StartRunConfig,
|
|
7
8
|
ThreadSuggestion,
|
|
8
9
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
ExportedMessageRepository,
|
|
30
31
|
MessageRepository,
|
|
31
32
|
} from "../../runtime/utils/message-repository";
|
|
33
|
+
import { ToolInvocationTracker } from "../tool-invocations/ToolInvocationTracker";
|
|
32
34
|
|
|
33
35
|
const EMPTY_ARRAY: readonly ThreadSuggestion[] = Object.freeze([]);
|
|
34
36
|
|
|
@@ -104,6 +106,12 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
104
106
|
|
|
105
107
|
private _store!: ExternalStoreAdapter<any>;
|
|
106
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Client-side tool-invocations pipeline. Constructed lazily on first
|
|
111
|
+
* snapshot — only when `adapter.unstable_enableToolInvocations === true`.
|
|
112
|
+
*/
|
|
113
|
+
private _toolInvocations: ToolInvocationTracker | null = null;
|
|
114
|
+
|
|
107
115
|
public override beginEdit(messageId: string) {
|
|
108
116
|
if (!this._store.onEdit)
|
|
109
117
|
throw new Error("Runtime does not support editing.");
|
|
@@ -272,9 +280,113 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
272
280
|
);
|
|
273
281
|
|
|
274
282
|
this._messages = this.repository.getMessages();
|
|
283
|
+
|
|
284
|
+
this._driveToolInvocations();
|
|
285
|
+
|
|
275
286
|
this._notifySubscribers();
|
|
276
287
|
}
|
|
277
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Feed the current message snapshot into the tool-invocations tracker.
|
|
291
|
+
* Opt-in via `adapter.unstable_enableToolInvocations: true`. The tracker
|
|
292
|
+
* itself is fail-silent — see ToolInvocationTracker for the
|
|
293
|
+
* state-transition contract.
|
|
294
|
+
*/
|
|
295
|
+
private _driveToolInvocations(): void {
|
|
296
|
+
if (!this._store.unstable_enableToolInvocations) {
|
|
297
|
+
// Adapter did not opt in (default). If a tracker was previously
|
|
298
|
+
// constructed (e.g. the adapter just toggled the flag off via a
|
|
299
|
+
// dynamic swap), drop it so subsequent snapshots are no-ops.
|
|
300
|
+
if (this._toolInvocations) {
|
|
301
|
+
this._toolInvocations.reset();
|
|
302
|
+
this._toolInvocations = null;
|
|
303
|
+
this._store.setToolStatuses?.({});
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!this._toolInvocations) {
|
|
309
|
+
this._toolInvocations = new ToolInvocationTracker(
|
|
310
|
+
() => this.getModelContext().tools,
|
|
311
|
+
{
|
|
312
|
+
onResult: (command) => {
|
|
313
|
+
try {
|
|
314
|
+
const messageId = this._findMessageIdForToolCall(
|
|
315
|
+
command.toolCallId,
|
|
316
|
+
);
|
|
317
|
+
if (messageId === undefined) {
|
|
318
|
+
// The tool call no longer exists in the snapshot (e.g.
|
|
319
|
+
// rolled back). Drop the result.
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
this._store.onAddToolResult?.({
|
|
323
|
+
messageId,
|
|
324
|
+
toolCallId: command.toolCallId,
|
|
325
|
+
toolName: command.toolName,
|
|
326
|
+
result: command.result,
|
|
327
|
+
isError: command.isError,
|
|
328
|
+
...(command.artifact !== undefined && {
|
|
329
|
+
artifact: command.artifact,
|
|
330
|
+
}),
|
|
331
|
+
...(command.modelContent !== undefined && {
|
|
332
|
+
modelContent: command.modelContent,
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.error(
|
|
337
|
+
"[ExternalStoreThreadRuntimeCore] onAddToolResult dispatch failed",
|
|
338
|
+
err,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
onStatusesChange: (statuses) => {
|
|
343
|
+
this._store.setToolStatuses?.(Object.fromEntries(statuses));
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
this._toolInvocations.setState({
|
|
350
|
+
messages: this._messages,
|
|
351
|
+
isRunning: this._store.isRunning ?? false,
|
|
352
|
+
...(this._store.isLoading !== undefined && {
|
|
353
|
+
isLoading: this._store.isLoading,
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Lookup table from `toolCallId` to the owning assistant message's `id`,
|
|
360
|
+
* rebuilt lazily when `_messages` changes (see `_messagesForToolCallIndex`).
|
|
361
|
+
*/
|
|
362
|
+
private _toolCallToMessageId = new Map<string, string>();
|
|
363
|
+
private _messagesForToolCallIndex: readonly ThreadMessage[] | null = null;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Look up the assistant message that owns a tool-call part. Lazily builds
|
|
367
|
+
* (and caches) a `toolCallId → messageId` map keyed off the current
|
|
368
|
+
* `_messages` reference, so onResult dispatches stay O(1) instead of
|
|
369
|
+
* walking the full thread on every result.
|
|
370
|
+
*/
|
|
371
|
+
private _findMessageIdForToolCall(toolCallId: string): string | undefined {
|
|
372
|
+
if (this._messagesForToolCallIndex !== this._messages) {
|
|
373
|
+
this._toolCallToMessageId.clear();
|
|
374
|
+
const visit = (messages: readonly ThreadMessage[]): void => {
|
|
375
|
+
for (const message of messages) {
|
|
376
|
+
if (!Array.isArray(message.content)) continue;
|
|
377
|
+
for (const part of message.content) {
|
|
378
|
+
if (!part || part.type !== "tool-call") continue;
|
|
379
|
+
this._toolCallToMessageId.set(part.toolCallId, message.id);
|
|
380
|
+
if (part.messages) visit(part.messages);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
visit(this._messages);
|
|
385
|
+
this._messagesForToolCallIndex = this._messages;
|
|
386
|
+
}
|
|
387
|
+
return this._toolCallToMessageId.get(toolCallId);
|
|
388
|
+
}
|
|
389
|
+
|
|
278
390
|
public override switchToBranch(branchId: string): void {
|
|
279
391
|
if (!this._store.setMessages)
|
|
280
392
|
throw new Error("Runtime does not support switching branches.");
|
|
@@ -289,6 +401,16 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
289
401
|
}
|
|
290
402
|
|
|
291
403
|
public async append(message: AppendMessage): Promise<void> {
|
|
404
|
+
// Auto-abort in-flight client-side tool executions when a new run is
|
|
405
|
+
// about to start. Without this, a tool that finishes after the new turn
|
|
406
|
+
// begins would feed a stale result into `onAddToolResult`, racing with
|
|
407
|
+
// the new turn the user just initiated. `startRun` defaults to true for
|
|
408
|
+
// user messages — matches the satellites' historical opt-in cancel
|
|
409
|
+
// behavior, which is now built in.
|
|
410
|
+
if (message.startRun ?? message.role === "user") {
|
|
411
|
+
await this._toolInvocations?.abort();
|
|
412
|
+
}
|
|
413
|
+
|
|
292
414
|
if (message.parentId !== (this.messages.at(-1)?.id ?? null)) {
|
|
293
415
|
if (!this._store.onEdit)
|
|
294
416
|
throw new Error("Runtime does not support editing messages.");
|
|
@@ -302,6 +424,11 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
302
424
|
if (!this._store.onReload)
|
|
303
425
|
throw new Error("Runtime does not support reloading messages.");
|
|
304
426
|
|
|
427
|
+
// Auto-abort in-flight client-side tool executions when a run reloads;
|
|
428
|
+
// any results that land afterward would target a turn that no longer
|
|
429
|
+
// exists. See `append` above for full rationale.
|
|
430
|
+
await this._toolInvocations?.abort();
|
|
431
|
+
|
|
305
432
|
await this._store.onReload(config.parentId, config);
|
|
306
433
|
}
|
|
307
434
|
|
|
@@ -323,6 +450,18 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
323
450
|
if (!this._store.onLoadExternalState)
|
|
324
451
|
throw new Error("Runtime does not support importing external states.");
|
|
325
452
|
|
|
453
|
+
// Re-arm the tracker so the next adapter snapshot (containing the
|
|
454
|
+
// imported state) is treated as historical — no streamCall/execute
|
|
455
|
+
// fires for the loaded tool calls. The adapter is expected to update
|
|
456
|
+
// its messages in response to onLoadExternalState; that update flows
|
|
457
|
+
// back here via __internal_setAdapter. We only clear adapter-side
|
|
458
|
+
// tool statuses when the tracker is the source of truth — otherwise
|
|
459
|
+
// we'd wipe statuses the adapter is managing on its own.
|
|
460
|
+
if (this._toolInvocations) {
|
|
461
|
+
this._toolInvocations.reset();
|
|
462
|
+
this._store.setToolStatuses?.({});
|
|
463
|
+
}
|
|
464
|
+
|
|
326
465
|
this._store.onLoadExternalState(state);
|
|
327
466
|
}
|
|
328
467
|
|
|
@@ -330,6 +469,11 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
330
469
|
if (!this._store.onCancel)
|
|
331
470
|
throw new Error("Runtime does not support cancelling runs.");
|
|
332
471
|
|
|
472
|
+
// Abort any in-flight client-side tool executions. Fire-and-forget —
|
|
473
|
+
// the abort resolves once executions settle, but we don't gate the
|
|
474
|
+
// cancel on it.
|
|
475
|
+
void this._toolInvocations?.abort();
|
|
476
|
+
|
|
333
477
|
this._store.onCancel();
|
|
334
478
|
|
|
335
479
|
if (this._assistantOptimisticId) {
|
|
@@ -361,15 +505,35 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
361
505
|
}
|
|
362
506
|
|
|
363
507
|
public addToolResult(options: AddToolResultOptions) {
|
|
364
|
-
if (!this._store.onAddToolResult
|
|
508
|
+
if (!this._store.onAddToolResult)
|
|
365
509
|
throw new Error("Runtime does not support tool results.");
|
|
366
510
|
this._store.onAddToolResult?.(options);
|
|
367
511
|
}
|
|
368
512
|
|
|
369
513
|
public resumeToolCall(options: ResumeToolCallOptions) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
514
|
+
// Tracker owns its own human-input handlers — let it resume in-process
|
|
515
|
+
// tool calls without round-tripping through the adapter. Falls back to
|
|
516
|
+
// the adapter's onResumeToolCall (if any) for tool calls the tracker
|
|
517
|
+
// doesn't know about.
|
|
518
|
+
const handled =
|
|
519
|
+
this._toolInvocations?.resume(options.toolCallId, options.payload) ??
|
|
520
|
+
false;
|
|
521
|
+
if (handled) return;
|
|
522
|
+
|
|
523
|
+
if (this._store.onResumeToolCall) {
|
|
524
|
+
this._store.onResumeToolCall(options);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
throw new Error(
|
|
529
|
+
`Tool call ${options.toolCallId} is not waiting for resume.`,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
public respondToToolApproval(options: RespondToToolApprovalOptions) {
|
|
534
|
+
if (!this._store.onRespondToToolApproval)
|
|
535
|
+
throw new Error("Runtime does not support tool approvals.");
|
|
536
|
+
this._store.onRespondToToolApproval(options);
|
|
373
537
|
}
|
|
374
538
|
|
|
375
539
|
public override reset(initialMessages?: readonly ThreadMessageLike[]) {
|
|
@@ -9,6 +9,7 @@ import type { LocalRuntimeOptionsBase } from "./local-runtime-options";
|
|
|
9
9
|
import type {
|
|
10
10
|
AddToolResultOptions,
|
|
11
11
|
ResumeToolCallOptions,
|
|
12
|
+
RespondToToolApprovalOptions,
|
|
12
13
|
ThreadSuggestion,
|
|
13
14
|
ThreadRuntimeCore,
|
|
14
15
|
StartRunConfig,
|
|
@@ -535,4 +536,8 @@ export class LocalThreadRuntimeCore
|
|
|
535
536
|
public resumeToolCall(_options: ResumeToolCallOptions) {
|
|
536
537
|
throw new Error("Local runtime does not support resuming tool calls.");
|
|
537
538
|
}
|
|
539
|
+
|
|
540
|
+
public respondToToolApproval(_options: RespondToToolApprovalOptions) {
|
|
541
|
+
throw new Error("Local runtime does not support tool approvals.");
|
|
542
|
+
}
|
|
538
543
|
}
|