@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.0
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/LICENSE +21 -0
- package/README.md +233 -2
- package/dist/assistant-ui/entry.d.ts +34 -0
- package/dist/assistant-ui/entry.d.ts.map +1 -0
- package/dist/assistant-ui/entry.js +50 -0
- package/dist/assistant-ui/entry.js.map +1 -0
- package/dist/assistant-ui/extension.d.ts +18 -0
- package/dist/assistant-ui/extension.d.ts.map +1 -0
- package/dist/assistant-ui/extension.js +0 -0
- package/dist/assistant-ui/history.d.ts +15 -0
- package/dist/assistant-ui/history.d.ts.map +1 -0
- package/dist/assistant-ui/history.js +39 -0
- package/dist/assistant-ui/history.js.map +1 -0
- package/dist/assistant-ui/modelContext.d.ts +45 -0
- package/dist/assistant-ui/modelContext.d.ts.map +1 -0
- package/dist/assistant-ui/modelContext.js +88 -0
- package/dist/assistant-ui/modelContext.js.map +1 -0
- package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
- package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
- package/dist/assistant-ui/snapshotSaver.js +83 -0
- package/dist/assistant-ui/snapshotSaver.js.map +1 -0
- package/dist/assistant-ui/tool-calls.d.ts +21 -0
- package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
- package/dist/assistant-ui/tool-calls.js +65 -0
- package/dist/assistant-ui/tool-calls.js.map +1 -0
- package/dist/assistant-ui/toolkit.d.ts +96 -0
- package/dist/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/assistant-ui/toolkit.js +456 -0
- package/dist/assistant-ui/toolkit.js.map +1 -0
- package/dist/assistant-ui/useSharedContext.d.ts +14 -0
- package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
- package/dist/assistant-ui/useSharedContext.js +27 -0
- package/dist/assistant-ui/useSharedContext.js.map +1 -0
- package/dist/assistant-ui.d.ts +3 -0
- package/dist/assistant-ui.d.ts.map +1 -0
- package/dist/assistant-ui.js +1 -0
- package/dist/bootstrap.d.ts +23 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +78 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/csp.d.ts +25 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +78 -0
- package/dist/csp.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/json-schema.d.ts +70 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +163 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/preview.d.ts +30 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +60 -0
- package/dist/preview.js.map +1 -0
- package/dist/prompts/guidance.d.ts +41 -0
- package/dist/prompts/guidance.d.ts.map +1 -0
- package/dist/prompts/guidance.js +268 -0
- package/dist/prompts/guidance.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/protocol.d.ts +114 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +49 -0
- package/dist/protocol.js.map +1 -0
- package/dist/repair/repair.d.ts +72 -0
- package/dist/repair/repair.d.ts.map +1 -0
- package/dist/repair/repair.js +103 -0
- package/dist/repair/repair.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +1 -0
- package/dist/rpc.d.ts +60 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +17 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +128 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/generated.d.ts +2 -0
- package/dist/runtime/generated.d.ts.map +1 -0
- package/dist/runtime/generated.js +5 -0
- package/dist/runtime/generated.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +264 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/morph.d.ts +51 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +251 -0
- package/dist/runtime/morph.js.map +1 -0
- package/dist/runtime/screenshot.d.ts +23 -0
- package/dist/runtime/screenshot.d.ts.map +1 -0
- package/dist/runtime/screenshot.js +188 -0
- package/dist/runtime/screenshot.js.map +1 -0
- package/dist/runtime/size.d.ts +10 -0
- package/dist/runtime/size.d.ts.map +1 -0
- package/dist/runtime/size.js +21 -0
- package/dist/runtime/size.js.map +1 -0
- package/dist/runtime/storage.d.ts +4 -0
- package/dist/runtime/storage.d.ts.map +1 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/spec/actions.d.ts +40 -0
- package/dist/spec/actions.d.ts.map +1 -0
- package/dist/spec/actions.js +53 -0
- package/dist/spec/actions.js.map +1 -0
- package/dist/spec/assistant-ui/thread.d.ts +66 -0
- package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
- package/dist/spec/assistant-ui/thread.js +252 -0
- package/dist/spec/assistant-ui/thread.js.map +1 -0
- package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
- package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/spec/assistant-ui/toolkit.js +142 -0
- package/dist/spec/assistant-ui/toolkit.js.map +1 -0
- package/dist/spec/assistant-ui/web.d.ts +6 -0
- package/dist/spec/assistant-ui/web.d.ts.map +1 -0
- package/dist/spec/assistant-ui/web.js +48 -0
- package/dist/spec/assistant-ui/web.js.map +1 -0
- package/dist/spec/assistant-ui.d.ts +3 -0
- package/dist/spec/assistant-ui.d.ts.map +1 -0
- package/dist/spec/assistant-ui.js +1 -0
- package/dist/spec/calc.d.ts +8 -0
- package/dist/spec/calc.d.ts.map +1 -0
- package/dist/spec/calc.js +498 -0
- package/dist/spec/calc.js.map +1 -0
- package/dist/spec/catalog.d.ts +52 -0
- package/dist/spec/catalog.d.ts.map +1 -0
- package/dist/spec/catalog.js +104 -0
- package/dist/spec/catalog.js.map +1 -0
- package/dist/spec/expressions.d.ts +45 -0
- package/dist/spec/expressions.d.ts.map +1 -0
- package/dist/spec/expressions.js +199 -0
- package/dist/spec/expressions.js.map +1 -0
- package/dist/spec/patch.d.ts +20 -0
- package/dist/spec/patch.d.ts.map +1 -0
- package/dist/spec/patch.js +77 -0
- package/dist/spec/patch.js.map +1 -0
- package/dist/spec/pending.d.ts +15 -0
- package/dist/spec/pending.d.ts.map +1 -0
- package/dist/spec/pending.js +33 -0
- package/dist/spec/pending.js.map +1 -0
- package/dist/spec/pointer.d.ts +17 -0
- package/dist/spec/pointer.d.ts.map +1 -0
- package/dist/spec/pointer.js +88 -0
- package/dist/spec/pointer.js.map +1 -0
- package/dist/spec/prompt.d.ts +29 -0
- package/dist/spec/prompt.d.ts.map +1 -0
- package/dist/spec/prompt.js +145 -0
- package/dist/spec/prompt.js.map +1 -0
- package/dist/spec/render-spec.d.ts +50 -0
- package/dist/spec/render-spec.d.ts.map +1 -0
- package/dist/spec/render-spec.js +104 -0
- package/dist/spec/render-spec.js.map +1 -0
- package/dist/spec/resolve.d.ts +39 -0
- package/dist/spec/resolve.d.ts.map +1 -0
- package/dist/spec/resolve.js +51 -0
- package/dist/spec/resolve.js.map +1 -0
- package/dist/spec/resolved.d.ts +3 -0
- package/dist/spec/resolved.d.ts.map +1 -0
- package/dist/spec/resolved.js +36 -0
- package/dist/spec/resolved.js.map +1 -0
- package/dist/spec/state.d.ts +34 -0
- package/dist/spec/state.d.ts.map +1 -0
- package/dist/spec/state.js +111 -0
- package/dist/spec/state.js.map +1 -0
- package/dist/spec/stream.d.ts +50 -0
- package/dist/spec/stream.d.ts.map +1 -0
- package/dist/spec/stream.js +168 -0
- package/dist/spec/stream.js.map +1 -0
- package/dist/spec/tools.d.ts +3 -0
- package/dist/spec/tools.d.ts.map +1 -0
- package/dist/spec/tools.js +3 -0
- package/dist/spec/tree.d.ts +21 -0
- package/dist/spec/tree.d.ts.map +1 -0
- package/dist/spec/tree.js +27 -0
- package/dist/spec/tree.js.map +1 -0
- package/dist/spec/types.d.ts +66 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +9 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/validate.d.ts +32 -0
- package/dist/spec/validate.d.ts.map +1 -0
- package/dist/spec/validate.js +264 -0
- package/dist/spec/validate.js.map +1 -0
- package/dist/spec/vue/SpecRenderer.d.ts +43 -0
- package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
- package/dist/spec/vue/SpecRenderer.js +357 -0
- package/dist/spec/vue/SpecRenderer.js.map +1 -0
- package/dist/spec/vue/useSpecStream.d.ts +19 -0
- package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
- package/dist/spec/vue/useSpecStream.js +115 -0
- package/dist/spec/vue/useSpecStream.js.map +1 -0
- package/dist/spec/vue.d.ts +3 -0
- package/dist/spec/vue.d.ts.map +1 -0
- package/dist/spec/vue.js +3 -0
- package/dist/spec.d.ts +12 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +8 -0
- package/dist/theme.d.ts +38 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +370 -0
- package/dist/theme.js.map +1 -0
- package/dist/tools/define.d.ts +43 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +31 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/edits.d.ts +19 -0
- package/dist/tools/edits.d.ts.map +1 -0
- package/dist/tools/edits.js +55 -0
- package/dist/tools/edits.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +33 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/tools.d.ts +88 -0
- package/dist/tools/tools.d.ts.map +1 -0
- package/dist/tools/tools.js +235 -0
- package/dist/tools/tools.js.map +1 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vue/Widget.d.ts +16 -0
- package/dist/vue/Widget.d.ts.map +1 -0
- package/dist/vue/Widget.js +58 -0
- package/dist/vue/Widget.js.map +1 -0
- package/dist/vue/sync.d.ts +19 -0
- package/dist/vue/sync.d.ts.map +1 -0
- package/dist/vue/sync.js +31 -0
- package/dist/vue/sync.js.map +1 -0
- package/dist/vue/themeStore.d.ts +23 -0
- package/dist/vue/themeStore.d.ts.map +1 -0
- package/dist/vue/themeStore.js +115 -0
- package/dist/vue/themeStore.js.map +1 -0
- package/dist/vue/useThemeTokens.d.ts +6 -0
- package/dist/vue/useThemeTokens.d.ts.map +1 -0
- package/dist/vue/useThemeTokens.js +49 -0
- package/dist/vue/useThemeTokens.js.map +1 -0
- package/dist/vue/useWidget.d.ts +10 -0
- package/dist/vue/useWidget.d.ts.map +1 -0
- package/dist/vue/useWidget.js +81 -0
- package/dist/vue/useWidget.js.map +1 -0
- package/dist/vue.d.ts +5 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +4 -0
- package/dist/widget.d.ts +129 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +655 -0
- package/dist/widget.js.map +1 -0
- package/package.json +125 -3
- package/src/assistant-ui/entry.ts +124 -0
- package/src/assistant-ui/extension.ts +19 -0
- package/src/assistant-ui/history.ts +63 -0
- package/src/assistant-ui/modelContext.ts +162 -0
- package/src/assistant-ui/snapshotSaver.ts +116 -0
- package/src/assistant-ui/tool-calls.ts +115 -0
- package/src/assistant-ui/toolkit.tsx +747 -0
- package/src/assistant-ui/useSharedContext.ts +40 -0
- package/src/assistant-ui.ts +14 -0
- package/src/bootstrap.ts +112 -0
- package/src/csp.ts +96 -0
- package/src/index.ts +44 -0
- package/src/json-schema.ts +353 -0
- package/src/preview.ts +120 -0
- package/src/prompts/guidance.ts +369 -0
- package/src/prompts.ts +8 -0
- package/src/protocol.ts +141 -0
- package/src/repair/repair.ts +202 -0
- package/src/repair.ts +11 -0
- package/src/rpc.ts +228 -0
- package/src/runtime/diagnostics.ts +165 -0
- package/src/runtime/generated.ts +2 -0
- package/src/runtime/index.ts +343 -0
- package/src/runtime/morph.ts +352 -0
- package/src/runtime/screenshot.ts +288 -0
- package/src/runtime/size.ts +17 -0
- package/src/runtime/storage.ts +49 -0
- package/src/spec/actions.ts +111 -0
- package/src/spec/assistant-ui/thread.ts +381 -0
- package/src/spec/assistant-ui/toolkit.tsx +295 -0
- package/src/spec/assistant-ui/web.tsx +63 -0
- package/src/spec/assistant-ui.ts +2 -0
- package/src/spec/calc.ts +598 -0
- package/src/spec/catalog.ts +196 -0
- package/src/spec/expressions.ts +301 -0
- package/src/spec/patch.ts +112 -0
- package/src/spec/pending.ts +36 -0
- package/src/spec/pointer.ts +170 -0
- package/src/spec/prompt.ts +243 -0
- package/src/spec/render-spec.ts +144 -0
- package/src/spec/resolve.ts +123 -0
- package/src/spec/resolved.ts +53 -0
- package/src/spec/state.ts +193 -0
- package/src/spec/stream.ts +230 -0
- package/src/spec/tools.ts +17 -0
- package/src/spec/tree.ts +37 -0
- package/src/spec/types.ts +51 -0
- package/src/spec/validate.ts +429 -0
- package/src/spec/vue/SpecRenderer.tsx +544 -0
- package/src/spec/vue/__tests__/harness.ts +50 -0
- package/src/spec/vue/useSpecStream.ts +162 -0
- package/src/spec/vue.ts +13 -0
- package/src/spec.ts +55 -0
- package/src/theme.ts +423 -0
- package/src/tools/define.ts +65 -0
- package/src/tools/edits.ts +73 -0
- package/src/tools/registry.ts +44 -0
- package/src/tools/tools.ts +361 -0
- package/src/tools.ts +32 -0
- package/src/version.ts +4 -0
- package/src/vue/Widget.tsx +74 -0
- package/src/vue/sync.ts +36 -0
- package/src/vue/themeStore.ts +157 -0
- package/src/vue/useThemeTokens.ts +71 -0
- package/src/vue/useWidget.ts +117 -0
- package/src/vue.ts +8 -0
- package/src/widget.ts +1080 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasLaterToolCallInMessage,
|
|
3
|
+
toolCalls,
|
|
4
|
+
type MessageLike,
|
|
5
|
+
} from "../../assistant-ui/tool-calls";
|
|
6
|
+
import type { ActionHandler } from "../actions";
|
|
7
|
+
import type { Catalog } from "../catalog";
|
|
8
|
+
import { formulaValueReads } from "../calc";
|
|
9
|
+
import { conditionGuards, templateStatePaths } from "../expressions";
|
|
10
|
+
import { escapePointerToken, getAtPointer } from "../pointer";
|
|
11
|
+
import type { SpecPromptOptions } from "../prompt";
|
|
12
|
+
import {
|
|
13
|
+
createSpecTools,
|
|
14
|
+
SPEC_TOOL_RESULTS_RULE,
|
|
15
|
+
specGuidanceModule,
|
|
16
|
+
type RenderSpecInput,
|
|
17
|
+
} from "../render-spec";
|
|
18
|
+
import type { SpecStateWrite } from "../state";
|
|
19
|
+
import { parseSpecStream } from "../stream";
|
|
20
|
+
import { childIdsOf, safePointer } from "../tree";
|
|
21
|
+
import type { Spec, SpecElement } from "../types";
|
|
22
|
+
import { formatSpecIssues, type SpecIssue } from "../validate";
|
|
23
|
+
|
|
24
|
+
/** The arguments of a `render_spec` call, which stream in. */
|
|
25
|
+
export type SpecArgs = Partial<RenderSpecInput>;
|
|
26
|
+
|
|
27
|
+
/** The interactions a host records on a tool call, as its `unstable_interactions` prop holds them. */
|
|
28
|
+
export type InteractionLog = {
|
|
29
|
+
readonly entries: readonly (
|
|
30
|
+
| {
|
|
31
|
+
readonly type: "action";
|
|
32
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
33
|
+
}
|
|
34
|
+
| { readonly type: "human-response"; readonly payload: unknown }
|
|
35
|
+
)[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The state writes saved on a `render_spec` call: the newest `setState` interaction recorded for that call. An entry whose `writes` is not a list marks a save that failed, so the call starts from the model's state rather than from an older snapshot.
|
|
40
|
+
*/
|
|
41
|
+
export function savedWrites(
|
|
42
|
+
log: InteractionLog | undefined,
|
|
43
|
+
toolCallId: string,
|
|
44
|
+
): SpecStateWrite[] {
|
|
45
|
+
const entries = log?.entries ?? [];
|
|
46
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
47
|
+
const entry = entries[index]!;
|
|
48
|
+
if (
|
|
49
|
+
entry.type !== "action" ||
|
|
50
|
+
entry.payload["type"] !== "setState" ||
|
|
51
|
+
entry.payload["toolCallId"] !== toolCallId
|
|
52
|
+
)
|
|
53
|
+
continue;
|
|
54
|
+
const writes = entry.payload["writes"];
|
|
55
|
+
if (!Array.isArray(writes)) return [];
|
|
56
|
+
return writes.filter(
|
|
57
|
+
(write): write is SpecStateWrite =>
|
|
58
|
+
Array.isArray(write) &&
|
|
59
|
+
typeof write[0] === "string" &&
|
|
60
|
+
(write.length === 2 ||
|
|
61
|
+
(write.length === 3 && write[2] === "replace") ||
|
|
62
|
+
(write.length === 4 &&
|
|
63
|
+
(write[2] === "add" || write[2] === "replace") &&
|
|
64
|
+
Array.isArray(write[3]) &&
|
|
65
|
+
write[3].every((path: unknown) => typeof path === "string"))),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const WIDGET_TOOLS = new Set([
|
|
72
|
+
"read_me",
|
|
73
|
+
"show_widget",
|
|
74
|
+
"edit_widget",
|
|
75
|
+
"preview_widget",
|
|
76
|
+
"render_spec",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
/** Results of the tool calls before this one, by tool name and newest first, leaving out the widget toolkit's own tools. Tool names are keys every model can see, unlike call ids, which some formats never show, and counting back from this call keeps an index stable when a host prunes older history. */
|
|
80
|
+
export function resolveToolResults(
|
|
81
|
+
messages: readonly MessageLike[],
|
|
82
|
+
toolCallId: string,
|
|
83
|
+
): Record<string, unknown[]> {
|
|
84
|
+
const results: Record<string, unknown[]> = Object.create(null);
|
|
85
|
+
for (const call of toolCalls(messages)) {
|
|
86
|
+
if (call.id === toolCallId) break;
|
|
87
|
+
if (WIDGET_TOOLS.has(call.name)) continue;
|
|
88
|
+
(results[call.name] ??= []).unshift(decodeResult(call.result));
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A string result that holds a JSON object or array is decoded, since the model read that string as the structure. */
|
|
94
|
+
function decodeResult(result: unknown): unknown {
|
|
95
|
+
if (typeof result !== "string") return result;
|
|
96
|
+
try {
|
|
97
|
+
const value: unknown = JSON.parse(result);
|
|
98
|
+
return typeof value === "object" && value !== null ? value : result;
|
|
99
|
+
} catch {
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A `spec` argument as the renderers read it, with `elements` an object even while the argument is still streaming in. */
|
|
105
|
+
export const specFromArgs = (spec: object): Spec => {
|
|
106
|
+
const value = { state: {}, ...spec } as Spec;
|
|
107
|
+
return typeof value.elements === "object" && value.elements !== null
|
|
108
|
+
? value
|
|
109
|
+
: { ...value, elements: {} };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The spec a `render_spec` call's patches apply onto: the result of the
|
|
114
|
+
* earlier `render_spec` calls with the same title in the thread.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveSpecBase(
|
|
117
|
+
messages: readonly MessageLike[],
|
|
118
|
+
toolCallId: string,
|
|
119
|
+
title: string,
|
|
120
|
+
): Spec | undefined {
|
|
121
|
+
let spec: Spec | undefined;
|
|
122
|
+
for (const call of toolCalls(messages)) {
|
|
123
|
+
if (call.id === toolCallId) return spec;
|
|
124
|
+
if (call.name !== "render_spec" || call.args["title"] !== title) continue;
|
|
125
|
+
if (call.args["spec"] && typeof call.args["spec"] === "object") {
|
|
126
|
+
spec = specFromArgs(call.args["spec"]);
|
|
127
|
+
} else if (typeof call.args["patches"] === "string") {
|
|
128
|
+
spec = parseSpecStream(
|
|
129
|
+
call.args["patches"],
|
|
130
|
+
spec ? { initial: spec } : {},
|
|
131
|
+
).spec;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return spec;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Whether a later `render_spec` call in the same message has this call's title. That call shows the spec this one's patches lead to, so this one shows nothing rather than a second copy. */
|
|
138
|
+
export function supersededInMessage(
|
|
139
|
+
messages: readonly MessageLike[],
|
|
140
|
+
toolCallId: string,
|
|
141
|
+
title: string,
|
|
142
|
+
): boolean {
|
|
143
|
+
return hasLaterToolCallInMessage(
|
|
144
|
+
messages,
|
|
145
|
+
toolCallId,
|
|
146
|
+
title,
|
|
147
|
+
(call) => call.name === "render_spec",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function priorSpecCalls(
|
|
152
|
+
messages: readonly MessageLike[],
|
|
153
|
+
toolCallId: string,
|
|
154
|
+
title: string,
|
|
155
|
+
): number {
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (const call of toolCalls(messages)) {
|
|
158
|
+
if (call.id === toolCallId) break;
|
|
159
|
+
if (call.name === "render_spec" && call.args["title"] === title) count++;
|
|
160
|
+
}
|
|
161
|
+
return count;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type SpecHistory = {
|
|
165
|
+
base(title: string): { spec: Spec | undefined; version: number };
|
|
166
|
+
toolResults: Record<string, unknown[]> | undefined;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** What a mounted call registers for `execute`: the spec each title held before it in its own thread, how many calls built it, and the earlier results it mounts under `/tools`. */
|
|
170
|
+
export const callHistory = (
|
|
171
|
+
messages: readonly MessageLike[],
|
|
172
|
+
toolCallId: string,
|
|
173
|
+
toolResults: Record<string, unknown[]> | undefined,
|
|
174
|
+
): SpecHistory => ({
|
|
175
|
+
base: (title) => ({
|
|
176
|
+
spec: resolveSpecBase(messages, toolCallId, title),
|
|
177
|
+
version: priorSpecCalls(messages, toolCallId, title),
|
|
178
|
+
}),
|
|
179
|
+
toolResults,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const readsTools = (pointer: string) =>
|
|
183
|
+
pointer === "/tools" || pointer.startsWith("/tools/");
|
|
184
|
+
|
|
185
|
+
/** Collects the state paths a value shows, leaving out reads in a branch that only renders while that path has a value: the `$then` of a `$cond` that tests it, and the `?` branch of a `$calc` condition on it. */
|
|
186
|
+
function collectShown(
|
|
187
|
+
value: unknown,
|
|
188
|
+
guarded: ReadonlySet<string>,
|
|
189
|
+
shown: string[],
|
|
190
|
+
): void {
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
for (const item of value) collectShown(item, guarded, shown);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (typeof value !== "object" || value === null) return;
|
|
196
|
+
const record = value as Record<string, unknown>;
|
|
197
|
+
if ("$cond" in record) {
|
|
198
|
+
const tested = conditionGuards(record["$cond"]).map(safePointer);
|
|
199
|
+
collectShown(record["$then"], new Set([...guarded, ...tested]), shown);
|
|
200
|
+
collectShown(record["$else"], guarded, shown);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const [key, child] of Object.entries(record)) {
|
|
204
|
+
if (typeof child !== "string") {
|
|
205
|
+
collectShown(child, guarded, shown);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const paths =
|
|
209
|
+
key === "$state" || key === "$bindState"
|
|
210
|
+
? [child]
|
|
211
|
+
: key === "$template"
|
|
212
|
+
? templateStatePaths(child)
|
|
213
|
+
: key === "$calc"
|
|
214
|
+
? formulaValueReads(child)
|
|
215
|
+
: [];
|
|
216
|
+
for (const path of paths) {
|
|
217
|
+
const pointer = safePointer(path);
|
|
218
|
+
if (!guarded.has(pointer)) shown.push(pointer);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Each place a spec can render an element, with the paths a `visible` condition on it or an ancestor guards there: reachable from the root through children and slots, leaving out one hidden with `visible: false` along with what it holds, and the repeated children of a `/tools` path the results hold no array at. An element under parents that guard different paths appears once per set of guards. */
|
|
224
|
+
function renderedElements(
|
|
225
|
+
spec: Spec,
|
|
226
|
+
state: unknown,
|
|
227
|
+
): [string, SpecElement, ReadonlySet<string>][] {
|
|
228
|
+
const elements = spec.elements ?? {};
|
|
229
|
+
const rendered: [string, SpecElement, ReadonlySet<string>][] = [];
|
|
230
|
+
const seen = new Set<string>();
|
|
231
|
+
const pending: [string, ReadonlySet<string>][] =
|
|
232
|
+
typeof spec.root === "string" ? [[spec.root, new Set()]] : [];
|
|
233
|
+
while (pending.length > 0) {
|
|
234
|
+
const [id, inherited] = pending.pop()!;
|
|
235
|
+
if (!Object.hasOwn(elements, id)) continue;
|
|
236
|
+
const element = elements[id];
|
|
237
|
+
if (typeof element !== "object" || element === null) continue;
|
|
238
|
+
if (element.visible === false) continue;
|
|
239
|
+
const guarded = new Set([
|
|
240
|
+
...inherited,
|
|
241
|
+
...conditionGuards(element.visible).map(safePointer),
|
|
242
|
+
]);
|
|
243
|
+
const visit = JSON.stringify([id, ...[...guarded].sort()]);
|
|
244
|
+
if (seen.has(visit)) continue;
|
|
245
|
+
seen.add(visit);
|
|
246
|
+
rendered.push([id, element, guarded]);
|
|
247
|
+
const repeat =
|
|
248
|
+
typeof element.repeat?.path === "string"
|
|
249
|
+
? safePointer(element.repeat.path)
|
|
250
|
+
: undefined;
|
|
251
|
+
const repeatsNothing =
|
|
252
|
+
repeat !== undefined &&
|
|
253
|
+
readsTools(repeat) &&
|
|
254
|
+
!Array.isArray(getAtPointer(state, repeat));
|
|
255
|
+
for (const slot of ["default", ...Object.keys(element.slots ?? {})]) {
|
|
256
|
+
if (slot === "default" && repeatsNothing) continue;
|
|
257
|
+
for (const child of childIdsOf(element, slot))
|
|
258
|
+
if (typeof child === "string") pending.push([child, guarded]);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return rendered;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Each place a spec shows state under `/tools` that the mounted results do not have, as a warning, since the model may have read the value from output the thread does not store or may mean a field that is absent. A read that only renders while its path has a value, under a `visible` condition or `$cond` test on it, is left out. */
|
|
265
|
+
function missingToolResults(
|
|
266
|
+
spec: Spec,
|
|
267
|
+
toolResults: Record<string, unknown[]>,
|
|
268
|
+
): SpecIssue[] {
|
|
269
|
+
const issues: SpecIssue[] = [];
|
|
270
|
+
const state = { tools: toolResults };
|
|
271
|
+
const reported = new Set<string>();
|
|
272
|
+
for (const [id, element, guarded] of renderedElements(spec, state)) {
|
|
273
|
+
const shown: string[] = [];
|
|
274
|
+
collectShown([element.props, element.on, element.watch], guarded, shown);
|
|
275
|
+
if (typeof element.repeat?.path === "string") {
|
|
276
|
+
const pointer = safePointer(element.repeat.path);
|
|
277
|
+
if (!guarded.has(pointer)) shown.push(pointer);
|
|
278
|
+
}
|
|
279
|
+
for (const pointer of new Set(shown)) {
|
|
280
|
+
if (!readsTools(pointer)) continue;
|
|
281
|
+
if (getAtPointer(state, pointer) !== undefined) continue;
|
|
282
|
+
const report = JSON.stringify([id, pointer]);
|
|
283
|
+
if (reported.has(report)) continue;
|
|
284
|
+
reported.add(report);
|
|
285
|
+
issues.push({
|
|
286
|
+
code: "missing-tool-result",
|
|
287
|
+
severity: "warning",
|
|
288
|
+
elementId: id,
|
|
289
|
+
path: `/elements/${escapePointerToken(id)}`,
|
|
290
|
+
message: `"${id}" shows \`${pointer}\`, which the earlier tool results do not have, so it shows nothing there. If you read that value from the tool's output, write it into the spec instead.`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return issues;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The app's rules and where earlier tool results are, which `render_spec`'s description and the `spec` module both carry. */
|
|
298
|
+
const toolkitRules = (
|
|
299
|
+
specPrompt: Omit<SpecPromptOptions, "mode"> | undefined,
|
|
300
|
+
) => [...(specPrompt?.customRules ?? []), SPEC_TOOL_RESULTS_RULE];
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The `render_spec` tool a toolkit shares across its calls. Each mounted call
|
|
304
|
+
* registers its {@link callHistory} in `histories` under its tool call id, so
|
|
305
|
+
* `execute` patches the base that call's own thread held, even after a reload
|
|
306
|
+
* or in another thread.
|
|
307
|
+
*/
|
|
308
|
+
export function createSpecCalls(
|
|
309
|
+
catalog: Catalog,
|
|
310
|
+
specPrompt: Omit<SpecPromptOptions, "mode"> | undefined,
|
|
311
|
+
) {
|
|
312
|
+
const specs = new Map<string, { spec: Spec; version: number }>();
|
|
313
|
+
const tools = createSpecTools(catalog, {
|
|
314
|
+
specs,
|
|
315
|
+
customRules: toolkitRules(specPrompt),
|
|
316
|
+
});
|
|
317
|
+
const histories = new Map<string, SpecHistory>();
|
|
318
|
+
const execute = async (
|
|
319
|
+
input: RenderSpecInput,
|
|
320
|
+
call: { toolCallId: string },
|
|
321
|
+
) => {
|
|
322
|
+
const history = histories.get(call.toolCallId);
|
|
323
|
+
if (history) {
|
|
324
|
+
const { spec, version } = history.base(input.title);
|
|
325
|
+
if (spec) specs.set(input.title, { spec, version });
|
|
326
|
+
else specs.delete(input.title);
|
|
327
|
+
}
|
|
328
|
+
const running = tools.render_spec.execute(input);
|
|
329
|
+
// `execute` stores this call's spec before it returns, so the spec is read before an overlapping call with the same title stores its own.
|
|
330
|
+
const spec = specs.get(input.title)?.spec;
|
|
331
|
+
const result = await running;
|
|
332
|
+
// The results are mounted only while the spec writes no `tools` of its own.
|
|
333
|
+
if (!history?.toolResults || !spec || spec.state?.["tools"] !== undefined)
|
|
334
|
+
return result;
|
|
335
|
+
const missing = missingToolResults(spec, history.toolResults);
|
|
336
|
+
if (missing.length === 0) return result;
|
|
337
|
+
const issues = [...result.issues, ...missing];
|
|
338
|
+
return { ...result, issues, feedback: formatSpecIssues(issues) };
|
|
339
|
+
};
|
|
340
|
+
return { tools, histories, execute };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The `spec` module of `read_me`: the catalog guidance with the built-in actions every assistant-ui toolkit handles, the app's rules, and where earlier tool results are. */
|
|
344
|
+
export const specModules = (
|
|
345
|
+
catalog: Catalog,
|
|
346
|
+
specPrompt: Omit<SpecPromptOptions, "mode"> | undefined,
|
|
347
|
+
host: { clipboard: boolean },
|
|
348
|
+
) => [
|
|
349
|
+
specGuidanceModule(catalog, {
|
|
350
|
+
...specPrompt,
|
|
351
|
+
hostApi: {
|
|
352
|
+
prompt: true,
|
|
353
|
+
modelContext: true,
|
|
354
|
+
clipboard: host.clipboard,
|
|
355
|
+
...specPrompt?.hostApi,
|
|
356
|
+
},
|
|
357
|
+
customRules: toolkitRules(specPrompt),
|
|
358
|
+
}),
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
/** Writes to the browser's clipboard, rejecting where the Clipboard API is missing. */
|
|
362
|
+
export const writeClipboard = (text: string): Promise<void> =>
|
|
363
|
+
typeof navigator !== "undefined" && navigator.clipboard
|
|
364
|
+
? navigator.clipboard.writeText(text)
|
|
365
|
+
: Promise.reject(new Error("Clipboard API is unavailable"));
|
|
366
|
+
|
|
367
|
+
/** The built-in `copyText` handler. A copy that does not happen fails, so the rest of the list, such as a confirmation, does not run. */
|
|
368
|
+
export const copyTextHandler =
|
|
369
|
+
(write: (text: string) => Promise<void>): ActionHandler =>
|
|
370
|
+
(params, source) => {
|
|
371
|
+
if (source.kind === "watch") {
|
|
372
|
+
throw new Error(
|
|
373
|
+
"copyText runs only from an event a component emits, never from `watch`",
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const text = params["text"];
|
|
377
|
+
if (typeof text !== "string") {
|
|
378
|
+
throw new Error("copyText needs a string `text`");
|
|
379
|
+
}
|
|
380
|
+
return write(text);
|
|
381
|
+
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onActivated,
|
|
6
|
+
onDeactivated,
|
|
7
|
+
onMounted,
|
|
8
|
+
onScopeDispose,
|
|
9
|
+
toValue,
|
|
10
|
+
watch,
|
|
11
|
+
type Component,
|
|
12
|
+
type MaybeRefOrGetter,
|
|
13
|
+
} from "vue";
|
|
14
|
+
import { toolkitEntry } from "../../assistant-ui/entry";
|
|
15
|
+
import type { WidgetToolkitExtension } from "../../assistant-ui/extension";
|
|
16
|
+
import {
|
|
17
|
+
savedContext,
|
|
18
|
+
toSharedContext,
|
|
19
|
+
type ModelContextSource,
|
|
20
|
+
type SharedContext,
|
|
21
|
+
} from "../../assistant-ui/modelContext";
|
|
22
|
+
import { createSnapshotSaver } from "../../assistant-ui/snapshotSaver";
|
|
23
|
+
import type { MessageLike } from "../../assistant-ui/tool-calls";
|
|
24
|
+
import { useSharedContext } from "../../assistant-ui/useSharedContext";
|
|
25
|
+
import type { ActionHandler } from "../actions";
|
|
26
|
+
import type { Catalog } from "../catalog";
|
|
27
|
+
import type { SpecPromptOptions } from "../prompt";
|
|
28
|
+
import type { SpecComponents } from "../vue/SpecRenderer";
|
|
29
|
+
import { useSpecStream } from "../vue/useSpecStream";
|
|
30
|
+
import {
|
|
31
|
+
createStateStore,
|
|
32
|
+
type SpecStateStore,
|
|
33
|
+
type SpecStateWrite,
|
|
34
|
+
} from "../state";
|
|
35
|
+
import {
|
|
36
|
+
callHistory,
|
|
37
|
+
copyTextHandler,
|
|
38
|
+
createSpecCalls,
|
|
39
|
+
resolveSpecBase,
|
|
40
|
+
resolveToolResults,
|
|
41
|
+
savedWrites,
|
|
42
|
+
specFromArgs,
|
|
43
|
+
specModules,
|
|
44
|
+
supersededInMessage,
|
|
45
|
+
type InteractionLog,
|
|
46
|
+
type SpecArgs,
|
|
47
|
+
} from "./thread";
|
|
48
|
+
export type SpecToolkitOptions = {
|
|
49
|
+
components: SpecComponents;
|
|
50
|
+
handlers?: Record<string, ActionHandler>;
|
|
51
|
+
onAction?: (
|
|
52
|
+
name: string,
|
|
53
|
+
params: Record<string, unknown>,
|
|
54
|
+
context: { elementId?: string; trigger?: string; state: SpecStateStore },
|
|
55
|
+
) => unknown;
|
|
56
|
+
placeholder?: Component;
|
|
57
|
+
specPrompt?: Omit<SpecPromptOptions, "mode">;
|
|
58
|
+
toolResults?: boolean;
|
|
59
|
+
};
|
|
60
|
+
export type SpecToolkitPlatform = {
|
|
61
|
+
useMessages(): MaybeRefOrGetter<readonly MessageLike[]>;
|
|
62
|
+
useArgsComplete(): MaybeRefOrGetter<boolean>;
|
|
63
|
+
SpecRenderer: Component;
|
|
64
|
+
/** The frame renders its content through its default Vue slot. */
|
|
65
|
+
Frame: Component;
|
|
66
|
+
useSendMessage(): (text: string) => void;
|
|
67
|
+
useModelContext(source: ModelContextSource): void;
|
|
68
|
+
copyText?: (text: string) => Promise<void>;
|
|
69
|
+
};
|
|
70
|
+
type RenderSpecProps = {
|
|
71
|
+
args: SpecArgs;
|
|
72
|
+
toolCallId: string;
|
|
73
|
+
/** @deprecated Experimental since 2026-09-23. Not scheduled for removal; the API may change in any release. */
|
|
74
|
+
unstable_interactions?: InteractionLog | undefined;
|
|
75
|
+
/** @deprecated Experimental since 2026-09-23. Not scheduled for removal; the API may change in any release. */
|
|
76
|
+
unstable_recordInteraction?:
|
|
77
|
+
| ((input: { type: "action"; payload: unknown }) => Promise<void>)
|
|
78
|
+
| undefined;
|
|
79
|
+
};
|
|
80
|
+
export const specToolkitFor =
|
|
81
|
+
({
|
|
82
|
+
useMessages,
|
|
83
|
+
useArgsComplete,
|
|
84
|
+
SpecRenderer,
|
|
85
|
+
Frame,
|
|
86
|
+
useSendMessage,
|
|
87
|
+
useModelContext,
|
|
88
|
+
copyText,
|
|
89
|
+
}: SpecToolkitPlatform) =>
|
|
90
|
+
(catalog: Catalog, options: SpecToolkitOptions): WidgetToolkitExtension => {
|
|
91
|
+
const { tools, histories, execute } = createSpecCalls(
|
|
92
|
+
catalog,
|
|
93
|
+
options.specPrompt,
|
|
94
|
+
);
|
|
95
|
+
const renderSpecUI = (mountsToolResults: boolean) =>
|
|
96
|
+
defineComponent(
|
|
97
|
+
(props: RenderSpecProps) => {
|
|
98
|
+
const complete = useArgsComplete();
|
|
99
|
+
const messages = useMessages();
|
|
100
|
+
const streaming = computed(() => !toValue(complete));
|
|
101
|
+
const title = computed(() =>
|
|
102
|
+
typeof props.args.title === "string" ? props.args.title : "",
|
|
103
|
+
);
|
|
104
|
+
const earlier = toValue(messages);
|
|
105
|
+
const superseded = computed(() =>
|
|
106
|
+
supersededInMessage(
|
|
107
|
+
toValue(messages),
|
|
108
|
+
props.toolCallId,
|
|
109
|
+
title.value,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
const base = computed(() =>
|
|
113
|
+
resolveSpecBase(earlier, props.toolCallId, title.value),
|
|
114
|
+
);
|
|
115
|
+
const toolResults = mountsToolResults
|
|
116
|
+
? resolveToolResults(earlier, props.toolCallId)
|
|
117
|
+
: undefined;
|
|
118
|
+
const history = callHistory(earlier, props.toolCallId, toolResults);
|
|
119
|
+
const streamed = useSpecStream(() => ({
|
|
120
|
+
source:
|
|
121
|
+
typeof props.args.patches === "string" ? props.args.patches : "",
|
|
122
|
+
complete: !streaming.value,
|
|
123
|
+
...(base.value ? { initial: base.value } : {}),
|
|
124
|
+
}));
|
|
125
|
+
watch(base, () => streamed.reset(), { flush: "sync" });
|
|
126
|
+
const specInput = computed(() => props.args.spec);
|
|
127
|
+
const spec = computed(() => {
|
|
128
|
+
const input = specInput.value;
|
|
129
|
+
return input &&
|
|
130
|
+
typeof input === "object" &&
|
|
131
|
+
typeof input.root === "string"
|
|
132
|
+
? specFromArgs(input)
|
|
133
|
+
: streamed.spec.value;
|
|
134
|
+
});
|
|
135
|
+
const emptyState: Record<string, unknown> = {};
|
|
136
|
+
const modelState = computed(() => spec.value.state ?? emptyState);
|
|
137
|
+
const stateWithTools = computed(() =>
|
|
138
|
+
toolResults && modelState.value["tools"] === undefined
|
|
139
|
+
? { ...modelState.value, tools: toolResults }
|
|
140
|
+
: modelState.value,
|
|
141
|
+
);
|
|
142
|
+
const renderSpec = computed(() =>
|
|
143
|
+
stateWithTools.value === spec.value.state
|
|
144
|
+
? spec.value
|
|
145
|
+
: { ...spec.value, state: stateWithTools.value },
|
|
146
|
+
);
|
|
147
|
+
const store = createStateStore(stateWithTools.value, {
|
|
148
|
+
writes: savedWrites(props.unstable_interactions, props.toolCallId),
|
|
149
|
+
});
|
|
150
|
+
watch(stateWithTools, (value) => store.seed(value), {
|
|
151
|
+
flush: "sync",
|
|
152
|
+
});
|
|
153
|
+
const initialContext = savedContext(
|
|
154
|
+
props.unstable_interactions,
|
|
155
|
+
props.toolCallId,
|
|
156
|
+
"setState",
|
|
157
|
+
);
|
|
158
|
+
let context = initialContext;
|
|
159
|
+
const snapshot = () => ({
|
|
160
|
+
writes: store.writes(),
|
|
161
|
+
...(context ? { context } : {}),
|
|
162
|
+
});
|
|
163
|
+
const saver = createSnapshotSaver<{
|
|
164
|
+
writes: readonly SpecStateWrite[];
|
|
165
|
+
context?: SharedContext;
|
|
166
|
+
}>(snapshot(), (saved) =>
|
|
167
|
+
props.unstable_recordInteraction?.({
|
|
168
|
+
type: "action",
|
|
169
|
+
payload: {
|
|
170
|
+
type: "setState",
|
|
171
|
+
toolCallId: props.toolCallId,
|
|
172
|
+
writes: saved?.writes ?? null,
|
|
173
|
+
...(saved?.context ? { context: saved.context } : {}),
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
const sendMessage = useSendMessage();
|
|
178
|
+
const share = useSharedContext(() => ({
|
|
179
|
+
title: title.value,
|
|
180
|
+
toolCallId: props.toolCallId,
|
|
181
|
+
initial: options.handlers?.["updateModelContext"]
|
|
182
|
+
? undefined
|
|
183
|
+
: initialContext,
|
|
184
|
+
useModelContext,
|
|
185
|
+
onShare(next) {
|
|
186
|
+
context = next;
|
|
187
|
+
saver.schedule(snapshot);
|
|
188
|
+
},
|
|
189
|
+
muted: superseded.value,
|
|
190
|
+
}));
|
|
191
|
+
const handlers = {
|
|
192
|
+
sendMessage(
|
|
193
|
+
params: Record<string, unknown>,
|
|
194
|
+
source: { kind?: string },
|
|
195
|
+
) {
|
|
196
|
+
const text = params["text"];
|
|
197
|
+
if (source.kind === "watch")
|
|
198
|
+
throw new Error(
|
|
199
|
+
"sendMessage runs only from an event a component emits, never from `watch`",
|
|
200
|
+
);
|
|
201
|
+
if (typeof text !== "string" || !text.trim())
|
|
202
|
+
throw new Error("sendMessage needs a non-empty string `text`");
|
|
203
|
+
sendMessage(text);
|
|
204
|
+
},
|
|
205
|
+
updateModelContext: (params: Record<string, unknown>) =>
|
|
206
|
+
share(toSharedContext(params["text"], params["data"])),
|
|
207
|
+
...(copyText ? { copyText: copyTextHandler(copyText) } : {}),
|
|
208
|
+
...options.handlers,
|
|
209
|
+
};
|
|
210
|
+
let enabled = true;
|
|
211
|
+
let unsubscribe: (() => void) | undefined;
|
|
212
|
+
let historyId = props.toolCallId;
|
|
213
|
+
const stop = () => {
|
|
214
|
+
unsubscribe?.();
|
|
215
|
+
unsubscribe = undefined;
|
|
216
|
+
if (histories.get(historyId) === history)
|
|
217
|
+
histories.delete(historyId);
|
|
218
|
+
saver.flush();
|
|
219
|
+
};
|
|
220
|
+
const start = () => {
|
|
221
|
+
if (unsubscribe || !enabled) return;
|
|
222
|
+
historyId = props.toolCallId;
|
|
223
|
+
histories.set(historyId, history);
|
|
224
|
+
unsubscribe = store.subscribe(() => saver.schedule(snapshot));
|
|
225
|
+
};
|
|
226
|
+
onMounted(start);
|
|
227
|
+
watch(
|
|
228
|
+
() => props.toolCallId,
|
|
229
|
+
() => {
|
|
230
|
+
stop();
|
|
231
|
+
start();
|
|
232
|
+
},
|
|
233
|
+
{ flush: "sync" },
|
|
234
|
+
);
|
|
235
|
+
onDeactivated(() => {
|
|
236
|
+
enabled = false;
|
|
237
|
+
stop();
|
|
238
|
+
});
|
|
239
|
+
onActivated(() => {
|
|
240
|
+
enabled = true;
|
|
241
|
+
start();
|
|
242
|
+
});
|
|
243
|
+
onScopeDispose(stop);
|
|
244
|
+
return () =>
|
|
245
|
+
superseded.value
|
|
246
|
+
? null
|
|
247
|
+
: h(
|
|
248
|
+
Frame,
|
|
249
|
+
{ title: title.value, streaming: streaming.value },
|
|
250
|
+
{
|
|
251
|
+
default: () =>
|
|
252
|
+
h(SpecRenderer, {
|
|
253
|
+
spec: renderSpec.value,
|
|
254
|
+
state: store,
|
|
255
|
+
catalog,
|
|
256
|
+
components: options.components,
|
|
257
|
+
streaming: streaming.value,
|
|
258
|
+
handlers,
|
|
259
|
+
...(options.onAction
|
|
260
|
+
? { onAction: options.onAction }
|
|
261
|
+
: {}),
|
|
262
|
+
...(options.placeholder
|
|
263
|
+
? { placeholder: options.placeholder }
|
|
264
|
+
: {}),
|
|
265
|
+
}),
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "RenderSpecUI",
|
|
271
|
+
props: [
|
|
272
|
+
"args",
|
|
273
|
+
"toolCallId",
|
|
274
|
+
"unstable_interactions",
|
|
275
|
+
"unstable_recordInteraction",
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
return {
|
|
280
|
+
tools,
|
|
281
|
+
modules: specModules(catalog, options.specPrompt, {
|
|
282
|
+
clipboard: copyText !== undefined,
|
|
283
|
+
}),
|
|
284
|
+
entries: (context) => ({
|
|
285
|
+
render_spec: toolkitEntry(
|
|
286
|
+
tools.render_spec,
|
|
287
|
+
renderSpecUI(
|
|
288
|
+
context.execution === "frontend" || options.toolResults === true,
|
|
289
|
+
),
|
|
290
|
+
context,
|
|
291
|
+
execute,
|
|
292
|
+
),
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
};
|