@artooi/ag-ui-web-component 0.22.0 → 0.23.1
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 +134 -36
- package/README.md +7 -7
- package/dist/ag-ui-web-component.bundle.js +145 -118
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +65 -78
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +93 -126
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +24 -30
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +9 -14
- package/dist/core/attachment.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +20 -27
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +13 -15
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +8 -9
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +11 -20
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/core/transcribe_audio.d.ts +8 -8
- package/dist/core/transcribe_audio.d.ts.map +1 -1
- package/dist/core/upload_attachment.d.ts +15 -18
- package/dist/core/upload_attachment.d.ts.map +1 -1
- package/dist/core/utils.d.ts +4 -6
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +22 -30
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/dom_driver.d.ts +7 -7
- package/dist/dom/native_setter.d.ts +2 -2
- package/dist/dom/native_setter.d.ts.map +1 -1
- package/dist/index.js +347 -401
- package/dist/index.js.map +2 -2
- package/dist/skills/fill_template.d.ts +4 -5
- package/dist/skills/fill_template.d.ts.map +1 -1
- package/dist/skills/parse_skills.d.ts.map +1 -1
- package/dist/skills/skill.d.ts +7 -8
- package/dist/skills/skill.d.ts.map +1 -1
- package/dist/tools/client_tool_registry.d.ts +2 -2
- package/dist/tools/page_action_tools.d.ts +7 -10
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/tools/page_state.d.ts +5 -8
- package/dist/tools/page_state.d.ts.map +1 -1
- package/dist/tools/route_map.d.ts +7 -10
- package/dist/tools/route_map.d.ts.map +1 -1
- package/dist/ui/approval_card.d.ts +15 -20
- package/dist/ui/approval_card.d.ts.map +1 -1
- package/dist/ui/attach_copy_buttons.d.ts +4 -10
- package/dist/ui/attach_copy_buttons.d.ts.map +1 -1
- package/dist/ui/attachment_chips.d.ts +13 -5
- package/dist/ui/attachment_chips.d.ts.map +1 -1
- package/dist/ui/attachment_tray.d.ts +6 -6
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts +7 -8
- package/dist/ui/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +10 -15
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/question_card.d.ts +12 -15
- package/dist/ui/question_card.d.ts.map +1 -1
- package/dist/ui/relative_time.d.ts +5 -7
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/render_markdown.d.ts +8 -8
- package/dist/ui/render_markdown.d.ts.map +1 -1
- package/dist/ui/resize_handle.d.ts +21 -34
- package/dist/ui/resize_handle.d.ts.map +1 -1
- package/dist/ui/run_notice.d.ts +5 -7
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/skills_menu.d.ts +4 -5
- package/dist/ui/skills_menu.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thoughts_block.d.ts +9 -11
- package/dist/ui/thoughts_block.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +6 -5
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +17 -25
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +6 -12
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts +10 -11
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +71 -80
- package/src/core/ag_ui_chat.ts +242 -292
- package/src/core/agui_client.ts +60 -71
- package/src/core/attachment.ts +9 -14
- package/src/core/conversation_store.ts +25 -33
- package/src/core/create_http_agent.ts +18 -22
- package/src/core/remote_conversation_store.ts +14 -15
- package/src/core/run_index.ts +14 -23
- package/src/core/transcribe_audio.ts +9 -10
- package/src/core/upload_attachment.ts +18 -21
- package/src/core/utils.ts +4 -6
- package/src/dom/animations.ts +33 -43
- package/src/dom/dom_driver.ts +7 -7
- package/src/dom/native_setter.ts +11 -12
- package/src/skills/fill_template.ts +4 -5
- package/src/skills/parse_skills.ts +3 -4
- package/src/skills/skill.ts +7 -8
- package/src/tools/client_tool_registry.ts +2 -2
- package/src/tools/page_action_tools.ts +12 -15
- package/src/tools/page_state.ts +5 -8
- package/src/tools/route_map.ts +15 -19
- package/src/ui/approval_card.ts +15 -20
- package/src/ui/attach_copy_buttons.ts +9 -18
- package/src/ui/attachment_chips.ts +19 -10
- package/src/ui/attachment_tray.ts +8 -7
- package/src/ui/checkpoint_menu.ts +7 -8
- package/src/ui/confirmation_card.ts +10 -15
- package/src/ui/question_card.ts +12 -15
- package/src/ui/relative_time.ts +5 -7
- package/src/ui/render_markdown.ts +25 -51
- package/src/ui/resize_handle.ts +25 -38
- package/src/ui/run_notice.ts +9 -12
- package/src/ui/skills_menu.ts +4 -5
- package/src/ui/styles.ts +122 -93
- package/src/ui/thoughts_block.ts +11 -13
- package/src/ui/thread_drawer.ts +6 -5
- package/src/ui/tool_call_card.ts +22 -32
- package/src/ui/ui_strings.ts +6 -12
- package/src/ui/voice_input.ts +10 -11
- package/src/version.ts +1 -1
|
@@ -3,16 +3,13 @@ import type { AttachmentRef } from "./attachment.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* The composer's upload contract: take a `File`, report `0..1` progress, and
|
|
5
5
|
* resolve to a durable {@link AttachmentRef}. The built-in handler is
|
|
6
|
-
* {@link uploadAttachment}
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* transport-agnostic).
|
|
6
|
+
* {@link uploadAttachment}; a host swaps in its own (a `tus-js-client` or
|
|
7
|
+
* direct-to-S3 adapter) via `AgUiChat.uploadHandler` without touching the tray,
|
|
8
|
+
* the chips, or the AG-UI wire, refs being transport-agnostic.
|
|
10
9
|
*
|
|
11
|
-
* The optional
|
|
12
|
-
*
|
|
13
|
-
* doesn't orphan a server-side file.
|
|
14
|
-
* handlers keep working (the extra argument is simply ignored), and a custom
|
|
15
|
-
* handler that honours it should abort its own transport when the signal fires.
|
|
10
|
+
* The optional `signal` fires when the tray removes a chip or the element is
|
|
11
|
+
* torn down; a handler that honours it should abort its own transport, so a
|
|
12
|
+
* cancelled upload doesn't orphan a server-side file.
|
|
16
13
|
*/
|
|
17
14
|
export type UploadHandler = (
|
|
18
15
|
file: File,
|
|
@@ -28,11 +25,11 @@ export interface UploadOptions {
|
|
|
28
25
|
readonly headers?: Record<string, string>;
|
|
29
26
|
/**
|
|
30
27
|
* Cookie policy, spelled as `fetch`'s `credentials` mode for consistency with
|
|
31
|
-
* the
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
28
|
+
* the other endpoints, but carried by `XMLHttpRequest`'s two-state
|
|
29
|
+
* `withCredentials`: `"include"` sets it and every other value leaves it off,
|
|
30
|
+
* matching `"same-origin"`. `"omit"` therefore cannot be honoured for a
|
|
31
|
+
* same-origin upload, XHR always sending cookies there; use a custom
|
|
32
|
+
* {@link UploadHandler} if that matters.
|
|
36
33
|
*/
|
|
37
34
|
readonly credentials?: RequestCredentials;
|
|
38
35
|
/** Progress callback, `0..1`, fired as the body uploads. */
|
|
@@ -45,10 +42,10 @@ export interface UploadOptions {
|
|
|
45
42
|
* Upload one file to the attachments endpoint and resolve to its durable
|
|
46
43
|
* {@link AttachmentRef}.
|
|
47
44
|
*
|
|
48
|
-
* Uses `XMLHttpRequest`
|
|
49
|
-
*
|
|
50
|
-
* CSRF / auth ride along
|
|
51
|
-
*
|
|
45
|
+
* Uses `XMLHttpRequest` rather than `fetch` for real upload-progress events.
|
|
46
|
+
* The file goes as multipart under the `file` field with the element's
|
|
47
|
+
* `headers`, so CSRF / auth ride along; a non-2xx response or a network/abort
|
|
48
|
+
* error rejects, letting the tray show an error chip.
|
|
52
49
|
*/
|
|
53
50
|
export function uploadAttachment(file: File, options: UploadOptions): Promise<AttachmentRef> {
|
|
54
51
|
return new Promise<AttachmentRef>((resolve, reject) => {
|
|
@@ -57,9 +54,9 @@ export function uploadAttachment(file: File, options: UploadOptions): Promise<At
|
|
|
57
54
|
|
|
58
55
|
const xhr = new XMLHttpRequest();
|
|
59
56
|
xhr.open("POST", options.url);
|
|
60
|
-
// Cross-origin cookies ride only when asked for,
|
|
61
|
-
//
|
|
62
|
-
//
|
|
57
|
+
// Cross-origin cookies ride only when asked for, as at the fetch sites.
|
|
58
|
+
// Without this an upload to another subdomain goes anonymous and 401s while
|
|
59
|
+
// the run itself succeeds.
|
|
63
60
|
xhr.withCredentials = options.credentials === "include";
|
|
64
61
|
for (const [key, value] of Object.entries(options.headers ?? {})) {
|
|
65
62
|
xhr.setRequestHeader(key, value);
|
package/src/core/utils.ts
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Overlay a `credentials` mode onto a fetch `init`, or hand the `init` back
|
|
5
|
-
* untouched when
|
|
5
|
+
* untouched when none is configured.
|
|
6
6
|
*
|
|
7
|
-
* Absent and `undefined`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Every fetch this component makes has to make that choice, so it is made in one
|
|
11
|
-
* place rather than repeated (and eventually forgotten) at each call site.
|
|
7
|
+
* Absent and `undefined` differ: `exactOptionalPropertyTypes` rejects an
|
|
8
|
+
* explicit `credentials: undefined`, and writing one anyway would state a mode
|
|
9
|
+
* where the point is to leave the browser's own default in place.
|
|
12
10
|
*/
|
|
13
11
|
export function withCredentials(
|
|
14
12
|
init: RequestInit | undefined,
|
package/src/dom/animations.ts
CHANGED
|
@@ -28,12 +28,11 @@ function delay(ms: number): Promise<void> {
|
|
|
28
28
|
/**
|
|
29
29
|
* Whether the user has asked the OS/browser to minimise motion.
|
|
30
30
|
*
|
|
31
|
-
* Honoured by every primitive that
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* explicit-duration contract.
|
|
31
|
+
* Honoured by every primitive that moves something: hold delays are skipped,
|
|
32
|
+
* `scrollIntoCenterView` jumps instead of gliding, and the flash drops its fade
|
|
33
|
+
* but still holds the ring long enough to be seen — reduced motion asks for no
|
|
34
|
+
* animation, not for no feedback. `typeInto` and `highlightThenClick` keep
|
|
35
|
+
* their explicit-duration contract instead.
|
|
37
36
|
*/
|
|
38
37
|
export function prefersReducedMotion(): boolean {
|
|
39
38
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -48,11 +47,11 @@ function motionDelay(ms: number): Promise<void> {
|
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
|
-
* Resolve the accent colour from the
|
|
50
|
+
* Resolve the accent colour from the target's own computed style.
|
|
52
51
|
*
|
|
53
|
-
* `--ag-ui-accent` inherits, so reading it off the element
|
|
54
|
-
*
|
|
55
|
-
*
|
|
52
|
+
* `--ag-ui-accent` inherits, so reading it off the element about to be touched
|
|
53
|
+
* picks up whatever the host's cascade produced there, and a themed page is
|
|
54
|
+
* flashed in its own colour rather than this package's indigo.
|
|
56
55
|
*/
|
|
57
56
|
function accentColor(el: HTMLElement, fallback: string): string {
|
|
58
57
|
const themed = window.getComputedStyle(el).getPropertyValue(ACCENT_PROPERTY).trim();
|
|
@@ -72,8 +71,8 @@ export interface TypeOptions {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
|
-
* Clear
|
|
76
|
-
*
|
|
74
|
+
* Clear `el` and type `value` one character at a time, firing an `input` event
|
|
75
|
+
* per character as a real user would, then a final `change`.
|
|
77
76
|
*/
|
|
78
77
|
export async function typeInto(
|
|
79
78
|
el: TextLikeElement,
|
|
@@ -98,7 +97,7 @@ export interface HighlightClickOptions {
|
|
|
98
97
|
highlightMs?: number;
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
/** Outline
|
|
100
|
+
/** Outline `el`, pause so the user sees it, then click and restore. */
|
|
102
101
|
export async function highlightThenClick(
|
|
103
102
|
el: HTMLElement,
|
|
104
103
|
options: HighlightClickOptions = {},
|
|
@@ -128,12 +127,11 @@ const SCROLL_SETTLE_MS = 600;
|
|
|
128
127
|
const SCROLL_START_MS = 100;
|
|
129
128
|
|
|
130
129
|
/**
|
|
131
|
-
* Scroll
|
|
130
|
+
* Scroll `el` to the vertical centre of the viewport.
|
|
132
131
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* the scroll itself is requested synchronously.
|
|
132
|
+
* Resolves once the scroll settles, so a caller can hold its animation until
|
|
133
|
+
* the element stops moving — a ring drawn mid-glide lands where the user is not
|
|
134
|
+
* looking yet. Awaiting is optional; the scroll is requested synchronously.
|
|
137
135
|
*/
|
|
138
136
|
export function scrollIntoCenterView(el: HTMLElement, options: ScrollOptions = {}): Promise<void> {
|
|
139
137
|
const reduced = prefersReducedMotion();
|
|
@@ -161,12 +159,10 @@ export function scrollIntoCenterView(el: HTMLElement, options: ScrollOptions = {
|
|
|
161
159
|
// Neither event bubbles, and both fire on whichever ancestor actually
|
|
162
160
|
// scrolled, so listen on the document in the capture phase.
|
|
163
161
|
//
|
|
164
|
-
// Two timeouts,
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
// covers browsers that do not implement scrollend at all (Safari gained it
|
|
169
|
-
// only recently).
|
|
162
|
+
// Two timeouts, for two failure modes: an element already in view never
|
|
163
|
+
// scrolls and so never fires scrollend (the short did-anything-move probe),
|
|
164
|
+
// and once something is moving the longer budget covers browsers without
|
|
165
|
+
// scrollend at all.
|
|
170
166
|
timer = setTimeout(settle, SCROLL_START_MS);
|
|
171
167
|
document.addEventListener("scroll", started, true);
|
|
172
168
|
document.addEventListener("scrollend", settle, true);
|
|
@@ -180,10 +176,7 @@ const FLASH_MS = 1200;
|
|
|
180
176
|
const FLASH_FADE_RATIO = 1 / 3;
|
|
181
177
|
|
|
182
178
|
export interface FlashOptions {
|
|
183
|
-
/**
|
|
184
|
-
* Total milliseconds the ring is on screen, hold plus fade. Default 1200 —
|
|
185
|
-
* long enough for someone who does not yet know where to look to find it.
|
|
186
|
-
*/
|
|
179
|
+
/** Total milliseconds the ring is on screen, hold plus fade. Default 1200. */
|
|
187
180
|
flashMs?: number;
|
|
188
181
|
/** Ring colour. Defaults to the target's `--ag-ui-accent`, else the package accent. */
|
|
189
182
|
color?: string;
|
|
@@ -220,8 +213,8 @@ async function flashRing(
|
|
|
220
213
|
const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
|
|
221
214
|
await delay(flashMs - fadeMs);
|
|
222
215
|
if (fadeMs > 0) {
|
|
223
|
-
//
|
|
224
|
-
//
|
|
216
|
+
// Transition and new colour in the same task is fine: the browser starts
|
|
217
|
+
// transitions from the after-change style.
|
|
225
218
|
el.style.transition = `outline-color ${fadeMs}ms ease-out`;
|
|
226
219
|
el.style.outline = "3px solid transparent";
|
|
227
220
|
await delay(fadeMs);
|
|
@@ -232,9 +225,9 @@ async function flashRing(
|
|
|
232
225
|
}
|
|
233
226
|
|
|
234
227
|
/**
|
|
235
|
-
* Flash a ring around
|
|
228
|
+
* Flash a ring around `el` without touching focus.
|
|
236
229
|
*
|
|
237
|
-
* Prefer this over {@link focusWithFlash} when the point is to
|
|
230
|
+
* Prefer this over {@link focusWithFlash} when the point is to show the user
|
|
238
231
|
* something: moving focus steals it from the composer, can fire blur validation
|
|
239
232
|
* on whatever they were mid-edit in, and can close an open menu.
|
|
240
233
|
*/
|
|
@@ -243,11 +236,8 @@ export function flash(el: HTMLElement, options: FlashOptions = {}): Promise<void
|
|
|
243
236
|
}
|
|
244
237
|
|
|
245
238
|
/**
|
|
246
|
-
* Focus
|
|
247
|
-
*
|
|
248
|
-
* Focus moves unless ``options.focus`` says otherwise — that is the documented
|
|
249
|
-
* behaviour of this name. Use {@link flash} for a highlight that leaves focus
|
|
250
|
-
* where the user put it.
|
|
239
|
+
* Focus `el` and flash a ring around it. Focus moves unless `options.focus`
|
|
240
|
+
* says otherwise; use {@link flash} to leave focus where the user put it.
|
|
251
241
|
*/
|
|
252
242
|
export function focusWithFlash(el: HTMLElement, options: FlashOptions = {}): Promise<void> {
|
|
253
243
|
return flashRing(el, options, true);
|
|
@@ -259,9 +249,9 @@ export interface PressOptions {
|
|
|
259
249
|
}
|
|
260
250
|
|
|
261
251
|
/**
|
|
262
|
-
* Show a brief
|
|
263
|
-
*
|
|
264
|
-
*
|
|
252
|
+
* Show a brief pressed affordance on a control — a slight scale-down plus
|
|
253
|
+
* accent ring — then click it and restore. Reads as a press rather than the
|
|
254
|
+
* plain outline {@link highlightThenClick} draws.
|
|
265
255
|
*/
|
|
266
256
|
export async function pressThenClick(el: HTMLElement, options: PressOptions = {}): Promise<void> {
|
|
267
257
|
const pressMs = options.pressMs ?? 140;
|
|
@@ -283,7 +273,7 @@ export interface SelectOptions {
|
|
|
283
273
|
highlightMs?: number;
|
|
284
274
|
}
|
|
285
275
|
|
|
286
|
-
/** Find an option in
|
|
276
|
+
/** Find an option in `el` whose value or visible text equals `value`. */
|
|
287
277
|
function findOption(el: HTMLSelectElement, value: string): HTMLOptionElement | null {
|
|
288
278
|
for (const option of Array.from(el.options)) {
|
|
289
279
|
if (option.value === value || option.text === value) {
|
|
@@ -295,7 +285,7 @@ function findOption(el: HTMLSelectElement, value: string): HTMLOptionElement | n
|
|
|
295
285
|
|
|
296
286
|
/**
|
|
297
287
|
* Outline a `<select>`, pause so the user sees the pick, then set it to the
|
|
298
|
-
* option matching
|
|
288
|
+
* option matching `value` (by value or visible text) and fire `input` +
|
|
299
289
|
* `change`. Throws when no option matches.
|
|
300
290
|
*/
|
|
301
291
|
export async function selectOption(
|
|
@@ -326,7 +316,7 @@ export interface ToggleOptions {
|
|
|
326
316
|
}
|
|
327
317
|
|
|
328
318
|
/**
|
|
329
|
-
* Flash a ring around a checkbox/radio, set its
|
|
319
|
+
* Flash a ring around a checkbox/radio, set its `checked` state, and fire
|
|
330
320
|
* `input` + `change` so frameworks observe the flip.
|
|
331
321
|
*/
|
|
332
322
|
export async function toggleControl(
|
package/src/dom/dom_driver.ts
CHANGED
|
@@ -25,16 +25,16 @@ import { setNativeChecked, setNativeValue } from "./native_setter.js";
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* Every flash option except `focus`, which is not on offer: a field has to hold
|
|
29
|
+
* focus to be typed into.
|
|
30
30
|
*/
|
|
31
31
|
export interface FillFieldOptions extends TypeOptions, Omit<FlashOptions, "focus"> {}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Scroll to, focus (with a flash), and type
|
|
34
|
+
* Scroll to, focus (with a flash), and type `value` into a text field.
|
|
35
35
|
*
|
|
36
|
-
* The scroll is awaited
|
|
37
|
-
*
|
|
36
|
+
* The scroll is awaited — typing into an element still gliding past shows the
|
|
37
|
+
* user nothing.
|
|
38
38
|
*/
|
|
39
39
|
export async function fillField(
|
|
40
40
|
el: TextLikeElement,
|
|
@@ -82,8 +82,8 @@ export async function toggleCheckbox(
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Set a `<select>` or checkbox value without
|
|
86
|
-
*
|
|
85
|
+
* Set a `<select>` or checkbox value without animation, dispatching the `input`
|
|
86
|
+
* and `change` events frameworks listen for.
|
|
87
87
|
*/
|
|
88
88
|
export function setControlValue(
|
|
89
89
|
el: HTMLInputElement | HTMLSelectElement,
|
package/src/dom/native_setter.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
// Set a control's `value` / `checked` through the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// `fillField` / `typeInto` / `setControlValue` work on framework-controlled
|
|
8
|
-
// inputs out of the box.
|
|
1
|
+
// Set a control's `value` / `checked` through the native prototype setter, so
|
|
2
|
+
// framework value-tracking sees the change. React (and Vue/Svelte) replace the
|
|
3
|
+
// element's own setter with a tracked one, so `el.value = x` hits that override
|
|
4
|
+
// and the following `input` event carries no change: the field looks filled
|
|
5
|
+
// while the framework's state stays empty. Calling the original prototype
|
|
6
|
+
// setter and then dispatching `input` avoids that.
|
|
9
7
|
|
|
10
8
|
type ValueSetter = (this: HTMLElement, value: string) => void;
|
|
11
9
|
type CheckedSetter = (this: HTMLElement, checked: boolean) => void;
|
|
12
10
|
|
|
13
|
-
/** The original prototype setter for
|
|
11
|
+
/** The original prototype setter for `prop` on `proto`. */
|
|
14
12
|
function prototypeSetter(proto: object, prop: string): (this: HTMLElement, value: never) => void {
|
|
15
|
-
// Captured once at module load; DOM environments always define these
|
|
13
|
+
// Captured once at module load; DOM environments always define these, which
|
|
14
|
+
// is what makes the descriptor cast safe.
|
|
16
15
|
const setter = (Object.getOwnPropertyDescriptor(proto, prop) as PropertyDescriptor).set;
|
|
17
16
|
return setter as unknown as (this: HTMLElement, value: never) => void;
|
|
18
17
|
}
|
|
@@ -22,7 +21,7 @@ const setTextareaValue = prototypeSetter(HTMLTextAreaElement.prototype, "value")
|
|
|
22
21
|
const setSelectValue = prototypeSetter(HTMLSelectElement.prototype, "value") as ValueSetter;
|
|
23
22
|
const setInputChecked = prototypeSetter(HTMLInputElement.prototype, "checked") as CheckedSetter;
|
|
24
23
|
|
|
25
|
-
/** Set
|
|
24
|
+
/** Set `el.value` via the element's native prototype setter. */
|
|
26
25
|
export function setNativeValue(
|
|
27
26
|
el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,
|
|
28
27
|
value: string,
|
|
@@ -36,7 +35,7 @@ export function setNativeValue(
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
/** Set
|
|
38
|
+
/** Set `el.checked` via the native prototype setter. */
|
|
40
39
|
export function setNativeChecked(el: HTMLInputElement, checked: boolean): void {
|
|
41
40
|
setInputChecked.call(el, checked);
|
|
42
41
|
}
|
|
@@ -9,12 +9,11 @@ export interface TemplateResult {
|
|
|
9
9
|
const PLACEHOLDER_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Substitute `{name}` placeholders in
|
|
12
|
+
* Substitute `{name}` placeholders in `template` from `values`.
|
|
13
13
|
*
|
|
14
|
-
* A placeholder whose value is missing,
|
|
15
|
-
* verbatim and reported in
|
|
16
|
-
*
|
|
17
|
-
* half-filled prompt.
|
|
14
|
+
* A placeholder whose value is missing, `null`, or an empty string is left
|
|
15
|
+
* verbatim and reported in `missing`, so the caller can block the send rather
|
|
16
|
+
* than dispatch a half-filled prompt.
|
|
18
17
|
*/
|
|
19
18
|
export function fillTemplate(template: string, values: Record<string, unknown>): TemplateResult {
|
|
20
19
|
const missing: string[] = [];
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Skill } from "./skill.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Whether
|
|
4
|
+
* Whether `value` has the required string fields of a {@link Skill}.
|
|
5
5
|
*
|
|
6
|
-
* `prompt`
|
|
7
|
-
*
|
|
8
|
-
* wording was kept off the client.
|
|
6
|
+
* `prompt` must stay optional: a server-resolved skill omits it, and requiring
|
|
7
|
+
* it would silently drop exactly the skills kept off the client.
|
|
9
8
|
*/
|
|
10
9
|
function isSkill(value: unknown): value is Skill {
|
|
11
10
|
if (typeof value !== "object" || value === null) {
|
package/src/skills/skill.ts
CHANGED
|
@@ -15,17 +15,16 @@ export interface Skill {
|
|
|
15
15
|
* The prompt to send. May contain `{placeholder}`s filled from the host's
|
|
16
16
|
* skill context before send; an unfilled placeholder blocks send.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* `/name` token
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Omit it to keep the prompt server-side: the skill then sends the bare
|
|
19
|
+
* `/name` token for the agent to resolve, and the wording never reaches the
|
|
20
|
+
* browser. Prefer that for anything internal — a fetched catalog is a plain
|
|
21
|
+
* GET and an embedded one sits in the page source.
|
|
22
22
|
*/
|
|
23
23
|
readonly prompt?: string;
|
|
24
24
|
/**
|
|
25
|
-
* Set `false` to pre-fill the composer instead of sending on pick.
|
|
26
|
-
* meaningful for a skill
|
|
27
|
-
*
|
|
28
|
-
* anything is a two-step shortcut.
|
|
25
|
+
* Set `false` to pre-fill the composer instead of sending on pick. Defaults
|
|
26
|
+
* to sending, and is only meaningful for a skill carrying its own `prompt`;
|
|
27
|
+
* a server-resolved one always sends.
|
|
29
28
|
*/
|
|
30
29
|
readonly sendImmediately?: boolean;
|
|
31
30
|
/** Also surface this skill as a chip (default false; the palette shows all). */
|
|
@@ -27,8 +27,8 @@ export class ClientToolRegistry {
|
|
|
27
27
|
/**
|
|
28
28
|
* Register a tool, replacing any existing one with the same name.
|
|
29
29
|
*
|
|
30
|
-
* Idempotent on the name so re-fired host
|
|
31
|
-
* double-invoke
|
|
30
|
+
* Idempotent on the name so a re-fired host ref or React StrictMode's
|
|
31
|
+
* double-invoke replaces rather than throws.
|
|
32
32
|
*/
|
|
33
33
|
register(tool: ClientTool): void {
|
|
34
34
|
this.#tools.set(tool.name, tool);
|
|
@@ -4,10 +4,8 @@ import type { ClientTool } from "./client_tool_registry.js";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Resolve a page-action target string to a host-page element, or `null` when it
|
|
7
|
-
* matches nothing. The default
|
|
8
|
-
*
|
|
9
|
-
* the same way host packages wrap the DOM-driver primitives with environment-
|
|
10
|
-
* aware lookups.
|
|
7
|
+
* matches nothing. The default treats the string as a CSS selector; a host with
|
|
8
|
+
* a page map overrides it to map its own element ids.
|
|
11
9
|
*/
|
|
12
10
|
export type ResolvePageTarget = (target: string) => HTMLElement | null;
|
|
13
11
|
|
|
@@ -24,13 +22,12 @@ export const PAGE_ACTIONS = {
|
|
|
24
22
|
* - `scroll_to` — scroll a target (`top` / `bottom` / a resolver target) into
|
|
25
23
|
* view. Benign; no confirmation.
|
|
26
24
|
* - `drag_and_drop` — drag one element onto another, firing the standard HTML5
|
|
27
|
-
* drag sequence so the host page's own drop handler reacts.
|
|
28
|
-
* destructive: a drag rearranges transient state and the durable
|
|
29
|
-
* happens at the page's explicit commit. A host whose page persists
|
|
30
|
-
*
|
|
25
|
+
* drag sequence so the host page's own drop handler reacts. Deliberately not
|
|
26
|
+
* stamped destructive: a drag rearranges transient state, and the durable
|
|
27
|
+
* change happens at the page's explicit commit. A host whose page persists on
|
|
28
|
+
* drop must gate it with the element's `confirmPredicate`.
|
|
31
29
|
*
|
|
32
|
-
* Both report a
|
|
33
|
-
* rather than throwing an opaque crash.
|
|
30
|
+
* Both report a model-readable error when a target resolves to nothing.
|
|
34
31
|
*/
|
|
35
32
|
export function createPageActionTools(
|
|
36
33
|
enabled: ReadonlySet<string>,
|
|
@@ -106,11 +103,11 @@ function dragTool(resolveTarget: ResolvePageTarget): ClientTool {
|
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
/**
|
|
109
|
-
* Fire the standard HTML5 drag sequence
|
|
110
|
-
* `dragenter` / `dragover` / `drop` on the
|
|
111
|
-
* sharing one {@link DataTransfer}
|
|
112
|
-
*
|
|
113
|
-
* environment
|
|
106
|
+
* Fire the standard HTML5 drag sequence in the order a page's own handlers
|
|
107
|
+
* expect — `dragstart` on the source, `dragenter` / `dragover` / `drop` on the
|
|
108
|
+
* target, `dragend` on the source — all sharing one {@link DataTransfer}, which
|
|
109
|
+
* is attached to each event explicitly so a drop handler reads it in every
|
|
110
|
+
* environment.
|
|
114
111
|
*/
|
|
115
112
|
function dispatchDragSequence(from: HTMLElement, to: HTMLElement): void {
|
|
116
113
|
const dataTransfer = new DataTransfer();
|
package/src/tools/page_state.ts
CHANGED
|
@@ -7,11 +7,9 @@ import type { ClientTool } from "./client_tool_registry.js";
|
|
|
7
7
|
* Ergonomic sugar over `registerTool` for SPA state (Redux/Zustand/signals):
|
|
8
8
|
* generates a read tool and, when `write` is given, a destructive set tool.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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.
|
|
10
|
+
* Not AG-UI shared state. `STATE_SNAPSHOT` / `STATE_DELTA` are protocol events
|
|
11
|
+
* carrying state between agent and client; these are ordinary client tools the
|
|
12
|
+
* agent calls like any other.
|
|
15
13
|
*/
|
|
16
14
|
export interface PageState {
|
|
17
15
|
/** Base name; tools become `read_<name>` and `set_<name>`. */
|
|
@@ -59,9 +57,8 @@ export function createPageStateTools(binding: PageState): ClientTool[] {
|
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
|
-
* @deprecated Renamed to {@link PageState}
|
|
63
|
-
* shared-state sync
|
|
64
|
-
* does not implement. Will be removed in a future major.
|
|
60
|
+
* @deprecated Renamed to {@link PageState} — the old name read as AG-UI
|
|
61
|
+
* shared-state sync. Will be removed in a future major.
|
|
65
62
|
*/
|
|
66
63
|
export type StateHook = PageState;
|
|
67
64
|
|
package/src/tools/route_map.ts
CHANGED
|
@@ -7,9 +7,8 @@ export interface Route {
|
|
|
7
7
|
readonly id: string;
|
|
8
8
|
/**
|
|
9
9
|
* The URL path to navigate to. May contain `:name` placeholders for dynamic
|
|
10
|
-
* segments (e.g. `/admin/shop/book/:pk/change/`)
|
|
11
|
-
*
|
|
12
|
-
* parameterised — few pages have a truly static path.
|
|
10
|
+
* segments (e.g. `/admin/shop/book/:pk/change/`), which the agent fills via
|
|
11
|
+
* `navigate_to_route`'s `params` argument.
|
|
13
12
|
*/
|
|
14
13
|
readonly path: string;
|
|
15
14
|
/** Human label shown to the agent. */
|
|
@@ -33,20 +32,19 @@ const PATH_PARAM_RE = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
|
33
32
|
|
|
34
33
|
/** The `:name` path-parameter names declared in a path template, in order. */
|
|
35
34
|
function pathParamNames(path: string): string[] {
|
|
36
|
-
//
|
|
37
|
-
// the group
|
|
38
|
-
//
|
|
39
|
-
// unreachable branch no test can ever cover.
|
|
35
|
+
// Slice the ":" off the whole match rather than read the capture group: under
|
|
36
|
+
// `noUncheckedIndexedAccess`, indexing the group forces an `undefined` guard
|
|
37
|
+
// on a case the regex cannot produce, which no test can cover.
|
|
40
38
|
return [...path.matchAll(PATH_PARAM_RE)].map((match) => match[0].slice(1));
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
|
-
* Substitute `:name` placeholders in
|
|
42
|
+
* Substitute `:name` placeholders in `path` from `params`.
|
|
45
43
|
*
|
|
46
|
-
* Returns the concrete path plus the params
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* Returns the concrete path plus the params no placeholder consumed, for the
|
|
45
|
+
* caller to append as a query string. Throws (naming `routeId`) when a declared
|
|
46
|
+
* path param is missing or empty — a half-filled path must never be navigated
|
|
47
|
+
* to.
|
|
50
48
|
*/
|
|
51
49
|
function fillPath(
|
|
52
50
|
routeId: string,
|
|
@@ -79,13 +77,11 @@ function withQuery(path: string, params: Record<string, unknown>): string {
|
|
|
79
77
|
* The built-in `route.*` tools, bound to live getters so a host can set
|
|
80
78
|
* `routeMap` / `navigate` before or after mount.
|
|
81
79
|
*
|
|
82
|
-
* `list_routes` is read-only and advertises each route's
|
|
83
|
-
*
|
|
84
|
-
* `x-navigates` so an MPA reload
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* routes client-side instead and the run loop simply continues — see
|
|
88
|
-
* `AgUiChat`'s execute path.
|
|
80
|
+
* `list_routes` is read-only and advertises each route's `pathParams` so the
|
|
81
|
+
* model knows what to supply. `navigate_to_route` fills those params, appends
|
|
82
|
+
* the rest as a query string, and is stamped `x-navigates` so an MPA reload
|
|
83
|
+
* checkpoints and resumes; with a host `navigate(path)` callback (an SPA) it
|
|
84
|
+
* routes client-side and the run loop continues instead.
|
|
89
85
|
*/
|
|
90
86
|
export function createRouteTools(
|
|
91
87
|
getRouteMap: () => RouteMap,
|
package/src/ui/approval_card.ts
CHANGED
|
@@ -25,9 +25,8 @@ function actionButton(modifier: string, label: string): HTMLButtonElement {
|
|
|
25
25
|
/** Options for {@link requestApproval}. */
|
|
26
26
|
export interface ApprovalOptions {
|
|
27
27
|
/**
|
|
28
|
-
* Aborting this signal resolves the card as
|
|
29
|
-
* `data-resolved="denied"`
|
|
30
|
-
* pending approval when the user cancels the whole run.
|
|
28
|
+
* Aborting this signal resolves the card as denied, with buttons disabled and
|
|
29
|
+
* `data-resolved="denied"` — how a Stop control dismisses a pending approval.
|
|
31
30
|
*/
|
|
32
31
|
signal?: AbortSignal;
|
|
33
32
|
/** Localized strings; defaults to the English {@link DEFAULT_UI_STRINGS}. */
|
|
@@ -36,13 +35,11 @@ export interface ApprovalOptions {
|
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* A fully custom renderer for a server-side-tool approval, set via
|
|
39
|
-
* `AgUiChat.approvalRenderer`. Receives the {@link ApprovalRequest}
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* anything else. See the `strings` / `::part()` seams for styling the built-in
|
|
45
|
-
* card instead of replacing it.
|
|
38
|
+
* `AgUiChat.approvalRenderer`. Receives the {@link ApprovalRequest} and an
|
|
39
|
+
* `AbortSignal` that fires when the run is stopped, and resolves `true` to
|
|
40
|
+
* approve or `false` to deny. Replaces the built-in {@link requestApproval}
|
|
41
|
+
* card entirely, the host owning the DOM; to restyle the built-in card instead,
|
|
42
|
+
* use the `strings` and `::part()` seams.
|
|
46
43
|
*/
|
|
47
44
|
export type ApprovalRenderer = (
|
|
48
45
|
request: ApprovalRequest,
|
|
@@ -50,17 +47,15 @@ export type ApprovalRenderer = (
|
|
|
50
47
|
) => Promise<boolean>;
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
|
-
* Append an inline
|
|
54
|
-
*
|
|
50
|
+
* Append an inline approval card to `host` and resolve `true` to run a gated
|
|
51
|
+
* server-side tool or `false` to deny it.
|
|
55
52
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* transcript as a resolved record (buttons disabled, `data-resolved` set)
|
|
63
|
-
* rather than vanishing.
|
|
53
|
+
* Distinct from the client-tool confirmation card
|
|
54
|
+
* ({@link requestConfirmation}): a gated server tool defers instead of
|
|
55
|
+
* executing, so the run finishes on an AG-UI interrupt the client answers with
|
|
56
|
+
* `resume[]`, and this is the browser half of that loop. Unlike the
|
|
57
|
+
* confirmation card, this one stays in the transcript as a resolved record,
|
|
58
|
+
* buttons disabled and `data-resolved` set.
|
|
64
59
|
*/
|
|
65
60
|
export function requestApproval(
|
|
66
61
|
host: Node & ParentNode,
|