@artooi/ag-ui-web-component 0.27.0 → 0.29.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 +663 -1
- package/README.md +557 -11
- package/dist/ag-ui-web-component.bundle.js +294 -36
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +69 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +262 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +46 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +43 -1
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +13 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +23 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/utils.d.ts +28 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2104 -194
- package/dist/index.js.map +4 -4
- package/dist/tools/is_destructive.d.ts +8 -2
- package/dist/tools/is_destructive.d.ts.map +1 -1
- package/dist/tools/parse_tool_catalog.d.ts +11 -4
- package/dist/tools/parse_tool_catalog.d.ts.map +1 -1
- package/dist/ui/approval_card.d.ts +18 -0
- package/dist/ui/approval_card.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts +10 -0
- package/dist/ui/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +16 -0
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/message_actions.d.ts +46 -0
- package/dist/ui/message_actions.d.ts.map +1 -0
- package/dist/ui/page_quote_offer.d.ts +33 -0
- package/dist/ui/page_quote_offer.d.ts.map +1 -0
- package/dist/ui/quote_selection.d.ts +66 -0
- package/dist/ui/quote_selection.d.ts.map +1 -0
- package/dist/ui/relative_time.d.ts +10 -0
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/render_markdown.d.ts +23 -5
- package/dist/ui/render_markdown.d.ts.map +1 -1
- package/dist/ui/resize_handle.d.ts +5 -1
- package/dist/ui/resize_handle.d.ts.map +1 -1
- package/dist/ui/stick_to_bottom.d.ts +55 -0
- package/dist/ui/stick_to_bottom.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/suggestion_chips.d.ts +29 -0
- package/dist/ui/suggestion_chips.d.ts.map +1 -0
- package/dist/ui/thread_drawer.d.ts +10 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +8 -0
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +53 -7
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +75 -0
- package/src/core/ag_ui_chat.ts +1357 -113
- package/src/core/agui_client.ts +81 -1
- package/src/core/conversation_store.ts +128 -42
- package/src/core/create_http_agent.ts +24 -2
- package/src/core/remote_conversation_store.ts +35 -2
- package/src/core/utils.ts +58 -0
- package/src/index.ts +39 -0
- package/src/tools/is_destructive.ts +8 -2
- package/src/tools/parse_tool_catalog.ts +18 -6
- package/src/ui/approval_card.ts +90 -2
- package/src/ui/checkpoint_menu.ts +22 -5
- package/src/ui/confirmation_card.ts +29 -1
- package/src/ui/message_actions.ts +158 -0
- package/src/ui/page_quote_offer.ts +215 -0
- package/src/ui/quote_selection.ts +345 -0
- package/src/ui/relative_time.ts +11 -0
- package/src/ui/render_markdown.ts +111 -21
- package/src/ui/resize_handle.ts +32 -2
- package/src/ui/stick_to_bottom.ts +126 -0
- package/src/ui/styles.ts +227 -0
- package/src/ui/suggestion_chips.ts +73 -0
- package/src/ui/thread_drawer.ts +22 -2
- package/src/ui/tool_call_card.ts +9 -0
- package/src/ui/ui_strings.ts +79 -8
- package/src/ui/voice_input.ts +43 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -50,9 +50,16 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
|
|
|
50
50
|
- [Collapsing to the launcher](#collapsing-to-the-launcher)
|
|
51
51
|
- [Tool-call display modes](#tool-call-display-modes)
|
|
52
52
|
- [Markdown rendering](#markdown-rendering)
|
|
53
|
+
- [Follow-up suggestions](#follow-up-suggestions)
|
|
54
|
+
- [Editing a gated call before approving it](#editing-a-gated-call-before-approving-it)
|
|
55
|
+
- [Localizing the timestamps](#localizing-the-timestamps)
|
|
56
|
+
- [Message actions: copy, retry, feedback](#message-actions-copy-retry-feedback)
|
|
57
|
+
- [Quoting a selection](#quoting-a-selection)
|
|
53
58
|
- [Run notices: compaction and agent skills](#run-notices-compaction-and-agent-skills)
|
|
54
59
|
- [Skills: prompt chips and slash palette](#skills-prompt-chips-and-slash-palette)
|
|
55
60
|
- [MPA durability: surviving full page reloads](#mpa-durability-surviving-full-page-reloads)
|
|
61
|
+
- [Who the stored conversation belongs to (`user-key`)](#who-the-stored-conversation-belongs-to-user-key)
|
|
62
|
+
- [Mounting more than one chat on a page](#mounting-more-than-one-chat-on-a-page)
|
|
56
63
|
- [Host seams: the SPA story](#host-seams-the-spa-story)
|
|
57
64
|
- [Public API surface](#public-api-surface)
|
|
58
65
|
- [Theming, density, and placement](#theming-density-and-placement)
|
|
@@ -162,7 +169,9 @@ another origin, add `credentials="include"` too; see
|
|
|
162
169
|
| `data-skills` | — | Inline JSON skill catalog. |
|
|
163
170
|
| `data-skills-url` | — | URL of a JSON skill catalog (fetched with the element's headers and cookie policy). |
|
|
164
171
|
| `data-tools-url` | — | URL of a server tool-label catalog (`[{ name, summary, description? }]`), fetched with the element's headers and cookie policy; labels tool-call cards for server-side tools. |
|
|
172
|
+
| `user-key` | `userKey` | Who the stored conversation belongs to — any string identifying the signed-in principal. Joins the storage namespace, and **changing it purges what the previous principal left behind**. Live (not connect-time): a logout is the host's to announce. See [Who the stored conversation belongs to](#who-the-stored-conversation-belongs-to-user-key). |
|
|
165
173
|
| `data-threads-url` | — | URL of a server thread index (django-ag-ui's `ThreadsView`); enables durable, cross-device chat history. |
|
|
174
|
+
| `data-threads-cache` | — | **On by default.** `="false"` stops mirroring message bodies into `sessionStorage` when `data-threads-url` is set, for a deployment that put history on the server so transcripts stay off the client. Only meaningful alongside `data-threads-url`. |
|
|
166
175
|
| `data-runs-url` | — | URL of a server run index (django-ag-ui's `RunsView`); reveals the header's ⭯ *Continue a run* panel. See [Resuming a run](#resuming-a-run). |
|
|
167
176
|
| `data-attachments-url` | — | URL of the file-upload endpoint (django-ag-ui's `AttachmentsView`); reveals the composer's paperclip picker + drag-and-drop. |
|
|
168
177
|
| `data-attachment-accept` | — | `<input accept>` list for client-side type filtering (e.g. `image/*,.pdf`). The server stays authoritative. |
|
|
@@ -173,6 +182,7 @@ another origin, add `credentials="include"` too; see
|
|
|
173
182
|
| `data-icon-url` | — | Header (and launcher) icon image URL. A slotted `slot="icon"` wins; see [Header & launcher icon](#header-and-launcher-icon). |
|
|
174
183
|
| `data-launcher-icon-url` | — | Icon image URL for the collapsed launcher only, when it should differ from the header's. Falls back to `data-icon-url`; a slotted `slot="launcher"` wins over both. |
|
|
175
184
|
| `data-unread-badge` | — | **On by default.** `="false"` hides the launcher's unread badge; the count and the `ag-ui-unread` event keep running. See [Collapsing to the launcher](#collapsing-to-the-launcher). |
|
|
185
|
+
| `data-quote-selection` | — | **On by default.** `="false"` stops the transcript offering to quote a selection. `quote()` keeps working either way. See [Quoting a selection](#quoting-a-selection). |
|
|
176
186
|
|
|
177
187
|
Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
|
|
178
188
|
`icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
|
|
@@ -196,7 +206,7 @@ same way: `icon-send`, `icon-stop`, `icon-attach`, `icon-voice`.
|
|
|
196
206
|
`confirmPredicate`, `askUser`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
|
|
197
207
|
`getPageMap`, `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`,
|
|
198
208
|
`navigationResult`, `skillContext`, `toolSummaries`, `strings`, `resolvePageTarget`, plus the
|
|
199
|
-
mirrors `endpoint` / `toolDisplay` / `collapsed` / `credentials`.
|
|
209
|
+
mirrors `endpoint` / `userKey` / `toolDisplay` / `collapsed` / `credentials`.
|
|
200
210
|
|
|
201
211
|
`headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
|
|
202
212
|
run; `getHeaders` is the one to use for a credential that rotates. See
|
|
@@ -332,6 +342,41 @@ One asymmetry: uploads use `XMLHttpRequest` for real progress events, and its co
|
|
|
332
342
|
two-state. `include` turns it on; every other value leaves it off. `omit` therefore cannot suppress
|
|
333
343
|
cookies on a *same-origin* upload — supply your own `uploadHandler` if that matters.
|
|
334
344
|
|
|
345
|
+
### Where those credentials are allowed to go
|
|
346
|
+
|
|
347
|
+
Every URL in the table above is a plain HTML attribute, and `headers` / `getHeaders` are attached to
|
|
348
|
+
whatever they name. That is what makes a cross-origin agent work — and it is also why a page must
|
|
349
|
+
never build one of those attributes out of a URL parameter, a CMS field, or anything else it did not
|
|
350
|
+
choose itself. Whoever supplies the value chooses where the token goes: the browser preflights the
|
|
351
|
+
custom header, any server willing to answer receives it, and it leaves on the element's first
|
|
352
|
+
request, before the user has typed anything.
|
|
353
|
+
|
|
354
|
+
Treat all seven as trusted configuration. When any of them resolves to another origin, the element
|
|
355
|
+
says so on the console once per origin, naming the destination and the header names it is about to
|
|
356
|
+
send. That covers all seven, not the agent endpoint alone: the tool catalog, the skills list, the
|
|
357
|
+
thread index, the attachment upload and the transcription endpoint carry the same headers, and
|
|
358
|
+
reporting only the agent would report the least interesting of them.
|
|
359
|
+
|
|
360
|
+
To confirm destinations you chose on purpose and silence the notice, name their origins:
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
chat.trustedOrigins = ["https://api.example.com"];
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
That covers every endpoint the element requests itself, and is forwarded to `createHttpAgent`, so a
|
|
367
|
+
host that does not override `agentFactory` needs nothing else. A custom factory can also be given
|
|
368
|
+
the option directly:
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
chat.agentFactory = (options) =>
|
|
372
|
+
createHttpAgent({ ...options, trustedOrigins: ["https://api.example.com"] });
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Origins are compared as `URL.origin` produces them — scheme, host and port. A notice is a notice,
|
|
376
|
+
not a refusal: nothing is blocked, because a cross-origin agent is a supported deployment and
|
|
377
|
+
refusing would break working installations to defend against a page that is already interpolating
|
|
378
|
+
untrusted data into its own markup.
|
|
379
|
+
|
|
335
380
|
### Framework hosts: configure before you insert
|
|
336
381
|
|
|
337
382
|
`headers`, `getHeaders` and `credentials` are read when a request is made, so they can be set at any
|
|
@@ -449,6 +494,13 @@ card (honouring `data-tool-display`), so server-side output is visible too. The
|
|
|
449
494
|
and context are read **fresh on every run** (`getTools()` / `getContext()`), so they always reflect
|
|
450
495
|
the current page state.
|
|
451
496
|
|
|
497
|
+
The catalog a run advertises is also the set that run can execute. Override `getTools` to scope
|
|
498
|
+
what a page offers — say, exposing `delete_record` only where deleting makes sense — and a call
|
|
499
|
+
naming a tool you withheld is treated exactly as a call naming a tool you never registered: no
|
|
500
|
+
handler runs, and the card settles with the no-result label. Withholding is per run, so the
|
|
501
|
+
mount-wide registry can stay complete. Hosts that leave `getTools` alone advertise the built-ins
|
|
502
|
+
plus everything registered, which is precisely what dispatch could reach anyway.
|
|
503
|
+
|
|
452
504
|
### Stopping a run
|
|
453
505
|
|
|
454
506
|
While a run is in flight the **Send button becomes Stop** (same button, label/`aria-label` swap,
|
|
@@ -491,10 +543,19 @@ chat.registerTool({
|
|
|
491
543
|
});
|
|
492
544
|
```
|
|
493
545
|
|
|
494
|
-
|
|
546
|
+
Registering a name twice replaces the earlier handler rather than throwing, so a re-fired
|
|
547
|
+
host ref or React StrictMode's double-invoke is harmless -- but two different tools sharing
|
|
548
|
+
a name means the second silently wins. Each `<ag-ui-chat>` element owns its own
|
|
495
549
|
registry, AG-UI client, and Shadow DOM, so **multiple instances on one page never interfere** —
|
|
496
550
|
there is no module-level shared state anywhere in the package.
|
|
497
551
|
|
|
552
|
+
**A handler's thrown message leaves the browser.** If a handler rejects, its `Error.message` is
|
|
553
|
+
posted back as that call's tool result: into the conversation, on to the AG-UI endpoint, persisted
|
|
554
|
+
there, and replayed to the model provider on every later round. That is deliberate — a real reason
|
|
555
|
+
is what lets the agent recover — but it means an internal hostname, a signed URL or a
|
|
556
|
+
stack-derived path in a rethrown error is disclosed to parties you never chose. Throw the message
|
|
557
|
+
you would be content for the model to read, and log the detail instead.
|
|
558
|
+
|
|
498
559
|
### Inline confirmation (`x-destructive` / `x-confirm` / `confirmPredicate`)
|
|
499
560
|
|
|
500
561
|
When a tool call needs confirmation, the element appends an **inline confirmation card** (a
|
|
@@ -506,6 +567,8 @@ transcript as a resolved record after the decision:
|
|
|
506
567
|
- **Confirm** → the handler runs and the result is posted back.
|
|
507
568
|
- **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
|
|
508
569
|
next turn.
|
|
570
|
+
- **Always allow** → the handler runs *and* this tool stops prompting for the rest of the
|
|
571
|
+
session. See below for when this button appears.
|
|
509
572
|
|
|
510
573
|
Whether a call is gated is decided in this order:
|
|
511
574
|
|
|
@@ -513,9 +576,27 @@ Whether a call is gated is decided in this order:
|
|
|
513
576
|
2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
|
|
514
577
|
name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
|
|
515
578
|
for others, which a static flag can't express).
|
|
516
|
-
3. Else the
|
|
579
|
+
3. Else if the user has waived this tool name for the session, the call runs.
|
|
580
|
+
4. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
|
|
517
581
|
which reads the `x-destructive` JSON-Schema flag.
|
|
518
582
|
|
|
583
|
+
#### "Always allow", and why only sometimes
|
|
584
|
+
|
|
585
|
+
A prompt that is approved nearly every time is not a decision, it is a speed bump — and the
|
|
586
|
+
reflex it trains is what makes the rare refusal easy to miss. Anthropic published that users
|
|
587
|
+
approve **~93%** of Claude Code permission prompts manually and called interactive confirmation
|
|
588
|
+
*"behaviorally unreliable as a sole safety mechanism"* on that basis. The waiver exists so the
|
|
589
|
+
prompts that remain still mean something.
|
|
590
|
+
|
|
591
|
+
**The button is offered only on cards raised by step 4** — the `x-destructive` default. Where
|
|
592
|
+
`confirmPredicate` is what gated the call, there is no button, because that predicate is
|
|
593
|
+
documented as authoritative and letting one click retire it would silently defeat a host policy.
|
|
594
|
+
The offer and the allowlist sit on the same path, so there is no dead button either.
|
|
595
|
+
|
|
596
|
+
The waiver is **per tool name and per element**, held in memory and never persisted. A session
|
|
597
|
+
decision that outlived the tab would be a permanent grant made by one click — which is what
|
|
598
|
+
`autoConfirm` already exists to say deliberately. It is cleared when the element goes away.
|
|
599
|
+
|
|
519
600
|
AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
|
|
520
601
|
**schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
|
|
521
602
|
constant). There is no parallel metadata channel and no name heuristic — destructiveness is exactly
|
|
@@ -525,6 +606,13 @@ the `x-destructive` flag (or `confirmPredicate`). The registry forwards the flag
|
|
|
525
606
|
If the schema carries an `x-confirm` string (use `X_CONFIRM_KEY`), the card shows it as the prompt;
|
|
526
607
|
otherwise it falls back to a generic `Run "<tool>"?`.
|
|
527
608
|
|
|
609
|
+
**This gate covers frontend tools only.** A server-side tool's schema never reaches the browser —
|
|
610
|
+
tool definitions travel client-to-server on `RunAgentInput.tools`, and the only channel coming back
|
|
611
|
+
is the label catalog (`data-tools-url`), which carries `{ name, summary, description? }` and no
|
|
612
|
+
flags. So marking a server tool destructive does not produce a card here; gate it server-side
|
|
613
|
+
instead (see [Server-side tool approval](#server-side-tool-approval-interrupts)), which surfaces as
|
|
614
|
+
an approval card in the same transcript.
|
|
615
|
+
|
|
528
616
|
```js
|
|
529
617
|
// Per-call: confirm a delete only when it would remove more than one row.
|
|
530
618
|
chat.confirmPredicate = (name, args) =>
|
|
@@ -947,6 +1035,67 @@ it runs again every time the conversation is restored.
|
|
|
947
1035
|
|
|
948
1036
|
---
|
|
949
1037
|
|
|
1038
|
+
## Drawing other things the server pushes
|
|
1039
|
+
|
|
1040
|
+
`activityType` is an open string the protocol does not enumerate — `"chart"` is
|
|
1041
|
+
just the one the component ships a renderer for. Register your own and the
|
|
1042
|
+
server can push anything it likes into the transcript:
|
|
1043
|
+
|
|
1044
|
+
```js
|
|
1045
|
+
chat.registerActivityRenderer({
|
|
1046
|
+
type: "build_status",
|
|
1047
|
+
render: (content) => {
|
|
1048
|
+
const el = document.createElement("div");
|
|
1049
|
+
el.className = "build";
|
|
1050
|
+
el.textContent = `Build ${content.status}`;
|
|
1051
|
+
return el; // return null for content not worth drawing
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
**`render` runs again on every thread load**, so it carries the same contract as
|
|
1057
|
+
a client tool's `render`: a pure function of `content`, deterministic, and free
|
|
1058
|
+
of effects outside the node it returns. Activities are materialised into
|
|
1059
|
+
`role: "activity"` messages and persisted with the transcript, so a renderer that
|
|
1060
|
+
writes to the page instead of returning DOM fires again on every restore.
|
|
1061
|
+
|
|
1062
|
+
The component places what you return, keyed by the activity's `messageId`, so a
|
|
1063
|
+
server repeating an id **replaces** your node rather than adding a second one —
|
|
1064
|
+
the same in-place update charts get. Returning `null` removes whatever was there:
|
|
1065
|
+
live and reload should agree, and the stored content is the version that could
|
|
1066
|
+
not be drawn.
|
|
1067
|
+
|
|
1068
|
+
`chart` and `compaction` are registrations exactly like yours, not privileged
|
|
1069
|
+
branches, so registering either name **replaces the built-in**.
|
|
1070
|
+
|
|
1071
|
+
### Which carrier should the server use?
|
|
1072
|
+
|
|
1073
|
+
AG-UI leaves exactly two payload names open, and they are not
|
|
1074
|
+
interchangeable:
|
|
1075
|
+
|
|
1076
|
+
| | Carrier | Reaches | Persisted | Replayed |
|
|
1077
|
+
| --- | --- | --- | --- | --- |
|
|
1078
|
+
| **Content** | `ACTIVITY_SNAPSHOT` | the transcript | yes | yes |
|
|
1079
|
+
| **Imperative** | `CUSTOM` | your page, as [`ag-ui-custom`](#events) | no | no |
|
|
1080
|
+
|
|
1081
|
+
⇒ **Content has a place in the conversation and should come back. An imperative
|
|
1082
|
+
has no place and no meaning once acted on** — replaying "refetch the board" on
|
|
1083
|
+
every thread load is a bug, not a feature. If it has to survive a reload, it is
|
|
1084
|
+
content.
|
|
1085
|
+
|
|
1086
|
+
### Finding out what arrived
|
|
1087
|
+
|
|
1088
|
+
An activity nobody registered for draws nothing and logs nothing — that is the
|
|
1089
|
+
protocol's own answer, and warning would fire on every forward-compatible
|
|
1090
|
+
server. But silence is hard to debug, so the names are readable:
|
|
1091
|
+
|
|
1092
|
+
```js
|
|
1093
|
+
chat.unhandledActivityTypes; // ["pydantic_ai_thinking", …]
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
Note `"chart"` appears there until you call `enableCharts(["activity"])`, which
|
|
1097
|
+
is the honest answer to "I pushed a chart and nothing happened".
|
|
1098
|
+
|
|
950
1099
|
## Tool-call display modes
|
|
951
1100
|
|
|
952
1101
|
How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
|
|
@@ -1042,9 +1191,12 @@ via the `resize-handle` part.
|
|
|
1042
1191
|
Assistant bubbles render sanitized markdown/HTML via [`marked`](https://www.npmjs.com/package/marked)
|
|
1043
1192
|
(GitHub-flavoured, single-newline line breaks) piped through
|
|
1044
1193
|
[DOMPurify](https://www.npmjs.com/package/dompurify). User messages stay literal text. The
|
|
1045
|
-
allowlist permits emphasis, code, lists, quotes, headings, links, tables, and
|
|
1046
|
-
are hardened with `target="_blank" rel="noopener noreferrer"`;
|
|
1047
|
-
excluded
|
|
1194
|
+
allowlist permits emphasis, code, lists, quotes, headings, links, tables, and — when `allowImages`
|
|
1195
|
+
is set — images; links are hardened with `target="_blank" rel="noopener noreferrer"`;
|
|
1196
|
+
`iframe`/`style`/scripting are excluded, as are every `data-*` and `aria-*` attribute and every
|
|
1197
|
+
`class` but a code fence's `language-*` hint, so model output cannot dress itself up as the
|
|
1198
|
+
component's own approval or tool-call chrome. The exported helper `renderMarkdown(text)` does this
|
|
1199
|
+
standalone. `marked` and `dompurify`
|
|
1048
1200
|
are runtime dependencies.
|
|
1049
1201
|
|
|
1050
1202
|
An animated 3-dot "thinking" indicator (`role="status"`, with an aria-label) appears before the
|
|
@@ -1062,6 +1214,181 @@ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
|
|
|
1062
1214
|
|
|
1063
1215
|
---
|
|
1064
1216
|
|
|
1217
|
+
## Follow-up suggestions
|
|
1218
|
+
|
|
1219
|
+
The agent can offer the *next* question, not just answer this one. Registered
|
|
1220
|
+
skill chips are static and host-configured, so they can say "summarize this" but
|
|
1221
|
+
never "want me to update the shipping address too?" after a tool has run.
|
|
1222
|
+
|
|
1223
|
+
The server pushes an `ACTIVITY_SNAPSHOT` under the `suggestions` type — with
|
|
1224
|
+
django-ag-ui, `suggestions_activity([...])`. The component draws each prompt as
|
|
1225
|
+
a chip; clicking one sends it as the user's message, exactly as if they had
|
|
1226
|
+
typed it.
|
|
1227
|
+
|
|
1228
|
+
```json
|
|
1229
|
+
{ "activityType": "suggestions",
|
|
1230
|
+
"content": { "prompts": ["Update the shipping address too"] } }
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
**Chips are content.** They ride the same activity envelope as charts, so they
|
|
1234
|
+
persist and a reload puts them back, and a set pushed under an id already on
|
|
1235
|
+
screen replaces that row rather than adding a second one.
|
|
1236
|
+
|
|
1237
|
+
At most **4** prompts of **120** characters each — `MAX_SUGGESTIONS` and
|
|
1238
|
+
`MAX_SUGGESTION_CHARS`, both exported. The server-side helper *raises* past
|
|
1239
|
+
those bounds while this side silently drops: that asymmetry is deliberate, since
|
|
1240
|
+
the producer can report the problem and the client cannot.
|
|
1241
|
+
|
|
1242
|
+
Nothing to enable. A `suggestions` activity from a server that pushes one is
|
|
1243
|
+
drawn; an `activity_type` this component does not know is ignored, which is what
|
|
1244
|
+
the open field is for.
|
|
1245
|
+
|
|
1246
|
+
## Editing a gated call before approving it
|
|
1247
|
+
|
|
1248
|
+
AG-UI's resume payload carries `editedArgs`, and the protocol gates it on the
|
|
1249
|
+
agent's own `approveWithEdits` capability. The approval card can offer it:
|
|
1250
|
+
|
|
1251
|
+
```js
|
|
1252
|
+
chat.approveWithEdits = true; // your server accepts editedArgs
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
**Off by default, and an assertion about your server rather than a
|
|
1256
|
+
negotiation** — capabilities are not on the wire this component reads, so it
|
|
1257
|
+
cannot check. Turned on against a server that ignores `editedArgs`, a user would
|
|
1258
|
+
edit arguments it silently discards, which is worse than not offering.
|
|
1259
|
+
|
|
1260
|
+
The card then shows the call's arguments as editable JSON. `editedArgs` rides
|
|
1261
|
+
the resume payload **only when something actually changed**, so a server can tell
|
|
1262
|
+
"approved as proposed" from "approved, but like this" without diffing what it
|
|
1263
|
+
already sent. Unparseable JSON, or JSON that is not an object, keeps the card
|
|
1264
|
+
open with the reason on it rather than approving the original behind the user's
|
|
1265
|
+
back.
|
|
1266
|
+
|
|
1267
|
+
Only offered for an interrupt naming a tool call this component holds a card
|
|
1268
|
+
for — the card is where the arguments still are.
|
|
1269
|
+
|
|
1270
|
+
## Localizing the timestamps
|
|
1271
|
+
|
|
1272
|
+
There is **no `Intl` anywhere in this component**. The relative timestamps in the
|
|
1273
|
+
thread drawer and checkpoint panel (`"5m ago"`, `"2d ago"`) are deliberately
|
|
1274
|
+
locale-neutral: a component that guessed a locale would disagree with the page
|
|
1275
|
+
it is embedded in, and being wrong in a second language is worse than being
|
|
1276
|
+
neutral in one.
|
|
1277
|
+
|
|
1278
|
+
That is a good default and a bad requirement, so it is replaceable:
|
|
1279
|
+
|
|
1280
|
+
```js
|
|
1281
|
+
const rtf = new Intl.RelativeTimeFormat("de", { numeric: "auto" });
|
|
1282
|
+
chat.formatRelativeTime = (ts) =>
|
|
1283
|
+
rtf.format(Math.round((ts - Date.now()) / 60000), "minute");
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
`relativeTime` is exported too, for a host that wants to build on the built-in
|
|
1287
|
+
rather than replace it.
|
|
1288
|
+
|
|
1289
|
+
## Message actions: copy, retry, feedback
|
|
1290
|
+
|
|
1291
|
+
Every finished assistant message carries a small row of actions beneath it —
|
|
1292
|
+
a **sibling** of the bubble, never a child, so the buttons never join the
|
|
1293
|
+
message's own text.
|
|
1294
|
+
|
|
1295
|
+
- **Retry** re-asks the question. History is truncated to the most recent user
|
|
1296
|
+
message inclusive and the run repeats, so the agent answers what it was asked
|
|
1297
|
+
rather than being told its last answer was wrong.
|
|
1298
|
+
- **Copy** puts the message's text on the clipboard, and says so on the button.
|
|
1299
|
+
A refused clipboard permission is reported there too, rather than thrown.
|
|
1300
|
+
- **Thumbs up / down** fire [`ag-ui-feedback`](#events) and **store nothing**.
|
|
1301
|
+
|
|
1302
|
+
Retry sits on the **last** answer only. Re-running an older turn is branching,
|
|
1303
|
+
and for a page-driving agent editing a past turn is not neutral — those turns
|
|
1304
|
+
clicked buttons, and re-running turn 3 does not un-save what turn 5 saved.
|
|
1305
|
+
|
|
1306
|
+
!!! note
|
|
1307
|
+
A retried turn **re-runs its tools.** The previous attempt already did what
|
|
1308
|
+
it did, and this does not undo it. Confirmation still applies, so a
|
|
1309
|
+
destructive tool asks again — unless the user waived it for this session
|
|
1310
|
+
with *Always allow*.
|
|
1311
|
+
|
|
1312
|
+
A failed run gets the same row, with Retry and Copy and no rating: error text is
|
|
1313
|
+
what people paste into a bug report, but "the connection dropped" is not a
|
|
1314
|
+
statement about answer quality and mixing it into feedback makes that signal say
|
|
1315
|
+
less. This is why a dropped connection is still rendered as an **error** rather
|
|
1316
|
+
than demoted to a run notice — a notice "never settles, takes no action, and
|
|
1317
|
+
carries no controls", and a failure with a way back needs one.
|
|
1318
|
+
|
|
1319
|
+
`retryLastTurn()` is public, for a host driving its own message UI.
|
|
1320
|
+
|
|
1321
|
+
```js
|
|
1322
|
+
chat.addEventListener("ag-ui-feedback", (e) => {
|
|
1323
|
+
analytics.track("assistant_rating", e.detail); // { content, rating }
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
await chat.retryLastTurn(); // false when there is nothing to ask again
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
## Quoting a selection
|
|
1330
|
+
|
|
1331
|
+
Select any text in the transcript and a small **Quote** offer floats beside it.
|
|
1332
|
+
Taking it drops the selection into the composer as a markdown blockquote and
|
|
1333
|
+
leaves the caret on a fresh line under it — a quotation is how a question
|
|
1334
|
+
narrows to one part of an answer, so nothing is sent until you say what you are
|
|
1335
|
+
asking.
|
|
1336
|
+
|
|
1337
|
+
Quoting **appends**, after whatever is already typed, so a second quotation is a
|
|
1338
|
+
second thing being asked about rather than a replacement for the first. Long
|
|
1339
|
+
selections are capped at 500 characters: select-all-then-quote is a gesture the
|
|
1340
|
+
transcript already answers, and pasting the whole conversation back costs tokens
|
|
1341
|
+
to say nothing.
|
|
1342
|
+
|
|
1343
|
+
Set `data-quote-selection="false"` to turn the offer off. The `quote-selection`
|
|
1344
|
+
`part` styles it.
|
|
1345
|
+
|
|
1346
|
+
### The half that matters: selection in **your** page
|
|
1347
|
+
|
|
1348
|
+
The transcript is the easy half. A chat mounted beside a table, a diff or a
|
|
1349
|
+
report is sitting in the surface the user actually works in — and *that*
|
|
1350
|
+
selection is one no hosted chat can reach.
|
|
1351
|
+
|
|
1352
|
+
`offerQuoteInPage()` extends the same select-then-offer gesture to the whole
|
|
1353
|
+
page. It is opt-in, because it listens on your document:
|
|
1354
|
+
|
|
1355
|
+
```js
|
|
1356
|
+
const stop = chat.offerQuoteInPage(); // the whole page
|
|
1357
|
+
chat.offerQuoteInPage(document.querySelector("#report")); // or one region
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
For a deliberate trigger instead of a selection, `quote(text)` is the seam
|
|
1361
|
+
underneath:
|
|
1362
|
+
|
|
1363
|
+
```js
|
|
1364
|
+
// "Ask about this row" — a button on each row of your own table.
|
|
1365
|
+
row.querySelector(".ask").addEventListener("click", () => {
|
|
1366
|
+
chat.quote(row.innerText);
|
|
1367
|
+
});
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
`quote()` never sends — pair it with [`sendMessage()`](#sending-from-your-own-ui)
|
|
1371
|
+
if you want a one-click "explain this" that skips the composer entirely.
|
|
1372
|
+
|
|
1373
|
+
> **Do not write the four-line version of `offerQuoteInPage()`.**
|
|
1374
|
+
> A `mouseup` listener that quotes every settled selection appends to the
|
|
1375
|
+
> composer on every drag the user made to *read*, to copy, or to fix a typo —
|
|
1376
|
+
> and it cannot tell a selection in your prose from one inside the user's own
|
|
1377
|
+
> half-typed `<input>`, because Chrome reports a field's internal selection
|
|
1378
|
+
> through `document.getSelection()` as an ordinary range over the field's
|
|
1379
|
+
> **wrapper**. The text reads back perfectly and nothing about the range says
|
|
1380
|
+
> where it came from; the only signal is `document.activeElement`. That guard,
|
|
1381
|
+
> plus skipping the widget's own transcript, plus retiring a fixed-position
|
|
1382
|
+
> affordance on scroll, is what the method is for.
|
|
1383
|
+
|
|
1384
|
+
> **Reading a selection out of a shadow tree takes care too.**
|
|
1385
|
+
> Engines disagree about what `document.getSelection()` reports for a selection
|
|
1386
|
+
> made *inside* a shadow root: WebKit rescopes the endpoints to the host element,
|
|
1387
|
+
> so you get the whole widget and none of the words, while Chromium hands back
|
|
1388
|
+
> the shadow nodes directly. `getComposedRanges` settles it, and this component
|
|
1389
|
+
> uses it where it exists. `quotableSelection(container, roots)` is exported if
|
|
1390
|
+
> you have the same problem in your own component.
|
|
1391
|
+
|
|
1065
1392
|
## Run notices: compaction and agent skills
|
|
1066
1393
|
|
|
1067
1394
|
Some things a run does are neither text nor a tool the user asked for — the server condensed
|
|
@@ -1177,6 +1504,87 @@ triggers a full reload. Before the handler navigates, the element writes a check
|
|
|
1177
1504
|
|
|
1178
1505
|
The MPA round-trip becomes a clean observation point instead of a dropped conversation.
|
|
1179
1506
|
|
|
1507
|
+
### Who the stored conversation belongs to (`user-key`)
|
|
1508
|
+
|
|
1509
|
+
`sessionStorage` is scoped to a tab, not to a session. It survives every same-tab navigation,
|
|
1510
|
+
and a logout is a navigation — so on a shared workstation, one user's transcript is still sitting
|
|
1511
|
+
there when the next user signs in and the chat mounts again. Transcripts routinely quote record
|
|
1512
|
+
data, so treat that as the default and turn it off:
|
|
1513
|
+
|
|
1514
|
+
```html
|
|
1515
|
+
<ag-ui-chat endpoint="/agent/" user-key="{{ request.user.pk }}"></ag-ui-chat>
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
The value is any string that identifies the principal — a user id, an account id, a hash of one.
|
|
1519
|
+
It joins the storage namespace, so two principals in the same tab cannot reach each other's
|
|
1520
|
+
conversation, and **changing it purges everything the previous principal stored**: transcript,
|
|
1521
|
+
history drawer index and navigation checkpoints, for this element's namespace only.
|
|
1522
|
+
|
|
1523
|
+
Set it live, from script, as part of signing out or in:
|
|
1524
|
+
|
|
1525
|
+
```js
|
|
1526
|
+
chat.userKey = String(session.userId); // or "" on sign-out
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
That is why it is a live attribute rather than a connect-time one. A single-page app signs a user
|
|
1530
|
+
out through its own router without remounting anything, so the host naming the new principal — or
|
|
1531
|
+
dropping the attribute — is the only signal the element will ever get. Removing the attribute
|
|
1532
|
+
purges too, so a sign-out that simply clears it is safe.
|
|
1533
|
+
|
|
1534
|
+
The **first** value to arrive is treated as a host naming the user who was already there, not as a
|
|
1535
|
+
handover: the conversation in progress moves into the principal's namespace instead of being
|
|
1536
|
+
destroyed. So an element configured by an async auth handshake — the shape described in
|
|
1537
|
+
[Framework hosts](#framework-hosts-configure-before-you-insert) — keeps what is on screen.
|
|
1538
|
+
|
|
1539
|
+
Two things it deliberately does not do. It does not scope the panel's own collapsed / dragged-size
|
|
1540
|
+
/ theme preferences, which are this element's UI state and carry no conversation content. And it
|
|
1541
|
+
does not encrypt or hide anything from the page: any script on the origin can still read
|
|
1542
|
+
`sessionStorage`. It scopes and it purges.
|
|
1543
|
+
|
|
1544
|
+
**Without it, nothing changes** — including the carry-over above. A conversation is scoped to the
|
|
1545
|
+
element and to nobody in particular, and on a shared workstation it will be there for whoever signs
|
|
1546
|
+
in next in the same tab.
|
|
1547
|
+
|
|
1548
|
+
For a deployment that keeps history server-side, `data-threads-cache="false"` stops the local
|
|
1549
|
+
mirror of the message bodies as well, so choosing `data-threads-url` actually keeps transcripts off
|
|
1550
|
+
the client:
|
|
1551
|
+
|
|
1552
|
+
```html
|
|
1553
|
+
<ag-ui-chat endpoint="/agent/" data-threads-url="/agent/threads/" data-threads-cache="false">
|
|
1554
|
+
</ag-ui-chat>
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
The client-only concerns (the active thread id, the navigation checkpoint) keep their local store
|
|
1558
|
+
either way, so reloads and navigating tools still work. What is lost is the offline fallback: when
|
|
1559
|
+
the thread endpoint is unreachable the transcript comes back empty rather than stale, and the
|
|
1560
|
+
drawer's offline list loses its previews — a preview being an excerpt of a message, which is the
|
|
1561
|
+
thing being kept off the client. Constructing the store yourself takes the same option:
|
|
1562
|
+
|
|
1563
|
+
```js
|
|
1564
|
+
chat.conversationStore = new RemoteConversationStore(
|
|
1565
|
+
"/agent/threads/",
|
|
1566
|
+
() => ({ "X-CSRFToken": token }),
|
|
1567
|
+
new SessionStorageStore(),
|
|
1568
|
+
() => "same-origin",
|
|
1569
|
+
false, // cacheMessages
|
|
1570
|
+
);
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
`SessionStorageStore.purge(namespace)` is the same primitive the element uses, for a host driving
|
|
1574
|
+
its own store from its own sign-out path.
|
|
1575
|
+
|
|
1576
|
+
### Mounting more than one chat on a page
|
|
1577
|
+
|
|
1578
|
+
Give each `<ag-ui-chat>` its own `id`. The storage namespace is the element's `id`, falling back to
|
|
1579
|
+
its `endpoint` — so two elements with no `id` against the same agent mount (a docked support panel
|
|
1580
|
+
and an inline page assistant, say) would resolve to the same namespace and share a thread pointer,
|
|
1581
|
+
a history drawer and every message key.
|
|
1582
|
+
|
|
1583
|
+
They no longer do: the first element to mount keeps the namespace, and a second is given a
|
|
1584
|
+
throwaway one of its own plus a console warning. That keeps the two conversations apart, but the
|
|
1585
|
+
throwaway namespace is minted per mount, so the second element will not restore its conversation
|
|
1586
|
+
across a reload until it has an `id`.
|
|
1587
|
+
|
|
1180
1588
|
---
|
|
1181
1589
|
|
|
1182
1590
|
## Host seams: the SPA story
|
|
@@ -1321,6 +1729,83 @@ substitute the other way round either, because shared state requires the *agent*
|
|
|
1321
1729
|
`STATE_SNAPSHOT`, which is not the host's decision to make. Use state when the two ends edit one
|
|
1322
1730
|
object; use this when your page owns the data and just needs to know it moved.
|
|
1323
1731
|
|
|
1732
|
+
**`ag-ui-custom`** *(event)* — the agent sent an AG-UI `CUSTOM` event.
|
|
1733
|
+
`detail: { name, value }` (typed `CustomAgentDetail`), both verbatim and uninterpreted.
|
|
1734
|
+
|
|
1735
|
+
```js
|
|
1736
|
+
chat.addEventListener("ag-ui-custom", (e) => {
|
|
1737
|
+
if (e.detail.name === "invalidate") {
|
|
1738
|
+
void refetch(e.detail.value);
|
|
1739
|
+
}
|
|
1740
|
+
// Any other name: no listener, nothing happens. That is the intended outcome.
|
|
1741
|
+
});
|
|
1742
|
+
```
|
|
1743
|
+
|
|
1744
|
+
`CUSTOM` is one of exactly two AG-UI carriers whose payload name is an open
|
|
1745
|
+
string the protocol does not enumerate, and it is the **imperative** one:
|
|
1746
|
+
something for your page to *do*. Its sibling `ACTIVITY_SNAPSHOT` carries
|
|
1747
|
+
transcript **content**, which is why an activity is materialised into a message,
|
|
1748
|
+
persisted with the thread and replayed on restore, and this is not.
|
|
1749
|
+
|
|
1750
|
+
**That asymmetry is the rule for choosing between them.** Content has a place in
|
|
1751
|
+
the conversation and should replay. An imperative has no place and no meaning
|
|
1752
|
+
once acted on — replaying "refetch the board" on every thread load is a bug, not
|
|
1753
|
+
a feature. If it must survive a reload, it belongs on the other carrier.
|
|
1754
|
+
|
|
1755
|
+
The element takes no view of what a name means and forwards every one, so a name
|
|
1756
|
+
it has never heard of reaches you unchanged. A host with no listener for a name
|
|
1757
|
+
simply ignores it, which is the graceful outcome an open field exists for.
|
|
1758
|
+
|
|
1759
|
+
**`ag-ui-invalidate`** *(event)* — the agent named resources its write moved.
|
|
1760
|
+
`detail: { keys, reason }` (typed `InvalidateDetail`).
|
|
1761
|
+
|
|
1762
|
+
One `CUSTOM` name routed to its own event so you do not have to string-match;
|
|
1763
|
+
every other name still arrives as `ag-ui-custom`. It fires **as each
|
|
1764
|
+
announcement arrives**, during the run, and the same keys ride
|
|
1765
|
+
`ag-ui-run-finished` again at the end as `invalidated`, de-duplicated.
|
|
1766
|
+
|
|
1767
|
+
> **Do not reload the page on this.** The user was probably typing. An
|
|
1768
|
+
> agent-triggered reload or a blind refetch into a live form destroys unsaved
|
|
1769
|
+
> input, and from their side the page threw their work away on its own. Check
|
|
1770
|
+
> first, and offer rather than act:
|
|
1771
|
+
|
|
1772
|
+
```js
|
|
1773
|
+
chat.addEventListener("ag-ui-invalidate", (e) => {
|
|
1774
|
+
if (formIsDirty()) {
|
|
1775
|
+
showBanner("This data changed. Refresh when you're ready.", e.detail.keys);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
refetch(e.detail.keys); // e.detail.keys → ["orders", "orders/42"]
|
|
1779
|
+
});
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
**Keys are opaque and matching is exact.** `orders/42` does not imply `orders` —
|
|
1783
|
+
a prefix rule would be the component guessing at a scheme it does not own, and
|
|
1784
|
+
`orders/1` would match `orders/11`. A server that wants the collection refreshed
|
|
1785
|
+
names it. Your own matching may be hierarchical, because in your vocabulary the
|
|
1786
|
+
scheme is known; that is what TanStack query keys are built for.
|
|
1787
|
+
|
|
1788
|
+
Already listening on `ag-ui-run-finished`? Upgrading is one line, and the `else`
|
|
1789
|
+
is the whole compatibility story:
|
|
1790
|
+
|
|
1791
|
+
```js
|
|
1792
|
+
if (detail.invalidated.length > 0) refetchOnly(detail.invalidated);
|
|
1793
|
+
else if (detail.tools.some((t) => t.side === "server")) refetchEverything();
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
| Server | Client | Result |
|
|
1797
|
+
| --- | --- | --- |
|
|
1798
|
+
| old | old | coarse refetch, as today |
|
|
1799
|
+
| new | old | the `CUSTOM` event is ignored; coarse refetch still fires |
|
|
1800
|
+
| old | new | `invalidated` is empty; the `else` branch runs |
|
|
1801
|
+
| new | new | precise, and live during the run |
|
|
1802
|
+
|
|
1803
|
+
Nothing negotiates and nothing handshakes, which is what makes this shippable
|
|
1804
|
+
across repos with independent release cadences.
|
|
1805
|
+
|
|
1806
|
+
**Note:** it reaches **the page that started the run**, during the run. There is one
|
|
1807
|
+
response stream per run and no channel to anybody else's browser.
|
|
1808
|
+
|
|
1324
1809
|
## Resuming a run
|
|
1325
1810
|
|
|
1326
1811
|
When the server persists run checkpoints (django-ag-ui's `step_store`), a run
|
|
@@ -1452,6 +1937,8 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1452
1937
|
| `SubmitDetail` | type | `detail` shape of the submit event. |
|
|
1453
1938
|
| `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
|
|
1454
1939
|
| `UnreadDetail` | type | `detail` shape of the `ag-ui-unread` event (`{ unread }`). |
|
|
1940
|
+
| `AttachmentsDetail` | type | `detail` shape of the `ag-ui-attachments` event (`{ attachments, pending }`). |
|
|
1941
|
+
| `StateDetail` | type | `detail` shape of the `ag-ui-state` event (`{ state }`). |
|
|
1455
1942
|
|
|
1456
1943
|
### AG-UI client & agent
|
|
1457
1944
|
|
|
@@ -1463,6 +1950,8 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1463
1950
|
| `ConnectionLostError` | class | Raised (→ `onError`) when a run's stream closes with no terminal AG-UI event. |
|
|
1464
1951
|
| `createHttpAgent(options)` | function | Default agent factory (wraps `HttpAgent`). |
|
|
1465
1952
|
| `AgentFactory` / `HttpAgentOptions` | type | Factory signature and its options. |
|
|
1953
|
+
| `ResolveInterrupts` | type | Resolver for server-side-tool approval interrupts (one decision per interrupt). |
|
|
1954
|
+
| `InterruptResponse` | type | One interrupt's answer: `resolved` (with an optional payload) or `cancelled`. |
|
|
1466
1955
|
|
|
1467
1956
|
### Tools & flags
|
|
1468
1957
|
|
|
@@ -1476,6 +1965,9 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1476
1965
|
| `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
|
|
1477
1966
|
| `ResolvePageTarget` | type | `(target) => HTMLElement | null` — the page-target resolver. |
|
|
1478
1967
|
| `X_DESTRUCTIVE_KEY` / `X_NAVIGATES_KEY` | const | The JSON-Schema extension keys. |
|
|
1968
|
+
| `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `name` → `summary` map. |
|
|
1969
|
+
| `ToolCatalogEntry` | type | One row of that catalog. |
|
|
1970
|
+
| `prettifyToolName(name)` | function | Last fallback of the tool-card label chain (`delete_record` reads as *Delete record*). |
|
|
1479
1971
|
|
|
1480
1972
|
### Host seams
|
|
1481
1973
|
|
|
@@ -1490,6 +1982,19 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1490
1982
|
| `PageState` | type | A page-state binding declaration. |
|
|
1491
1983
|
| `Skill` | type | A launchable prompt (chip / `/`-command). |
|
|
1492
1984
|
| `RunFinishedDetail` / `ToolRun` | type | `ag-ui-run-finished` detail: the tools an interaction ran, and which side ran them. |
|
|
1985
|
+
| `CustomAgentDetail` | type | `ag-ui-custom` detail: an AG-UI `CUSTOM` event's `name` and `value`, verbatim. |
|
|
1986
|
+
| `InvalidateDetail` | type | `ag-ui-invalidate` detail: the resource `keys` that moved, and the `reason`. |
|
|
1987
|
+
| `FeedbackDetail` | type | `ag-ui-feedback` detail: the rated message's `content` and the `rating`. |
|
|
1988
|
+
| `relativeTime` | function | The built-in locale-neutral timestamp formatter (`"5m ago"`), for a host building on it. |
|
|
1989
|
+
| `RelativeTimeFormatter` | type | What `formatRelativeTime` takes: an epoch-ms timestamp in, row text out. |
|
|
1990
|
+
| `renderSuggestionChips` | function | Draw a `suggestions` activity as chips that send themselves; `null` when nothing survives. |
|
|
1991
|
+
| `suggestionPrompts` | function | The usable prompts in a `suggestions` activity's content, bounded and trimmed. |
|
|
1992
|
+
| `attachMessageActions` | function | Give a finished bubble its action row (copy, and feedback when a handler is passed). |
|
|
1993
|
+
| `messageActionBar` | function | The empty action row for a bubble, created if it has none — the shared shell both callers use. |
|
|
1994
|
+
| `MessageActionsOptions` | type | What `attachMessageActions` takes: `strings`, a `text` source, an optional `onFeedback`. |
|
|
1995
|
+
| `ActivityRenderer` | type | Draws one activity from its `content`. Pure: it runs again on every restore. |
|
|
1996
|
+
| `ActivityRegistration` | type | What `registerActivityRenderer` takes: `type`, `render`, and an optional `removedNotice`. |
|
|
1997
|
+
| `createStateHookTools(binding)` / `StateHook` | deprecated | The former names for `createPageStateTools` / `PageState`. |
|
|
1493
1998
|
|
|
1494
1999
|
### Durability
|
|
1495
2000
|
|
|
@@ -1515,6 +2020,14 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1515
2020
|
| `AttachmentRef` | type | The durable upload ref (`{ id, name, mime, size, url? }`). |
|
|
1516
2021
|
| `messageAttachments(message)` | function | Read the refs a restored user message carries. |
|
|
1517
2022
|
|
|
2023
|
+
### Voice input
|
|
2024
|
+
|
|
2025
|
+
| Export | Kind | Summary |
|
|
2026
|
+
| --- | --- | --- |
|
|
2027
|
+
| `transcribeAudio(audio, options)` | function | The built-in transcription POST (multipart) → the transcript text. |
|
|
2028
|
+
| `TranscribeOptions` | type | `{ url, headers? }`. |
|
|
2029
|
+
| `TranscribeHandler` | type | `(audio) => Promise<string>` — the `transcribeHandler` swap seam (Web Speech, direct-to-provider). |
|
|
2030
|
+
|
|
1518
2031
|
### UI & DOM primitives
|
|
1519
2032
|
|
|
1520
2033
|
| Export | Kind | Summary |
|
|
@@ -1527,7 +2040,25 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1527
2040
|
| `UiStrings` | type | The flat table of every user-facing string. |
|
|
1528
2041
|
| `DEFAULT_UI_STRINGS` | const | The English defaults (the override floor). |
|
|
1529
2042
|
| `mergeUiStrings(overrides)` | function | Merge a partial override over the defaults. |
|
|
1530
|
-
| `renderMarkdown(text)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
|
|
2043
|
+
| `renderMarkdown(text, options?)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
|
|
2044
|
+
| `RenderMarkdownOptions` | type | `{ allowImages? }` — opt `<img>` back into the sanitized output. |
|
|
2045
|
+
| `requestApproval(host, request, options?)` | function | Append the inline approval card that gates a server-side tool. |
|
|
2046
|
+
| `ApprovalRequest` | type | What that card displays (`{ message?, toolName? }`). |
|
|
2047
|
+
| `ApprovalOptions` | type | `{ signal?, strings? }` — abort resolves the card as denied; `strings` localizes it. |
|
|
2048
|
+
| `ApprovalRenderer` | type | Replace the built-in approval card outright (`AgUiChat.approvalRenderer`). |
|
|
2049
|
+
| `requestQuestion(host, request, options?)` | function | Append the inline `ask_user` card (radios and/or free text). |
|
|
2050
|
+
| `QuestionRequest` | type | What that card asks. |
|
|
2051
|
+
| `QuestionOptions` | type | `{ signal?, strings? }` — abort resolves it with an empty answer. |
|
|
2052
|
+
| `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.questionRenderer`). |
|
|
2053
|
+
| `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
|
|
2054
|
+
| `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
|
|
2055
|
+
| `ChartSpec` / `ChartSeries` / `ChartKind` | type | A chart as data, one named series, and how it is drawn. |
|
|
2056
|
+
| `attachQuoteOffer(options)` | function | The page-side select-then-quote offer, with its guards. `AgUiChat.offerQuoteInPage()` is the one-line form. |
|
|
2057
|
+
| `PageQuoteOffer` / `PageQuoteOfferOptions` | type | The live offer (`{ element, detach }`) and what it takes. |
|
|
2058
|
+
| `quotableSelection(container, roots)` | function | The current selection when it lies inside `container`, read through the shadow-aware API where the engine has one. |
|
|
2059
|
+
| `QuotableSelection` | type | `{ text, rect }` — what was selected, and where it sits. |
|
|
2060
|
+
| `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
|
|
2061
|
+
| `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
|
|
1531
2062
|
| `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
|
|
1532
2063
|
| `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
|
|
1533
2064
|
| `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
|
|
@@ -1542,6 +2073,19 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1542
2073
|
| `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
|
|
1543
2074
|
| `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
|
|
1544
2075
|
| `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
|
|
2076
|
+
| `CUSTOM_AGENT_EVENT` | The agent-`CUSTOM` CustomEvent name (`ag-ui-custom`). |
|
|
2077
|
+
| `INVALIDATE_EVENT` | The resource-invalidation CustomEvent name (`ag-ui-invalidate`). |
|
|
2078
|
+
| `FEEDBACK_EVENT` | The message-rating CustomEvent name (`ag-ui-feedback`). |
|
|
2079
|
+
| `SUGGESTIONS_ACTIVITY_TYPE` | The `activity_type` carrying follow-up prompts (`suggestions`). |
|
|
2080
|
+
| `MAX_SUGGESTIONS` | Most prompts one push draws (4). Mirrors the server's cap. |
|
|
2081
|
+
| `MAX_SUGGESTION_CHARS` | Longest one prompt may be (120). Mirrors the server's cap. |
|
|
2082
|
+
| `INVALIDATE_CUSTOM_NAME` | The AG-UI `CUSTOM` `name` that carries one (`ag_ui.invalidate`). |
|
|
2083
|
+
| `ATTACHMENT_EVENT` | The attachments-changed CustomEvent name (`ag-ui-attachments`). |
|
|
2084
|
+
| `STATE_EVENT` | The shared-state CustomEvent name (`ag-ui-state`). |
|
|
2085
|
+
| `CHART_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type a server sets to push a chart. |
|
|
2086
|
+
| `CHART_TOOL_NAME` | The name the built-in chart tool registers under (`render_chart`). |
|
|
2087
|
+
| `COMPACTION_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type reporting a trimmed history. |
|
|
2088
|
+
| `LOAD_CAPABILITY_TOOL` | The agent-side capability-loading tool's name. |
|
|
1545
2089
|
| `MESSAGE_ROLE` | Message role constants. |
|
|
1546
2090
|
| `TOOL_CALL_STATUS` | Tool-call card status constants. |
|
|
1547
2091
|
| `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
|
|
@@ -1675,15 +2219,17 @@ component sets, so a new one cannot ship undocumented.
|
|
|
1675
2219
|
|
|
1676
2220
|
| Feature | Parts |
|
|
1677
2221
|
| --- | --- |
|
|
1678
|
-
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `resize-handle` |
|
|
2222
|
+
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `jump-latest`, `resize-handle` |
|
|
1679
2223
|
| Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
|
|
1680
2224
|
| Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
|
|
1681
2225
|
| Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
|
|
1682
2226
|
| Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
|
|
1683
|
-
|
|
|
2227
|
+
| Follow-up suggestions | `suggestions`, `suggestion-chip` |
|
|
2228
|
+
| Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`) |
|
|
2229
|
+
| Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`, `run-notice-history-replaced`, `run-notice-chart-undrawable`), `run-notice-icon`, `run-notice-text` |
|
|
1684
2230
|
| Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval` |
|
|
1685
|
-
| Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`) |
|
|
1686
|
-
| Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`) |
|
|
2231
|
+
| Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
|
|
2232
|
+
| Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
|
|
1687
2233
|
| Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
|
|
1688
2234
|
| Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
|
|
1689
2235
|
| Attachments | `attachment-tray`, `attachment-chips` (the read-only chips on sent bubbles), and the shared chip parts `attachment-chip`, `attachment-chip-icon`, `attachment-chip-name`, `attachment-chip-size`, `attachment-chip-bar`, `attachment-chip-bar-fill`, `attachment-chip-retry`, `attachment-chip-remove` |
|