@artooi/ag-ui-web-component 0.1.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 +13 -0
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/ag-ui-web-component.bundle.js +319 -0
- package/dist/ag-ui-web-component.bundle.js.map +7 -0
- package/dist/ag_ui_chat.d.ts +85 -0
- package/dist/ag_ui_chat.d.ts.map +1 -0
- package/dist/agui_client.d.ts +99 -0
- package/dist/agui_client.d.ts.map +1 -0
- package/dist/animations.d.ts +33 -0
- package/dist/animations.d.ts.map +1 -0
- package/dist/client_tool_registry.d.ts +32 -0
- package/dist/client_tool_registry.d.ts.map +1 -0
- package/dist/confirmation_modal.d.ts +14 -0
- package/dist/confirmation_modal.d.ts.map +1 -0
- package/dist/constants.d.ts +40 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/conversation_store.d.ts +54 -0
- package/dist/conversation_store.d.ts.map +1 -0
- package/dist/create_http_agent.d.ts +22 -0
- package/dist/create_http_agent.d.ts.map +1 -0
- package/dist/define_ag_ui_chat.d.ts +9 -0
- package/dist/define_ag_ui_chat.d.ts.map +1 -0
- package/dist/dom_driver.d.ts +24 -0
- package/dist/dom_driver.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1159 -0
- package/dist/index.js.map +7 -0
- package/dist/is_destructive.d.ts +8 -0
- package/dist/is_destructive.d.ts.map +1 -0
- package/dist/is_navigates.d.ts +9 -0
- package/dist/is_navigates.d.ts.map +1 -0
- package/dist/page_map.d.ts +16 -0
- package/dist/page_map.d.ts.map +1 -0
- package/dist/route_map.d.ts +27 -0
- package/dist/route_map.d.ts.map +1 -0
- package/dist/state_hook.d.ts +23 -0
- package/dist/state_hook.d.ts.map +1 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/tool_call_card.d.ts +29 -0
- package/dist/tool_call_card.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +79 -0
- package/src/ag_ui_chat.ts +411 -0
- package/src/agui_client.ts +212 -0
- package/src/animations.ts +86 -0
- package/src/client_tool_registry.ts +56 -0
- package/src/confirmation_modal.ts +69 -0
- package/src/constants.ts +48 -0
- package/src/conversation_store.ts +103 -0
- package/src/create_http_agent.ts +40 -0
- package/src/define_ag_ui_chat.ts +15 -0
- package/src/dom_driver.ts +60 -0
- package/src/index.ts +60 -0
- package/src/is_destructive.ts +11 -0
- package/src/is_navigates.ts +12 -0
- package/src/page_map.ts +25 -0
- package/src/route_map.ts +83 -0
- package/src/state_hook.ts +44 -0
- package/src/styles.ts +296 -0
- package/src/tool_call_card.ts +95 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Tool } from "@ag-ui/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A tool the frontend declares and executes itself.
|
|
5
|
+
*
|
|
6
|
+
* `parameters` is a JSON Schema (and may carry the `x-destructive` extension).
|
|
7
|
+
* `handler` receives the parsed arguments and returns a result that is
|
|
8
|
+
* JSON-serialised into the AG-UI tool-result message sent back to the agent.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClientTool {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
parameters: Record<string, unknown>;
|
|
14
|
+
handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Holds the frontend tools a host has declared on an `<ag-ui-chat>` element.
|
|
19
|
+
*
|
|
20
|
+
* Produces AG-UI {@link Tool} definitions for `RunAgentInput.tools` and looks
|
|
21
|
+
* up handlers when the agent calls a tool. Pure (no DOM); the element owns one
|
|
22
|
+
* instance.
|
|
23
|
+
*/
|
|
24
|
+
export class ClientToolRegistry {
|
|
25
|
+
readonly #tools = new Map<string, ClientTool>();
|
|
26
|
+
|
|
27
|
+
/** Register a tool. Throws if the name is already taken. */
|
|
28
|
+
register(tool: ClientTool): void {
|
|
29
|
+
if (this.#tools.has(tool.name)) {
|
|
30
|
+
throw new Error(`tool "${tool.name}" already registered`);
|
|
31
|
+
}
|
|
32
|
+
this.#tools.set(tool.name, tool);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
has(name: string): boolean {
|
|
36
|
+
return this.#tools.has(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Return a registered tool or throw. */
|
|
40
|
+
get(name: string): ClientTool {
|
|
41
|
+
const tool = this.#tools.get(name);
|
|
42
|
+
if (tool === undefined) {
|
|
43
|
+
throw new Error(`tool "${name}" is not registered`);
|
|
44
|
+
}
|
|
45
|
+
return tool;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** AG-UI tool definitions for `RunAgentInput.tools`. */
|
|
49
|
+
tools(): Tool[] {
|
|
50
|
+
return [...this.#tools.values()].map((tool) => ({
|
|
51
|
+
name: tool.name,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
parameters: tool.parameters,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** What the confirmation modal displays. */
|
|
2
|
+
export interface ConfirmationRequest {
|
|
3
|
+
toolName: string;
|
|
4
|
+
args: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Render a confirmation modal into ``host`` and resolve when the user decides.
|
|
9
|
+
*
|
|
10
|
+
* Resolves ``true`` if the user confirms, ``false`` if they cancel or dismiss
|
|
11
|
+
* via the backdrop. The modal is appended to ``host`` (the chat container
|
|
12
|
+
* inside the element's shadow root) and removed once resolved.
|
|
13
|
+
*/
|
|
14
|
+
export function requestConfirmation(
|
|
15
|
+
host: Node & ParentNode,
|
|
16
|
+
request: ConfirmationRequest,
|
|
17
|
+
): Promise<boolean> {
|
|
18
|
+
return new Promise<boolean>((resolve) => {
|
|
19
|
+
const overlay = document.createElement("div");
|
|
20
|
+
overlay.className = "modal-overlay";
|
|
21
|
+
|
|
22
|
+
const dialog = document.createElement("div");
|
|
23
|
+
dialog.className = "modal";
|
|
24
|
+
|
|
25
|
+
const title = document.createElement("div");
|
|
26
|
+
title.className = "modal-title";
|
|
27
|
+
title.textContent = "Confirm action";
|
|
28
|
+
|
|
29
|
+
const body = document.createElement("div");
|
|
30
|
+
body.className = "modal-body";
|
|
31
|
+
body.textContent = `Run “${request.toolName}”?`;
|
|
32
|
+
|
|
33
|
+
const args = document.createElement("pre");
|
|
34
|
+
args.className = "modal-args";
|
|
35
|
+
args.textContent = JSON.stringify(request.args, null, 2);
|
|
36
|
+
|
|
37
|
+
const actions = document.createElement("div");
|
|
38
|
+
actions.className = "modal-actions";
|
|
39
|
+
|
|
40
|
+
const cancel = document.createElement("button");
|
|
41
|
+
cancel.className = "modal-btn modal-btn--cancel";
|
|
42
|
+
cancel.type = "button";
|
|
43
|
+
cancel.textContent = "Cancel";
|
|
44
|
+
|
|
45
|
+
const confirm = document.createElement("button");
|
|
46
|
+
confirm.className = "modal-btn modal-btn--confirm";
|
|
47
|
+
confirm.type = "button";
|
|
48
|
+
confirm.textContent = "Run";
|
|
49
|
+
|
|
50
|
+
const close = (accepted: boolean): void => {
|
|
51
|
+
overlay.remove();
|
|
52
|
+
resolve(accepted);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
cancel.addEventListener("click", () => close(false));
|
|
56
|
+
confirm.addEventListener("click", () => close(true));
|
|
57
|
+
overlay.addEventListener("click", (event) => {
|
|
58
|
+
if (event.target === overlay) {
|
|
59
|
+
close(false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
actions.append(cancel, confirm);
|
|
64
|
+
dialog.append(title, body, args, actions);
|
|
65
|
+
overlay.append(dialog);
|
|
66
|
+
host.appendChild(overlay);
|
|
67
|
+
confirm.focus();
|
|
68
|
+
});
|
|
69
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The package's single home for enums and constant-like values. Per
|
|
2
|
+
// CLAUDE.md this is the only file allowed to export multiple symbols.
|
|
3
|
+
|
|
4
|
+
/** The Custom Element tag name registered by {@link defineAgUiChat}. */
|
|
5
|
+
export const ELEMENT_TAG = "ag-ui-chat";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Event dispatched by `<ag-ui-chat>` when the user submits a message.
|
|
9
|
+
* `detail` carries `{ content: string }`. Later phases wire this to the
|
|
10
|
+
* AG-UI client; for now it is the public seam for host integration.
|
|
11
|
+
*/
|
|
12
|
+
export const SUBMIT_EVENT = "ag-ui-submit";
|
|
13
|
+
|
|
14
|
+
/** Roles a chat message can take. */
|
|
15
|
+
export const MESSAGE_ROLE = {
|
|
16
|
+
USER: "user",
|
|
17
|
+
ASSISTANT: "assistant",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* JSON-Schema extension key marking a tool as destructive. Mirrors the
|
|
22
|
+
* `django-ag-ui` server side. When a tool's `parameters` carries
|
|
23
|
+
* `{ "x-destructive": true }`, the element gates its execution behind the
|
|
24
|
+
* confirmation modal (unless `autoConfirm` is set).
|
|
25
|
+
*/
|
|
26
|
+
export const X_DESTRUCTIVE_KEY = "x-destructive";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* JSON-Schema extension key marking a tool as navigating — its handler triggers
|
|
30
|
+
* a full page reload (an MPA navigation). When a tool's `parameters` carries
|
|
31
|
+
* `{ "x-navigates": true }`, the element checkpoints the call before the reload
|
|
32
|
+
* and resumes the run loop once the next page mounts. Mirrors `x-destructive`.
|
|
33
|
+
*/
|
|
34
|
+
export const X_NAVIGATES_KEY = "x-navigates";
|
|
35
|
+
|
|
36
|
+
/** Upper bound on frontend tool-call → re-run rounds within one send. */
|
|
37
|
+
export const MAX_TOOL_ROUNDS = 10;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Lifecycle status of a rendered tool-call card. A card opens as `PENDING`
|
|
41
|
+
* while the call runs, then settles to `DONE`, `ERROR`, or `DECLINED`.
|
|
42
|
+
*/
|
|
43
|
+
export const TOOL_CALL_STATUS = {
|
|
44
|
+
PENDING: "pending",
|
|
45
|
+
DONE: "done",
|
|
46
|
+
ERROR: "error",
|
|
47
|
+
DECLINED: "declined",
|
|
48
|
+
} as const;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomUUID } from "@ag-ui/client";
|
|
2
|
+
import type { Message } from "@ag-ui/core";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A checkpoint recorded just before a navigating tool reloads the page.
|
|
6
|
+
*
|
|
7
|
+
* The reload destroys the in-memory run loop, so the element persists this
|
|
8
|
+
* marker first; on the next mount it supplies the tool's result and resumes.
|
|
9
|
+
*/
|
|
10
|
+
export interface NavigationCheckpoint {
|
|
11
|
+
/** The tool-call id whose result must be supplied after the reload. */
|
|
12
|
+
readonly toolCallId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Client-side persistence seam for the conversation and a pending-navigation
|
|
17
|
+
* checkpoint, keyed by `thread_id`.
|
|
18
|
+
*
|
|
19
|
+
* The default {@link SessionStorageStore} keeps everything per-tab in
|
|
20
|
+
* `sessionStorage`, so the chat survives the full page reloads of a
|
|
21
|
+
* multi-page app. A host may inject a server-backed store instead (e.g. one
|
|
22
|
+
* that rehydrates from a history endpoint); `loadMessages` is therefore
|
|
23
|
+
* async-friendly. The checkpoint methods stay synchronous — the marker is a
|
|
24
|
+
* tiny local hint a server store can derive from history and no-op.
|
|
25
|
+
*/
|
|
26
|
+
export interface ClientConversationStore {
|
|
27
|
+
/** A stable conversation id, generated and persisted on first read. */
|
|
28
|
+
threadId(): string;
|
|
29
|
+
/** Load the persisted message history, or `null` when none exists. */
|
|
30
|
+
loadMessages(threadId: string): Promise<readonly Message[] | null>;
|
|
31
|
+
/** Persist the message history. */
|
|
32
|
+
saveMessages(threadId: string, messages: readonly Message[]): void;
|
|
33
|
+
/** Load the pending-navigation checkpoint, or `null` when none is set. */
|
|
34
|
+
loadCheckpoint(threadId: string): NavigationCheckpoint | null;
|
|
35
|
+
/** Set the pending-navigation checkpoint, or clear it when given `null`. */
|
|
36
|
+
saveCheckpoint(threadId: string, checkpoint: NavigationCheckpoint | null): void;
|
|
37
|
+
/** Forget the conversation and checkpoint (e.g. a "new chat" action). */
|
|
38
|
+
clear(threadId: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const THREAD_KEY = "ag-ui-chat:thread";
|
|
42
|
+
const MESSAGES_PREFIX = "ag-ui-chat:messages:";
|
|
43
|
+
const CHECKPOINT_PREFIX = "ag-ui-chat:checkpoint:";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default {@link ClientConversationStore}: per-tab `sessionStorage`.
|
|
47
|
+
*
|
|
48
|
+
* Survives full page reloads and same-tab navigation, clears on tab close —
|
|
49
|
+
* the right scope for an embedded agent's conversation in a multi-page app.
|
|
50
|
+
* One thread id per tab; the message history and checkpoint are namespaced by
|
|
51
|
+
* it so two tabs hold independent conversations.
|
|
52
|
+
*/
|
|
53
|
+
export class SessionStorageStore implements ClientConversationStore {
|
|
54
|
+
threadId(): string {
|
|
55
|
+
const existing = sessionStorage.getItem(THREAD_KEY);
|
|
56
|
+
if (existing !== null) {
|
|
57
|
+
return existing;
|
|
58
|
+
}
|
|
59
|
+
const id = randomUUID();
|
|
60
|
+
sessionStorage.setItem(THREAD_KEY, id);
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
loadMessages(threadId: string): Promise<readonly Message[] | null> {
|
|
65
|
+
return Promise.resolve(this.#readJson<Message[]>(MESSAGES_PREFIX + threadId));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
saveMessages(threadId: string, messages: readonly Message[]): void {
|
|
69
|
+
sessionStorage.setItem(MESSAGES_PREFIX + threadId, JSON.stringify(messages));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
loadCheckpoint(threadId: string): NavigationCheckpoint | null {
|
|
73
|
+
return this.#readJson<NavigationCheckpoint>(CHECKPOINT_PREFIX + threadId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
saveCheckpoint(threadId: string, checkpoint: NavigationCheckpoint | null): void {
|
|
77
|
+
const key = CHECKPOINT_PREFIX + threadId;
|
|
78
|
+
if (checkpoint === null) {
|
|
79
|
+
sessionStorage.removeItem(key);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
sessionStorage.setItem(key, JSON.stringify(checkpoint));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
clear(threadId: string): void {
|
|
86
|
+
sessionStorage.removeItem(MESSAGES_PREFIX + threadId);
|
|
87
|
+
sessionStorage.removeItem(CHECKPOINT_PREFIX + threadId);
|
|
88
|
+
sessionStorage.removeItem(THREAD_KEY);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Parse a stored JSON value, returning `null` when absent or corrupt. */
|
|
92
|
+
#readJson<T>(key: string): T | null {
|
|
93
|
+
const raw = sessionStorage.getItem(key);
|
|
94
|
+
if (raw === null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(raw) as T;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type AbstractAgent, HttpAgent } from "@ag-ui/client";
|
|
2
|
+
import type { Message } from "@ag-ui/core";
|
|
3
|
+
|
|
4
|
+
/** Config for {@link createHttpAgent}. */
|
|
5
|
+
export interface HttpAgentOptions {
|
|
6
|
+
endpoint: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
/** Stable conversation id, so the agent's runs share a thread. */
|
|
9
|
+
threadId?: string;
|
|
10
|
+
/** Rehydrated history to seed the agent with (durable conversation). */
|
|
11
|
+
initialMessages?: readonly Message[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build an AG-UI {@link HttpAgent} pointed at ``endpoint``.
|
|
16
|
+
*
|
|
17
|
+
* This is the default agent factory used by ``<ag-ui-chat>``. Tests and
|
|
18
|
+
* advanced hosts override the element's ``agentFactory`` to inject a
|
|
19
|
+
* different {@link AbstractAgent} (e.g. a fake, or a middleware-wrapped one).
|
|
20
|
+
*/
|
|
21
|
+
export function createHttpAgent(options: HttpAgentOptions): AbstractAgent {
|
|
22
|
+
return new HttpAgent({
|
|
23
|
+
url: options.endpoint,
|
|
24
|
+
headers: options.headers ?? {},
|
|
25
|
+
// HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
|
|
26
|
+
// which would rebind the global `fetch` to the agent instance and trigger
|
|
27
|
+
// "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
|
|
28
|
+
// a free function with the correct receiver.
|
|
29
|
+
fetch: (url, init) => fetch(url, init),
|
|
30
|
+
// Spread conditionally: under `exactOptionalPropertyTypes` an explicit
|
|
31
|
+
// `undefined` is not assignable to these optional config fields.
|
|
32
|
+
...(options.threadId !== undefined ? { threadId: options.threadId } : {}),
|
|
33
|
+
...(options.initialMessages !== undefined
|
|
34
|
+
? { initialMessages: [...options.initialMessages] }
|
|
35
|
+
: {}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Signature of the agent factory the element calls to build its agent. */
|
|
40
|
+
export type AgentFactory = (options: HttpAgentOptions) => AbstractAgent;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AgUiChat } from "./ag_ui_chat.js";
|
|
2
|
+
import { ELEMENT_TAG } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register the `<ag-ui-chat>` Custom Element.
|
|
6
|
+
*
|
|
7
|
+
* Idempotent: calling it more than once (or after another module already
|
|
8
|
+
* registered the tag) is a no-op. Registration is an explicit step rather
|
|
9
|
+
* than an import side effect so the package is SSR-safe and tree-shakeable.
|
|
10
|
+
*/
|
|
11
|
+
export function defineAgUiChat(): void {
|
|
12
|
+
if (customElements.get(ELEMENT_TAG) === undefined) {
|
|
13
|
+
customElements.define(ELEMENT_TAG, AgUiChat);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
focusWithFlash,
|
|
3
|
+
type HighlightClickOptions,
|
|
4
|
+
highlightThenClick,
|
|
5
|
+
scrollIntoCenterView,
|
|
6
|
+
type TextLikeElement,
|
|
7
|
+
type TypeOptions,
|
|
8
|
+
typeInto,
|
|
9
|
+
} from "./animations.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generic, framework-free DOM-driving primitives.
|
|
13
|
+
*
|
|
14
|
+
* Each operates on an element the caller has already located. Host packages
|
|
15
|
+
* (e.g. `django-admin-agent`) wrap these with environment-aware lookups —
|
|
16
|
+
* `fill_field(name, value)` finds `#id_<name>` then calls {@link fillField}.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface FillFieldOptions extends TypeOptions, FlashOptionsLike {}
|
|
20
|
+
|
|
21
|
+
interface FlashOptionsLike {
|
|
22
|
+
flashMs?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Scroll to, focus (with a flash), and type ``value`` into a text field. */
|
|
26
|
+
export async function fillField(
|
|
27
|
+
el: TextLikeElement,
|
|
28
|
+
value: string,
|
|
29
|
+
options: FillFieldOptions = {},
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
scrollIntoCenterView(el);
|
|
32
|
+
await focusWithFlash(el, { flashMs: options.flashMs ?? 0 });
|
|
33
|
+
await typeInto(el, value, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Scroll to, highlight, and click an element. */
|
|
37
|
+
export async function clickElement(
|
|
38
|
+
el: HTMLElement,
|
|
39
|
+
options: HighlightClickOptions = {},
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
scrollIntoCenterView(el);
|
|
42
|
+
await highlightThenClick(el, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set a `<select>` or checkbox value without typing animation, dispatching the
|
|
47
|
+
* ``input`` and ``change`` events frameworks listen for.
|
|
48
|
+
*/
|
|
49
|
+
export function setControlValue(
|
|
50
|
+
el: HTMLInputElement | HTMLSelectElement,
|
|
51
|
+
value: string | boolean,
|
|
52
|
+
): void {
|
|
53
|
+
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
|
54
|
+
el.checked = Boolean(value);
|
|
55
|
+
} else {
|
|
56
|
+
el.value = String(value);
|
|
57
|
+
}
|
|
58
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
59
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
60
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Public surface re-exports. Per CLAUDE.md, this is the only re-export point.
|
|
2
|
+
export { AgUiChat, type MessageRole, type SubmitDetail } from "./ag_ui_chat.js";
|
|
3
|
+
export {
|
|
4
|
+
AgUiClient,
|
|
5
|
+
type AgUiClientConfig,
|
|
6
|
+
type AgUiClientHandlers,
|
|
7
|
+
type AgUiRunInputs,
|
|
8
|
+
type AgUiToolCall,
|
|
9
|
+
type ExecuteTool,
|
|
10
|
+
type ToolExecution,
|
|
11
|
+
} from "./agui_client.js";
|
|
12
|
+
export {
|
|
13
|
+
type FlashOptions,
|
|
14
|
+
focusWithFlash,
|
|
15
|
+
type HighlightClickOptions,
|
|
16
|
+
highlightThenClick,
|
|
17
|
+
scrollIntoCenterView,
|
|
18
|
+
type TextLikeElement,
|
|
19
|
+
type TypeOptions,
|
|
20
|
+
typeInto,
|
|
21
|
+
} from "./animations.js";
|
|
22
|
+
export { type ClientTool, ClientToolRegistry } from "./client_tool_registry.js";
|
|
23
|
+
export { type ConfirmationRequest, requestConfirmation } from "./confirmation_modal.js";
|
|
24
|
+
export {
|
|
25
|
+
ELEMENT_TAG,
|
|
26
|
+
MAX_TOOL_ROUNDS,
|
|
27
|
+
MESSAGE_ROLE,
|
|
28
|
+
SUBMIT_EVENT,
|
|
29
|
+
TOOL_CALL_STATUS,
|
|
30
|
+
X_DESTRUCTIVE_KEY,
|
|
31
|
+
X_NAVIGATES_KEY,
|
|
32
|
+
} from "./constants.js";
|
|
33
|
+
export {
|
|
34
|
+
type ClientConversationStore,
|
|
35
|
+
type NavigationCheckpoint,
|
|
36
|
+
SessionStorageStore,
|
|
37
|
+
} from "./conversation_store.js";
|
|
38
|
+
export {
|
|
39
|
+
type AgentFactory,
|
|
40
|
+
createHttpAgent,
|
|
41
|
+
type HttpAgentOptions,
|
|
42
|
+
} from "./create_http_agent.js";
|
|
43
|
+
export { defineAgUiChat } from "./define_ag_ui_chat.js";
|
|
44
|
+
export {
|
|
45
|
+
clickElement,
|
|
46
|
+
type FillFieldOptions,
|
|
47
|
+
fillField,
|
|
48
|
+
setControlValue,
|
|
49
|
+
} from "./dom_driver.js";
|
|
50
|
+
export { isDestructive } from "./is_destructive.js";
|
|
51
|
+
export { isNavigates } from "./is_navigates.js";
|
|
52
|
+
export { createPageMapContext, type PageMap } from "./page_map.js";
|
|
53
|
+
export { createRouteTools, type Route, type RouteMap } from "./route_map.js";
|
|
54
|
+
export { createStateHookTools, type StateHook } from "./state_hook.js";
|
|
55
|
+
export {
|
|
56
|
+
type SettledStatus,
|
|
57
|
+
ToolCallCard,
|
|
58
|
+
type ToolCallStatus,
|
|
59
|
+
} from "./tool_call_card.js";
|
|
60
|
+
export { VERSION } from "./version.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { X_DESTRUCTIVE_KEY } from "./constants.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a tool's JSON-Schema `parameters` marks it destructive.
|
|
5
|
+
*
|
|
6
|
+
* Reads the `x-destructive` extension stamped by the server (`django-ag-ui`'s
|
|
7
|
+
* `build_input_schema`) or by a host declaring a tool directly.
|
|
8
|
+
*/
|
|
9
|
+
export function isDestructive(parameters: Record<string, unknown>): boolean {
|
|
10
|
+
return parameters[X_DESTRUCTIVE_KEY] === true;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { X_NAVIGATES_KEY } from "./constants.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a tool's JSON-Schema `parameters` marks it as navigating.
|
|
5
|
+
*
|
|
6
|
+
* Reads the `x-navigates` extension. A navigating tool's handler reloads the
|
|
7
|
+
* page (MPA navigation), so the element checkpoints the call and resumes the
|
|
8
|
+
* run loop after the next page mounts, rather than awaiting a result inline.
|
|
9
|
+
*/
|
|
10
|
+
export function isNavigates(parameters: Record<string, unknown>): boolean {
|
|
11
|
+
return parameters[X_NAVIGATES_KEY] === true;
|
|
12
|
+
}
|
package/src/page_map.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Context } from "@ag-ui/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A compact snapshot of the current page's actionable surface (field
|
|
5
|
+
* names/types/labels, button labels+handles — not values). Host-defined shape;
|
|
6
|
+
* kept small because it rides in every `RunAgentInput.context`.
|
|
7
|
+
*/
|
|
8
|
+
export type PageMap = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build the per-run context entry for the page map.
|
|
12
|
+
*
|
|
13
|
+
* Returns a single `page_map` context item when auto-injection is on and a
|
|
14
|
+
* provider is set, else nothing. Recomputed each run so it reflects the page
|
|
15
|
+
* the agent is currently looking at.
|
|
16
|
+
*/
|
|
17
|
+
export function createPageMapContext(
|
|
18
|
+
getPageMap: (() => PageMap) | null,
|
|
19
|
+
autoInject: boolean,
|
|
20
|
+
): Context[] {
|
|
21
|
+
if (!autoInject || getPageMap === null) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return [{ description: "page_map", value: JSON.stringify(getPageMap()) }];
|
|
25
|
+
}
|
package/src/route_map.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ClientTool } from "./client_tool_registry.js";
|
|
2
|
+
import { X_NAVIGATES_KEY } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
/** A single navigable route the host declares for the agent. */
|
|
5
|
+
export interface Route {
|
|
6
|
+
/** Stable id the agent uses with `navigate_to_route`. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
/** The URL path to navigate to. */
|
|
9
|
+
readonly path: string;
|
|
10
|
+
/** Human label shown to the agent. */
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
/** Optional grouping (e.g. an app or section). */
|
|
13
|
+
readonly group?: string;
|
|
14
|
+
/** Optional longer description of when to use the route. */
|
|
15
|
+
readonly description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The host-declared catalog of navigable routes. */
|
|
19
|
+
export type RouteMap = readonly Route[];
|
|
20
|
+
|
|
21
|
+
/** Append `params` to `path` as a query string (no-op when empty). */
|
|
22
|
+
function withQuery(path: string, params: Record<string, unknown> | undefined): string {
|
|
23
|
+
if (params === undefined) {
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
const usp = new URLSearchParams();
|
|
27
|
+
for (const [key, value] of Object.entries(params)) {
|
|
28
|
+
usp.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
const query = usp.toString();
|
|
31
|
+
return query === "" ? path : `${path}?${query}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The built-in `route.*` tools, bound to live getters so a host can set
|
|
36
|
+
* `routeMap` / `navigate` before or after mount.
|
|
37
|
+
*
|
|
38
|
+
* `list_routes` is read-only; `navigate_to_route` is marked `x-navigates` so an
|
|
39
|
+
* MPA reload checkpoints + resumes. When the host supplies a `navigate(path)`
|
|
40
|
+
* callback (an SPA), the element routes client-side instead and the run loop
|
|
41
|
+
* simply continues — see `AgUiChat`'s execute path.
|
|
42
|
+
*/
|
|
43
|
+
export function createRouteTools(
|
|
44
|
+
getRouteMap: () => RouteMap,
|
|
45
|
+
getNavigate: () => ((path: string) => void) | null,
|
|
46
|
+
): ClientTool[] {
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
name: "list_routes",
|
|
50
|
+
description: "List the routes the app can navigate to.",
|
|
51
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
52
|
+
handler: () => getRouteMap(),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "navigate_to_route",
|
|
56
|
+
description: "Navigate to one of the app's routes by its id.",
|
|
57
|
+
parameters: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
route_id: { type: "string" },
|
|
61
|
+
params: { type: "object" },
|
|
62
|
+
},
|
|
63
|
+
required: ["route_id"],
|
|
64
|
+
[X_NAVIGATES_KEY]: true,
|
|
65
|
+
},
|
|
66
|
+
handler: (args) => {
|
|
67
|
+
const routeId = args["route_id"];
|
|
68
|
+
const route = getRouteMap().find((r) => r.id === routeId);
|
|
69
|
+
if (route === undefined) {
|
|
70
|
+
throw new Error(`unknown route "${String(routeId)}"`);
|
|
71
|
+
}
|
|
72
|
+
const path = withQuery(route.path, args["params"] as Record<string, unknown> | undefined);
|
|
73
|
+
const navigate = getNavigate();
|
|
74
|
+
if (navigate !== null) {
|
|
75
|
+
navigate(path);
|
|
76
|
+
} else {
|
|
77
|
+
window.location.assign(path);
|
|
78
|
+
}
|
|
79
|
+
return { navigated: true, path };
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ClientTool } from "./client_tool_registry.js";
|
|
2
|
+
import { X_DESTRUCTIVE_KEY } from "./constants.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: { type: "object", properties: {}, required: [] },
|
|
31
|
+
handler: () => hook.read(),
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const write = hook.write;
|
|
35
|
+
if (write !== undefined) {
|
|
36
|
+
tools.push({
|
|
37
|
+
name: `set_${hook.name}`,
|
|
38
|
+
description: `Update the "${hook.name}" state.`,
|
|
39
|
+
parameters: { ...(hook.schema ?? { type: "object" }), [X_DESTRUCTIVE_KEY]: true },
|
|
40
|
+
handler: (args) => write(args),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return tools;
|
|
44
|
+
}
|