@artooi/ag-ui-web-component 0.11.0 → 0.13.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/CHANGELOG.md +86 -1
- package/README.md +98 -5
- package/dist/ag-ui-web-component.bundle.js +126 -50
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +29 -4
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +11 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +6 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +50 -0
- package/dist/core/run_index.d.ts.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +453 -63
- package/dist/index.js.map +3 -3
- package/dist/tools/page_state.d.ts +40 -0
- package/dist/tools/page_state.d.ts.map +1 -0
- package/dist/ui/checkpoint_menu.d.ts +32 -0
- package/dist/ui/checkpoint_menu.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +10 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +10 -0
- package/src/core/ag_ui_chat.ts +153 -6
- package/src/core/agui_client.ts +28 -0
- package/src/core/create_http_agent.ts +7 -0
- package/src/core/run_index.ts +91 -0
- package/src/index.ts +14 -1
- package/src/tools/page_state.ts +72 -0
- package/src/ui/checkpoint_menu.ts +153 -0
- package/src/ui/styles.ts +76 -0
- package/src/ui/ui_strings.ts +15 -0
- package/src/version.ts +1 -1
- package/dist/tools/state_hook.d.ts +0 -23
- package/dist/tools/state_hook.d.ts.map +0 -1
- package/src/tools/state_hook.ts +0 -53
|
@@ -18,6 +18,12 @@ export interface HttpAgentOptions {
|
|
|
18
18
|
threadId?: string;
|
|
19
19
|
/** Rehydrated history to seed the agent with (durable conversation). */
|
|
20
20
|
initialMessages?: readonly Message[];
|
|
21
|
+
/**
|
|
22
|
+
* AG-UI shared state to seed the agent with. `@ag-ui/client` sends it as
|
|
23
|
+
* `RunAgentInput.state` on every run and replaces it in place when the
|
|
24
|
+
* server streams `STATE_SNAPSHOT` / `STATE_DELTA`.
|
|
25
|
+
*/
|
|
26
|
+
initialState?: Readonly<Record<string, unknown>>;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -31,6 +37,7 @@ export function createHttpAgent(options: HttpAgentOptions): AbstractAgent {
|
|
|
31
37
|
return new HttpAgent({
|
|
32
38
|
url: options.endpoint,
|
|
33
39
|
headers: options.headers ?? {},
|
|
40
|
+
initialState: { ...(options.initialState ?? {}) },
|
|
34
41
|
// HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
|
|
35
42
|
// which would rebind the global `fetch` to the agent instance and trigger
|
|
36
43
|
// "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** One row of the server run index (django-ag-ui's `RunsView` wire shape). */
|
|
2
|
+
export interface RunRow {
|
|
3
|
+
readonly run_id: string;
|
|
4
|
+
readonly thread_id: string | null;
|
|
5
|
+
readonly parent_run_id: string | null;
|
|
6
|
+
readonly started_at: string | null;
|
|
7
|
+
/** Whether the run has a snapshot to seed from — see {@link RunIndex}. */
|
|
8
|
+
readonly continuable: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Live header source, read per request so rotated tokens / CSRF reach the server. */
|
|
12
|
+
type HeadersProvider = () => Record<string, string>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reads the server's run index and derives the resume / fork URLs beside it.
|
|
16
|
+
*
|
|
17
|
+
* Backed by django-ag-ui's owner-scoped `RunsView` — the URL passed to
|
|
18
|
+
* `<ag-ui-chat>` as `data-runs-url`:
|
|
19
|
+
*
|
|
20
|
+
* - `GET <url>` → the user's runs, newest first.
|
|
21
|
+
*
|
|
22
|
+
* **Only `continuable` rows can be resumed.** The server reports whether a run
|
|
23
|
+
* has a saved snapshot to seed from; a run that never reached a provider-valid
|
|
24
|
+
* boundary has none, so resuming it would start from nothing. Callers should
|
|
25
|
+
* offer the action only for those rows and treat the rest as informational — a
|
|
26
|
+
* crashed run worth showing, not worth continuing. {@link continuable} filters
|
|
27
|
+
* for exactly that.
|
|
28
|
+
*
|
|
29
|
+
* **Resume and fork are siblings of the index**, not separate configuration.
|
|
30
|
+
* django-ag-ui mounts all three under one prefix whenever a step store is
|
|
31
|
+
* configured (`runs/`, `resume/<id>/`, `fork/<id>/`), so one URL locates them
|
|
32
|
+
* all and there is no way to configure a half-working set.
|
|
33
|
+
*/
|
|
34
|
+
export class RunIndex {
|
|
35
|
+
readonly #url: string;
|
|
36
|
+
readonly #headers: HeadersProvider;
|
|
37
|
+
|
|
38
|
+
constructor(url: string, headers: HeadersProvider = () => ({})) {
|
|
39
|
+
this.#url = url.endsWith("/") ? url : `${url}/`;
|
|
40
|
+
this.#headers = headers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The user's runs, or `[]` when the endpoint is unreachable or answers with
|
|
45
|
+
* an error. A history affordance that cannot load is empty, never broken:
|
|
46
|
+
* the caller renders its empty state rather than surfacing a transport fault
|
|
47
|
+
* the user can do nothing about.
|
|
48
|
+
*/
|
|
49
|
+
async list(): Promise<readonly RunRow[]> {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(this.#url, {
|
|
52
|
+
method: "GET",
|
|
53
|
+
headers: { Accept: "application/json", ...this.#headers() },
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const body = (await response.json()) as { runs?: readonly RunRow[] };
|
|
59
|
+
return body.runs ?? [];
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The rows a client may actually continue. */
|
|
66
|
+
async continuable(): Promise<readonly RunRow[]> {
|
|
67
|
+
return (await this.list()).filter((run) => run.continuable);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The endpoint that continues `runId` as a new run. */
|
|
71
|
+
resumeUrl(runId: string): string {
|
|
72
|
+
return this.#sibling("resume", runId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The endpoint that branches `runId` into a new run, leaving the source untouched. */
|
|
76
|
+
forkUrl(runId: string): string {
|
|
77
|
+
return this.#sibling("fork", runId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `<mount>/<verb>/<runId>/`, derived from the index URL's own prefix.
|
|
82
|
+
*
|
|
83
|
+
* Built by string surgery on the trailing `runs/` rather than with `new URL`,
|
|
84
|
+
* because the configured value may be root-relative (`/agent/runs/`) — the
|
|
85
|
+
* common case in a Django template — and `new URL` needs an absolute base.
|
|
86
|
+
*/
|
|
87
|
+
#sibling(verb: string, runId: string): string {
|
|
88
|
+
const prefix = this.#url.slice(0, -"runs/".length);
|
|
89
|
+
return `${prefix}${verb}/${encodeURIComponent(runId)}/`;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
ELEMENT_TAG,
|
|
5
5
|
MAX_TOOL_ROUNDS,
|
|
6
6
|
MESSAGE_ROLE,
|
|
7
|
+
STATE_EVENT,
|
|
7
8
|
SUBMIT_EVENT,
|
|
8
9
|
TOGGLE_EVENT,
|
|
9
10
|
TOOL_CALL_STATUS,
|
|
@@ -16,6 +17,7 @@ export {
|
|
|
16
17
|
export {
|
|
17
18
|
AgUiChat,
|
|
18
19
|
type MessageRole,
|
|
20
|
+
type StateDetail,
|
|
19
21
|
type SubmitDetail,
|
|
20
22
|
type ToggleDetail,
|
|
21
23
|
} from "./core/ag_ui_chat.js";
|
|
@@ -45,6 +47,7 @@ export {
|
|
|
45
47
|
} from "./core/create_http_agent.js";
|
|
46
48
|
export { defineAgUiChat } from "./core/define_ag_ui_chat.js";
|
|
47
49
|
export { RemoteConversationStore } from "./core/remote_conversation_store.js";
|
|
50
|
+
export { RunIndex, type RunRow } from "./core/run_index.js";
|
|
48
51
|
export {
|
|
49
52
|
type TranscribeHandler,
|
|
50
53
|
type TranscribeOptions,
|
|
@@ -92,6 +95,16 @@ export {
|
|
|
92
95
|
type ResolvePageTarget,
|
|
93
96
|
} from "./tools/page_action_tools.js";
|
|
94
97
|
export { createPageMapContext, type PageMap } from "./tools/page_map.js";
|
|
98
|
+
/**
|
|
99
|
+
* @deprecated Renamed to `createPageStateTools` / `PageState`. The old names
|
|
100
|
+
* read as AG-UI shared-state sync, which this component does not implement.
|
|
101
|
+
*/
|
|
102
|
+
export {
|
|
103
|
+
createPageStateTools,
|
|
104
|
+
createStateHookTools,
|
|
105
|
+
type PageState,
|
|
106
|
+
type StateHook,
|
|
107
|
+
} from "./tools/page_state.js";
|
|
95
108
|
export { parseToolCatalog, type ToolCatalogEntry } from "./tools/parse_tool_catalog.js";
|
|
96
109
|
export {
|
|
97
110
|
createRouteTools,
|
|
@@ -99,13 +112,13 @@ export {
|
|
|
99
112
|
type RouteMap,
|
|
100
113
|
type RouteWithParams,
|
|
101
114
|
} from "./tools/route_map.js";
|
|
102
|
-
export { createStateHookTools, type StateHook } from "./tools/state_hook.js";
|
|
103
115
|
export {
|
|
104
116
|
type ApprovalOptions,
|
|
105
117
|
type ApprovalRenderer,
|
|
106
118
|
type ApprovalRequest,
|
|
107
119
|
requestApproval,
|
|
108
120
|
} from "./ui/approval_card.js";
|
|
121
|
+
export { CheckpointMenu, type CheckpointVerb } from "./ui/checkpoint_menu.js";
|
|
109
122
|
export {
|
|
110
123
|
type ConfirmationOptions,
|
|
111
124
|
type ConfirmationRequest,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { X_DESTRUCTIVE_KEY, X_SUMMARY_KEY } from "../constants.js";
|
|
2
|
+
import type { ClientTool } from "./client_tool_registry.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A binding from a named piece of host page state to agent tools.
|
|
6
|
+
*
|
|
7
|
+
* Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
|
|
8
|
+
* generates a read tool and, when `write` is given, a destructive set tool.
|
|
9
|
+
*
|
|
10
|
+
* This is **not** AG-UI shared state. `STATE_SNAPSHOT` / `STATE_DELTA` are
|
|
11
|
+
* protocol events carrying state between agent and client; this is a pair of
|
|
12
|
+
* ordinary client tools the agent calls like any other. The two are unrelated,
|
|
13
|
+
* which is why this no longer carries the word "hook" — the old name read as
|
|
14
|
+
* protocol state sync, which this component does not implement.
|
|
15
|
+
*/
|
|
16
|
+
export interface PageState {
|
|
17
|
+
/** Base name; tools become `read_<name>` and `set_<name>`. */
|
|
18
|
+
readonly name: string;
|
|
19
|
+
/** Returns the current state value. */
|
|
20
|
+
readonly read: () => unknown;
|
|
21
|
+
/** Mutates the state from the agent-supplied args. Omit for read-only. */
|
|
22
|
+
readonly write?: (args: Record<string, unknown>) => unknown;
|
|
23
|
+
/** JSON-Schema for the set tool's args. Defaults to an open object. */
|
|
24
|
+
readonly schema?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build the tools for a {@link PageState}: always a `read_<name>` (read-only)
|
|
29
|
+
* tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
|
|
30
|
+
*/
|
|
31
|
+
export function createPageStateTools(binding: PageState): ClientTool[] {
|
|
32
|
+
const tools: ClientTool[] = [
|
|
33
|
+
{
|
|
34
|
+
name: `read_${binding.name}`,
|
|
35
|
+
description: `Read the "${binding.name}" state.`,
|
|
36
|
+
parameters: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {},
|
|
39
|
+
required: [],
|
|
40
|
+
[X_SUMMARY_KEY]: `Read ${binding.name}`,
|
|
41
|
+
},
|
|
42
|
+
handler: () => binding.read(),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const write = binding.write;
|
|
46
|
+
if (write !== undefined) {
|
|
47
|
+
tools.push({
|
|
48
|
+
name: `set_${binding.name}`,
|
|
49
|
+
description: `Update the "${binding.name}" state.`,
|
|
50
|
+
parameters: {
|
|
51
|
+
...(binding.schema ?? { type: "object" }),
|
|
52
|
+
[X_DESTRUCTIVE_KEY]: true,
|
|
53
|
+
[X_SUMMARY_KEY]: `Update ${binding.name}`,
|
|
54
|
+
},
|
|
55
|
+
handler: (args) => write(args),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return tools;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated Renamed to {@link PageState}. The old name read as AG-UI
|
|
63
|
+
* shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this component
|
|
64
|
+
* does not implement. Will be removed in a future major.
|
|
65
|
+
*/
|
|
66
|
+
export type StateHook = PageState;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated Renamed to {@link createPageStateTools}. Will be removed in a
|
|
70
|
+
* future major.
|
|
71
|
+
*/
|
|
72
|
+
export const createStateHookTools = createPageStateTools;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { RunRow } from "../core/run_index.js";
|
|
2
|
+
import { relativeTime } from "./relative_time.js";
|
|
3
|
+
import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
|
|
4
|
+
|
|
5
|
+
/** How the host continues a picked run. */
|
|
6
|
+
export type CheckpointVerb = "resume" | "fork";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The checkpoint panel: continuable runs, each offering **resume** or **fork**.
|
|
10
|
+
*
|
|
11
|
+
* A separate surface from the thread drawer on purpose — they are different
|
|
12
|
+
* axes. A thread is a conversation you switch *to*; a checkpoint is a run you
|
|
13
|
+
* continue *from*, and one thread can hold many. Folding them into one list
|
|
14
|
+
* would make "resume" look like "open", which it isn't: resuming starts a new
|
|
15
|
+
* run seeded from a snapshot.
|
|
16
|
+
*
|
|
17
|
+
* Only rows the server marked `continuable` are worth offering, so the host
|
|
18
|
+
* feeds those; a run with no snapshot would resume from nothing. Pure DOM in
|
|
19
|
+
* the spirit of {@link SkillsMenu} — the host appends {@link element}, toggles
|
|
20
|
+
* it, feeds rows via {@link setRuns}, and acts on {@link onPick}.
|
|
21
|
+
*/
|
|
22
|
+
export class CheckpointMenu {
|
|
23
|
+
/** The panel root. Append to the chat shell; hidden until opened. */
|
|
24
|
+
readonly element: HTMLDivElement;
|
|
25
|
+
|
|
26
|
+
readonly #onPick: (runId: string, verb: CheckpointVerb) => void;
|
|
27
|
+
readonly #list: HTMLDivElement;
|
|
28
|
+
readonly #heading: HTMLSpanElement;
|
|
29
|
+
#strings: UiStrings;
|
|
30
|
+
#runs: readonly RunRow[] = [];
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
onPick: (runId: string, verb: CheckpointVerb) => void,
|
|
34
|
+
strings: UiStrings = DEFAULT_UI_STRINGS,
|
|
35
|
+
) {
|
|
36
|
+
this.#onPick = onPick;
|
|
37
|
+
this.#strings = strings;
|
|
38
|
+
|
|
39
|
+
this.element = document.createElement("div");
|
|
40
|
+
this.element.className = "checkpoints";
|
|
41
|
+
this.element.setAttribute("part", "checkpoints");
|
|
42
|
+
this.element.setAttribute("role", "dialog");
|
|
43
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
44
|
+
this.element.hidden = true;
|
|
45
|
+
|
|
46
|
+
const header = document.createElement("div");
|
|
47
|
+
header.className = "checkpoints-header";
|
|
48
|
+
header.setAttribute("part", "checkpoints-header");
|
|
49
|
+
this.#heading = document.createElement("span");
|
|
50
|
+
this.#heading.className = "checkpoints-title";
|
|
51
|
+
this.#heading.textContent = strings.checkpoints;
|
|
52
|
+
header.append(this.#heading);
|
|
53
|
+
|
|
54
|
+
this.#list = document.createElement("div");
|
|
55
|
+
this.#list.className = "checkpoints-list";
|
|
56
|
+
this.#list.setAttribute("part", "checkpoints-list");
|
|
57
|
+
|
|
58
|
+
this.element.append(header, this.#list);
|
|
59
|
+
this.element.addEventListener("keydown", (event) => {
|
|
60
|
+
if (event.key === "Escape") {
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
this.close();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Replace the rows. The host passes only `continuable` runs. */
|
|
68
|
+
setRuns(runs: readonly RunRow[]): void {
|
|
69
|
+
this.#runs = runs;
|
|
70
|
+
this.#render();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Re-localize a panel built before the host's strings resolved. */
|
|
74
|
+
setStrings(strings: UiStrings): void {
|
|
75
|
+
this.#strings = strings;
|
|
76
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
77
|
+
this.#heading.textContent = strings.checkpoints;
|
|
78
|
+
this.#render();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
open(): void {
|
|
82
|
+
this.element.hidden = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
close(): void {
|
|
86
|
+
this.element.hidden = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get open_(): boolean {
|
|
90
|
+
return !this.element.hidden;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#render(): void {
|
|
94
|
+
this.#list.replaceChildren();
|
|
95
|
+
if (this.#runs.length === 0) {
|
|
96
|
+
const empty = document.createElement("div");
|
|
97
|
+
empty.className = "checkpoints-empty";
|
|
98
|
+
empty.setAttribute("part", "checkpoints-empty");
|
|
99
|
+
empty.textContent = this.#strings.noCheckpoints;
|
|
100
|
+
this.#list.append(empty);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const run of this.#runs) {
|
|
104
|
+
this.#list.append(this.#row(run));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#row(run: RunRow): HTMLDivElement {
|
|
109
|
+
const row = document.createElement("div");
|
|
110
|
+
row.className = "checkpoint-row";
|
|
111
|
+
row.setAttribute("part", "checkpoint-row");
|
|
112
|
+
|
|
113
|
+
const label = document.createElement("span");
|
|
114
|
+
label.className = "checkpoint-label";
|
|
115
|
+
// A run id is opaque to a person, so the time is the identifying detail;
|
|
116
|
+
// the id rides `title` for anyone who needs to correlate with server logs.
|
|
117
|
+
label.textContent =
|
|
118
|
+
run.started_at === null
|
|
119
|
+
? run.run_id
|
|
120
|
+
: relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
|
|
121
|
+
label.title = run.run_id;
|
|
122
|
+
row.append(label);
|
|
123
|
+
|
|
124
|
+
if (run.parent_run_id !== null) {
|
|
125
|
+
// Lineage, so a branch doesn't read as a duplicate of its parent.
|
|
126
|
+
const branch = document.createElement("span");
|
|
127
|
+
branch.className = "checkpoint-branch";
|
|
128
|
+
branch.setAttribute("part", "checkpoint-branch");
|
|
129
|
+
branch.textContent = this.#strings.forkedRun;
|
|
130
|
+
branch.title = run.parent_run_id;
|
|
131
|
+
row.append(branch);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
row.append(
|
|
135
|
+
this.#action(run.run_id, "resume", this.#strings.resumeRun),
|
|
136
|
+
this.#action(run.run_id, "fork", this.#strings.forkRun),
|
|
137
|
+
);
|
|
138
|
+
return row;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#action(runId: string, verb: CheckpointVerb, label: string): HTMLButtonElement {
|
|
142
|
+
const button = document.createElement("button");
|
|
143
|
+
button.type = "button";
|
|
144
|
+
button.className = `checkpoint-action checkpoint-${verb}`;
|
|
145
|
+
button.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
|
|
146
|
+
button.textContent = label;
|
|
147
|
+
button.addEventListener("click", () => {
|
|
148
|
+
this.close();
|
|
149
|
+
this.#onPick(runId, verb);
|
|
150
|
+
});
|
|
151
|
+
return button;
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/ui/styles.ts
CHANGED
|
@@ -1229,6 +1229,82 @@ export const STYLES = `
|
|
|
1229
1229
|
display: none;
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
|
+
.checkpoints {
|
|
1233
|
+
position: absolute;
|
|
1234
|
+
inset-block-start: 3rem;
|
|
1235
|
+
inset-inline: 0.75rem;
|
|
1236
|
+
z-index: 6;
|
|
1237
|
+
display: flex;
|
|
1238
|
+
flex-direction: column;
|
|
1239
|
+
gap: 0.25rem;
|
|
1240
|
+
padding: 0.5rem;
|
|
1241
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
1242
|
+
border-radius: 0.5rem;
|
|
1243
|
+
background: var(--agui-surface, #fff);
|
|
1244
|
+
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
1245
|
+
max-height: 60%;
|
|
1246
|
+
overflow-y: auto;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.checkpoints[hidden] {
|
|
1250
|
+
display: none;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.checkpoints-title {
|
|
1254
|
+
font-size: 0.75rem;
|
|
1255
|
+
font-weight: 600;
|
|
1256
|
+
opacity: 0.7;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.checkpoints-empty {
|
|
1260
|
+
padding: 0.5rem 0.25rem;
|
|
1261
|
+
font-size: 0.8125rem;
|
|
1262
|
+
opacity: 0.7;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.checkpoint-row {
|
|
1266
|
+
display: flex;
|
|
1267
|
+
align-items: center;
|
|
1268
|
+
gap: 0.5rem;
|
|
1269
|
+
padding: 0.25rem;
|
|
1270
|
+
border-radius: 0.375rem;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
.checkpoint-row:hover {
|
|
1274
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.checkpoint-label {
|
|
1278
|
+
flex: 1;
|
|
1279
|
+
font-size: 0.8125rem;
|
|
1280
|
+
white-space: nowrap;
|
|
1281
|
+
overflow: hidden;
|
|
1282
|
+
text-overflow: ellipsis;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.checkpoint-branch {
|
|
1286
|
+
font-size: 0.6875rem;
|
|
1287
|
+
padding: 0 0.375rem;
|
|
1288
|
+
border-radius: 999px;
|
|
1289
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1290
|
+
opacity: 0.8;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.checkpoint-action {
|
|
1294
|
+
font: inherit;
|
|
1295
|
+
font-size: 0.75rem;
|
|
1296
|
+
cursor: pointer;
|
|
1297
|
+
padding: 0.125rem 0.5rem;
|
|
1298
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
1299
|
+
border-radius: 0.375rem;
|
|
1300
|
+
background: transparent;
|
|
1301
|
+
color: inherit;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.checkpoint-action:hover {
|
|
1305
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1232
1308
|
.drawer-backdrop {
|
|
1233
1309
|
position: absolute;
|
|
1234
1310
|
inset: 0;
|
package/src/ui/ui_strings.ts
CHANGED
|
@@ -157,6 +157,16 @@ export interface UiStrings {
|
|
|
157
157
|
minutesAgo: string;
|
|
158
158
|
/** Hours ago. Token: `{n}`. */
|
|
159
159
|
hoursAgo: string;
|
|
160
|
+
/** Title of the checkpoint panel. */
|
|
161
|
+
checkpoints: string;
|
|
162
|
+
/** Empty state when no run can be continued. */
|
|
163
|
+
noCheckpoints: string;
|
|
164
|
+
/** Action: continue a run from its last checkpoint. */
|
|
165
|
+
resumeRun: string;
|
|
166
|
+
/** Action: branch a run without touching the original. */
|
|
167
|
+
forkRun: string;
|
|
168
|
+
/** Badge on a run that branched from another. */
|
|
169
|
+
forkedRun: string;
|
|
160
170
|
/** Days ago. Token: `{n}`. */
|
|
161
171
|
daysAgo: string;
|
|
162
172
|
/** Weeks ago. Token: `{n}`. */
|
|
@@ -171,6 +181,11 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
|
|
|
171
181
|
collapse: "Collapse",
|
|
172
182
|
expand: "Expand",
|
|
173
183
|
toggleTheme: "Toggle theme",
|
|
184
|
+
checkpoints: "Continue a run",
|
|
185
|
+
noCheckpoints: "Nothing to continue yet.",
|
|
186
|
+
resumeRun: "Resume",
|
|
187
|
+
forkRun: "Fork",
|
|
188
|
+
forkedRun: "branched",
|
|
174
189
|
|
|
175
190
|
conversation: "Conversation",
|
|
176
191
|
thinking: "Assistant is thinking…",
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.13.0";
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ClientTool } from "./client_tool_registry.js";
|
|
2
|
-
/**
|
|
3
|
-
* A binding from a named piece of host application state to agent tools.
|
|
4
|
-
*
|
|
5
|
-
* Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
|
|
6
|
-
* generates a read tool and, when `write` is given, a destructive set tool.
|
|
7
|
-
*/
|
|
8
|
-
export interface StateHook {
|
|
9
|
-
/** Base name; tools become `read_<name>` and `set_<name>`. */
|
|
10
|
-
readonly name: string;
|
|
11
|
-
/** Returns the current state value. */
|
|
12
|
-
readonly read: () => unknown;
|
|
13
|
-
/** Mutates the state from the agent-supplied args. Omit for read-only. */
|
|
14
|
-
readonly write?: (args: Record<string, unknown>) => unknown;
|
|
15
|
-
/** JSON-Schema for the set tool's args. Defaults to an open object. */
|
|
16
|
-
readonly schema?: Record<string, unknown>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Build the tools for a {@link StateHook}: always a `read_<name>` (read-only)
|
|
20
|
-
* tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
|
|
21
|
-
*/
|
|
22
|
-
export declare function createStateHookTools(hook: StateHook): ClientTool[];
|
|
23
|
-
//# sourceMappingURL=state_hook.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"state_hook.d.ts","sourceRoot":"","sources":["../../src/tools/state_hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,CA4BlE"}
|
package/src/tools/state_hook.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { X_DESTRUCTIVE_KEY, X_SUMMARY_KEY } from "../constants.js";
|
|
2
|
-
import type { ClientTool } from "./client_tool_registry.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A binding from a named piece of host application state to agent tools.
|
|
6
|
-
*
|
|
7
|
-
* Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
|
|
8
|
-
* generates a read tool and, when `write` is given, a destructive set tool.
|
|
9
|
-
*/
|
|
10
|
-
export interface StateHook {
|
|
11
|
-
/** Base name; tools become `read_<name>` and `set_<name>`. */
|
|
12
|
-
readonly name: string;
|
|
13
|
-
/** Returns the current state value. */
|
|
14
|
-
readonly read: () => unknown;
|
|
15
|
-
/** Mutates the state from the agent-supplied args. Omit for read-only. */
|
|
16
|
-
readonly write?: (args: Record<string, unknown>) => unknown;
|
|
17
|
-
/** JSON-Schema for the set tool's args. Defaults to an open object. */
|
|
18
|
-
readonly schema?: Record<string, unknown>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build the tools for a {@link StateHook}: always a `read_<name>` (read-only)
|
|
23
|
-
* tool, plus a `set_<name>` (`x-destructive`) tool when `write` is supplied.
|
|
24
|
-
*/
|
|
25
|
-
export function createStateHookTools(hook: StateHook): ClientTool[] {
|
|
26
|
-
const tools: ClientTool[] = [
|
|
27
|
-
{
|
|
28
|
-
name: `read_${hook.name}`,
|
|
29
|
-
description: `Read the "${hook.name}" state.`,
|
|
30
|
-
parameters: {
|
|
31
|
-
type: "object",
|
|
32
|
-
properties: {},
|
|
33
|
-
required: [],
|
|
34
|
-
[X_SUMMARY_KEY]: `Read ${hook.name}`,
|
|
35
|
-
},
|
|
36
|
-
handler: () => hook.read(),
|
|
37
|
-
},
|
|
38
|
-
];
|
|
39
|
-
const write = hook.write;
|
|
40
|
-
if (write !== undefined) {
|
|
41
|
-
tools.push({
|
|
42
|
-
name: `set_${hook.name}`,
|
|
43
|
-
description: `Update the "${hook.name}" state.`,
|
|
44
|
-
parameters: {
|
|
45
|
-
...(hook.schema ?? { type: "object" }),
|
|
46
|
-
[X_DESTRUCTIVE_KEY]: true,
|
|
47
|
-
[X_SUMMARY_KEY]: `Update ${hook.name}`,
|
|
48
|
-
},
|
|
49
|
-
handler: (args) => write(args),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return tools;
|
|
53
|
-
}
|