@artooi/ag-ui-web-component 0.28.0 → 0.30.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 +615 -1
- package/README.md +564 -35
- package/dist/ag-ui-web-component.bundle.js +491 -50
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +129 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +232 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +56 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2081 -98
- package/dist/index.js.map +4 -4
- 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 +56 -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/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/subagent_panel.d.ts +92 -0
- package/dist/ui/subagent_panel.d.ts.map +1 -0
- package/dist/ui/subagent_update.d.ts +19 -0
- package/dist/ui/subagent_update.d.ts.map +1 -0
- 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 +81 -1
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +50 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +138 -1
- package/src/core/ag_ui_chat.ts +1081 -73
- package/src/core/agui_client.ts +89 -2
- package/src/index.ts +43 -0
- 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 +170 -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/stick_to_bottom.ts +126 -0
- package/src/ui/styles.ts +410 -0
- package/src/ui/subagent_panel.ts +213 -0
- package/src/ui/subagent_update.ts +80 -0
- package/src/ui/suggestion_chips.ts +73 -0
- package/src/ui/thread_drawer.ts +22 -2
- package/src/ui/tool_call_card.ts +138 -3
- package/src/ui/ui_strings.ts +75 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,13 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
|
|
|
49
49
|
- [New chat and collapse](#new-chat-and-collapse)
|
|
50
50
|
- [Collapsing to the launcher](#collapsing-to-the-launcher)
|
|
51
51
|
- [Tool-call display modes](#tool-call-display-modes)
|
|
52
|
+
- [Delegated sub-agents](#delegated-sub-agents)
|
|
52
53
|
- [Markdown rendering](#markdown-rendering)
|
|
54
|
+
- [Follow-up suggestions](#follow-up-suggestions)
|
|
55
|
+
- [Editing a gated call before approving it](#editing-a-gated-call-before-approving-it)
|
|
56
|
+
- [Localizing the timestamps](#localizing-the-timestamps)
|
|
57
|
+
- [Message actions: copy, retry, feedback](#message-actions-copy-retry-feedback)
|
|
58
|
+
- [Quoting a selection](#quoting-a-selection)
|
|
53
59
|
- [Run notices: compaction and agent skills](#run-notices-compaction-and-agent-skills)
|
|
54
60
|
- [Skills: prompt chips and slash palette](#skills-prompt-chips-and-slash-palette)
|
|
55
61
|
- [MPA durability: surviving full page reloads](#mpa-durability-surviving-full-page-reloads)
|
|
@@ -156,7 +162,7 @@ another origin, add `credentials="include"` too; see
|
|
|
156
162
|
| --- | --- | --- |
|
|
157
163
|
| `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send. Reflecting getter + setter. |
|
|
158
164
|
| `credentials` | `credentials` | Cookie policy for every request the element makes: `omit` / `same-origin` / `include`. Unset means the browser default (`same-origin`), which sends no cookies cross-origin. See [Authenticating requests](#authenticating-requests). |
|
|
159
|
-
| `title-text` | — | Header label; defaults to `"Assistant"`.
|
|
165
|
+
| `title-text` | — | Header label; defaults to `"Assistant"`. Live: writing it after the element connects re-labels the header. See [When each attribute is read](#when-each-attribute-is-read). |
|
|
160
166
|
| `data-tool-display` | `toolDisplay` | Tool-call card detail: `inline` / `minimal` / `compact` / `full` (default `full`). |
|
|
161
167
|
| `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
|
|
162
168
|
| `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
|
|
@@ -177,6 +183,16 @@ another origin, add `credentials="include"` too; see
|
|
|
177
183
|
| `data-icon-url` | — | Header (and launcher) icon image URL. A slotted `slot="icon"` wins; see [Header & launcher icon](#header-and-launcher-icon). |
|
|
178
184
|
| `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. |
|
|
179
185
|
| `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). |
|
|
186
|
+
| `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). |
|
|
187
|
+
| `data-message-actions` | — | **All on by default.** A comma list of the actions a finished answer keeps: `copy` / `retry` / `feedback` (e.g. `"copy,retry"`). `="false"` removes the row entirely. See [Message actions](#message-actions-copy-retry-feedback). |
|
|
188
|
+
| `data-max-tool-rounds` | — | Upper bound on frontend tool-call → re-run rounds within one send (default 10; a value below 1 is ignored). Raise it for a page-driving agent whose turn takes many small steps. See [The run loop](#the-run-loop-and-the-ag-ui-client). |
|
|
189
|
+
| `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
|
|
190
|
+
| `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
|
|
191
|
+
| `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
|
|
192
|
+
| `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
|
|
193
|
+
| `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
|
|
194
|
+
| `density` | — | CSS-only: `comfortable` (default) / `compact`. |
|
|
195
|
+
| `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
|
|
180
196
|
|
|
181
197
|
Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
|
|
182
198
|
`icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
|
|
@@ -188,19 +204,39 @@ same way: `icon-send`, `icon-stop`, `icon-attach`, `icon-voice`.
|
|
|
188
204
|
<svg slot="icon-new" width="16" height="16"><!-- ... --></svg>
|
|
189
205
|
</ag-ui-chat>
|
|
190
206
|
```
|
|
191
|
-
| `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
|
|
192
|
-
| `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
|
|
193
|
-
| `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
|
|
194
|
-
| `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
|
|
195
|
-
| `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
|
|
196
|
-
| `density` | — | CSS-only: `comfortable` (default) / `compact`. |
|
|
197
|
-
| `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
#### When each attribute is read
|
|
209
|
+
|
|
210
|
+
The element observes two groups of attributes, and they behave differently once it is in the DOM.
|
|
211
|
+
Nothing outside those groups is observed: a CSS-only attribute (`theme`, `density`, `data-side`,
|
|
212
|
+
`data-answer-well`) is read by the stylesheet rather than by script, and `endpoint`,
|
|
213
|
+
`data-tool-display`, `data-text-animation`, `data-runs-url`, `data-page-actions`,
|
|
214
|
+
`data-message-actions`, `data-max-tool-rounds`, `data-unread-badge` and `data-quote-selection` are
|
|
215
|
+
re-read at each use, so a late write to any of those simply takes effect. The one attribute in
|
|
216
|
+
neither camp is `data-launcher-icon-url`: it is read while the element connects, like the group
|
|
217
|
+
below, but is not observed, so a late write is inert and says nothing.
|
|
218
|
+
|
|
219
|
+
**Live attributes.** Written at any time, before or after the element connects, and acted on
|
|
220
|
+
either way: `title-text`, `placement`, `credentials`, `user-key`.
|
|
221
|
+
|
|
222
|
+
**Connect-time attributes.** Read once, while the element connects, to decide what chrome exists at
|
|
223
|
+
all — the tray, the mic, the skills menu, the header mark. Writing one afterwards has **no effect**;
|
|
224
|
+
the element logs a console warning naming the attribute rather than failing silently, because the
|
|
225
|
+
symptom is an affordance that never appears and that reads as a broken component. Set them before
|
|
226
|
+
the element enters the DOM, or remove and re-insert it. See
|
|
227
|
+
[Framework hosts](#framework-hosts-configure-before-you-insert), where the boundary bites hardest.
|
|
228
|
+
The list: `data-attachments-url`, `data-attachment-accept`, `data-attachment-max-bytes`,
|
|
229
|
+
`data-transcribe-url`, `data-threads-url`, `data-threads-cache`, `data-tools-url`,
|
|
230
|
+
`data-skills-url`, `data-skills`, `data-prompt-chips`, `data-slash-commands`, `data-theme-toggle`,
|
|
231
|
+
`data-strings`, `data-icon-url`.
|
|
232
|
+
|
|
233
|
+
**Properties** (JS only, not attributes): `headers`, `getHeaders`, `trustedOrigins`, `allowImages`,
|
|
234
|
+
`autoConfirm`, `confirmPredicate`, `askUser`, `askUserRenderer`, `approvalRenderer`,
|
|
235
|
+
`approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
|
|
236
|
+
`autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
|
|
237
|
+
`skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
|
|
238
|
+
`resolvePageTarget`, `sharedState`, plus the read-only `unread` and `unhandledActivityTypes`, and
|
|
239
|
+
the attribute mirrors `endpoint` / `userKey` / `toolDisplay` / `collapsed` / `credentials`.
|
|
204
240
|
|
|
205
241
|
`headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
|
|
206
242
|
run; `getHeaders` is the one to use for a credential that rotates. See
|
|
@@ -220,15 +256,20 @@ the django-ag-ui `@tool` registry), whose schema never reaches the browser — e
|
|
|
220
256
|
labels are fetched automatically — per card, `x-summary` → an explicit
|
|
221
257
|
`toolSummaries` entry → the fetched catalog → the raw name.
|
|
222
258
|
|
|
223
|
-
|
|
259
|
+
`sharedState` is AG-UI shared state, documented under
|
|
260
|
+
[the run loop](#the-run-loop-and-the-ag-ui-client); `unread` and `unhandledActivityTypes` are
|
|
261
|
+
read-only counters, covered under [the unread badge](#the-unread-badge) and
|
|
262
|
+
[finding out what arrived](#finding-out-what-arrived).
|
|
224
263
|
|
|
225
264
|
Code blocks in an agent's answer carry a **copy button**, revealed on hover or
|
|
226
265
|
keyboard focus and styleable via the `code-copy` part. Override its labels with
|
|
227
266
|
the `copyCode` / `copied` / `copyFailed` strings.
|
|
228
267
|
|
|
229
|
-
**Methods**: `registerTool`, `registerPageState`, `
|
|
230
|
-
`
|
|
231
|
-
`
|
|
268
|
+
**Methods**: `registerTool`, `registerPageState`, `registerActivityRenderer`, `setSkills`,
|
|
269
|
+
`sendMessage`, `attachFile`, `appendMessage`, `retryLastTurn`, `quote`, `offerQuoteInPage`,
|
|
270
|
+
`enableCharts`, `newChat`, `setCollapsed`, `toggleCollapsed`, `toggleTheme`, `openThreads`,
|
|
271
|
+
`openCheckpoints`, `closeCheckpoints`, `toggleCheckpoints`, `reload`, and the deprecated
|
|
272
|
+
`registerStateHook` (renamed to `registerPageState`).
|
|
232
273
|
|
|
233
274
|
### Sending from your own UI
|
|
234
275
|
|
|
@@ -480,7 +521,12 @@ an AG-UI `AbstractAgent`. On the first send the element builds a client (via the
|
|
|
480
521
|
render into a bubble; each `TOOL_CALL_END` becomes a tool-call card.
|
|
481
522
|
3. Any **frontend** tool calls collected during the run are executed locally, their results are
|
|
482
523
|
appended as `tool` messages, and the agent is re-run with the results.
|
|
483
|
-
4. This repeats until the agent stops calling frontend tools, bounded by `MAX_TOOL_ROUNDS
|
|
524
|
+
4. This repeats until the agent stops calling frontend tools, bounded by `MAX_TOOL_ROUNDS`
|
|
525
|
+
(10) — raise it with `data-max-tool-rounds`, or `AgUiClientConfig.maxToolRounds` when you
|
|
526
|
+
drive the client yourself. The default suits a chat whose tools answer questions; a
|
|
527
|
+
page-driving deployment reaches it legitimately, one round per field filled, and the symptom
|
|
528
|
+
is not an error but an answer that stops mid-task. A value below 1 is ignored rather than
|
|
529
|
+
honoured — it would be a send that never runs the agent at all.
|
|
484
530
|
|
|
485
531
|
Tool calls the client doesn't own (server-side tools the server already executed) are left alone —
|
|
486
532
|
the loop doesn't re-run them, but their streamed `TOOL_CALL_RESULT` is rendered into the tool-call
|
|
@@ -561,6 +607,8 @@ transcript as a resolved record after the decision:
|
|
|
561
607
|
- **Confirm** → the handler runs and the result is posted back.
|
|
562
608
|
- **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
|
|
563
609
|
next turn.
|
|
610
|
+
- **Always allow** → the handler runs *and* this tool stops prompting for the rest of the
|
|
611
|
+
session. See below for when this button appears.
|
|
564
612
|
|
|
565
613
|
Whether a call is gated is decided in this order:
|
|
566
614
|
|
|
@@ -568,9 +616,27 @@ Whether a call is gated is decided in this order:
|
|
|
568
616
|
2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
|
|
569
617
|
name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
|
|
570
618
|
for others, which a static flag can't express).
|
|
571
|
-
3. Else the
|
|
619
|
+
3. Else if the user has waived this tool name for the session, the call runs.
|
|
620
|
+
4. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
|
|
572
621
|
which reads the `x-destructive` JSON-Schema flag.
|
|
573
622
|
|
|
623
|
+
#### "Always allow", and why only sometimes
|
|
624
|
+
|
|
625
|
+
A prompt that is approved nearly every time is not a decision, it is a speed bump — and the
|
|
626
|
+
reflex it trains is what makes the rare refusal easy to miss. Anthropic published that users
|
|
627
|
+
approve **~93%** of Claude Code permission prompts manually and called interactive confirmation
|
|
628
|
+
*"behaviorally unreliable as a sole safety mechanism"* on that basis. The waiver exists so the
|
|
629
|
+
prompts that remain still mean something.
|
|
630
|
+
|
|
631
|
+
**The button is offered only on cards raised by step 4** — the `x-destructive` default. Where
|
|
632
|
+
`confirmPredicate` is what gated the call, there is no button, because that predicate is
|
|
633
|
+
documented as authoritative and letting one click retire it would silently defeat a host policy.
|
|
634
|
+
The offer and the allowlist sit on the same path, so there is no dead button either.
|
|
635
|
+
|
|
636
|
+
The waiver is **per tool name and per element**, held in memory and never persisted. A session
|
|
637
|
+
decision that outlived the tab would be a permanent grant made by one click — which is what
|
|
638
|
+
`autoConfirm` already exists to say deliberately. It is cleared when the element goes away.
|
|
639
|
+
|
|
574
640
|
AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
|
|
575
641
|
**schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
|
|
576
642
|
constant). There is no parallel metadata channel and no name heuristic — destructiveness is exactly
|
|
@@ -1009,6 +1075,67 @@ it runs again every time the conversation is restored.
|
|
|
1009
1075
|
|
|
1010
1076
|
---
|
|
1011
1077
|
|
|
1078
|
+
## Drawing other things the server pushes
|
|
1079
|
+
|
|
1080
|
+
`activityType` is an open string the protocol does not enumerate — `"chart"` is
|
|
1081
|
+
just the one the component ships a renderer for. Register your own and the
|
|
1082
|
+
server can push anything it likes into the transcript:
|
|
1083
|
+
|
|
1084
|
+
```js
|
|
1085
|
+
chat.registerActivityRenderer({
|
|
1086
|
+
type: "build_status",
|
|
1087
|
+
render: (content) => {
|
|
1088
|
+
const el = document.createElement("div");
|
|
1089
|
+
el.className = "build";
|
|
1090
|
+
el.textContent = `Build ${content.status}`;
|
|
1091
|
+
return el; // return null for content not worth drawing
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
**`render` runs again on every thread load**, so it carries the same contract as
|
|
1097
|
+
a client tool's `render`: a pure function of `content`, deterministic, and free
|
|
1098
|
+
of effects outside the node it returns. Activities are materialised into
|
|
1099
|
+
`role: "activity"` messages and persisted with the transcript, so a renderer that
|
|
1100
|
+
writes to the page instead of returning DOM fires again on every restore.
|
|
1101
|
+
|
|
1102
|
+
The component places what you return, keyed by the activity's `messageId`, so a
|
|
1103
|
+
server repeating an id **replaces** your node rather than adding a second one —
|
|
1104
|
+
the same in-place update charts get. Returning `null` removes whatever was there:
|
|
1105
|
+
live and reload should agree, and the stored content is the version that could
|
|
1106
|
+
not be drawn.
|
|
1107
|
+
|
|
1108
|
+
`chart` and `compaction` are registrations exactly like yours, not privileged
|
|
1109
|
+
branches, so registering either name **replaces the built-in**.
|
|
1110
|
+
|
|
1111
|
+
### Which carrier should the server use?
|
|
1112
|
+
|
|
1113
|
+
AG-UI leaves exactly two payload names open, and they are not
|
|
1114
|
+
interchangeable:
|
|
1115
|
+
|
|
1116
|
+
| | Carrier | Reaches | Persisted | Replayed |
|
|
1117
|
+
| --- | --- | --- | --- | --- |
|
|
1118
|
+
| **Content** | `ACTIVITY_SNAPSHOT` | the transcript | yes | yes |
|
|
1119
|
+
| **Imperative** | `CUSTOM` | your page, as [`ag-ui-custom`](#host-seams-the-spa-story) | no | no |
|
|
1120
|
+
|
|
1121
|
+
⇒ **Content has a place in the conversation and should come back. An imperative
|
|
1122
|
+
has no place and no meaning once acted on** — replaying "refetch the board" on
|
|
1123
|
+
every thread load is a bug, not a feature. If it has to survive a reload, it is
|
|
1124
|
+
content.
|
|
1125
|
+
|
|
1126
|
+
### Finding out what arrived
|
|
1127
|
+
|
|
1128
|
+
An activity nobody registered for draws nothing and logs nothing — that is the
|
|
1129
|
+
protocol's own answer, and warning would fire on every forward-compatible
|
|
1130
|
+
server. But silence is hard to debug, so the names are readable:
|
|
1131
|
+
|
|
1132
|
+
```js
|
|
1133
|
+
chat.unhandledActivityTypes; // ["pydantic_ai_thinking", …]
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
Note `"chart"` appears there until you call `enableCharts(["activity"])`, which
|
|
1137
|
+
is the honest answer to "I pushed a chart and nothing happened".
|
|
1138
|
+
|
|
1012
1139
|
## Tool-call display modes
|
|
1013
1140
|
|
|
1014
1141
|
How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
|
|
@@ -1031,6 +1158,42 @@ visible. Style them via the `tool-card-args` / `tool-card-result` parts, their h
|
|
|
1031
1158
|
transcript, the way `data-answer-well` does — the modes are pure visibility over one DOM shape,
|
|
1032
1159
|
selected by the shadow CSS from the host attribute.
|
|
1033
1160
|
|
|
1161
|
+
### Drawing a card's body yourself
|
|
1162
|
+
|
|
1163
|
+
A thirty-field result is a wall of JSON where a host wanted a table, or a
|
|
1164
|
+
sentence. `formatToolPayload` is the seam: it is asked about each region of each
|
|
1165
|
+
card and may return a `Node` to take it over, a `string` to replace its text, or
|
|
1166
|
+
`null` to leave the built-in pretty-print alone.
|
|
1167
|
+
|
|
1168
|
+
```js
|
|
1169
|
+
chat.formatToolPayload = (payload) => {
|
|
1170
|
+
if (payload.kind !== "result" || payload.toolName !== "list_orders") {
|
|
1171
|
+
return null; // everything else keeps the default rendering
|
|
1172
|
+
}
|
|
1173
|
+
const table = document.createElement("table");
|
|
1174
|
+
// ... build it from JSON.parse(payload.text)
|
|
1175
|
+
return table;
|
|
1176
|
+
};
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
Both halves come through the same hook, told apart by `kind`: `arguments`
|
|
1180
|
+
carries the parsed record the call was made with, `result` the raw string the
|
|
1181
|
+
tool returned plus the outcome it settled on. A region a formatter took over is
|
|
1182
|
+
marked `data-formatted`, which relaxes the preformatted whitespace the default
|
|
1183
|
+
JSON block relies on — a table would otherwise inherit it as mangled cell
|
|
1184
|
+
spacing. Whitespace only: the card's face, frame and scroll cap stay, so one long
|
|
1185
|
+
payload still cannot stretch the transcript, and a host wanting different
|
|
1186
|
+
typography restyles the `tool-card-result` part.
|
|
1187
|
+
|
|
1188
|
+
This is **presentation, not translation.** The card and the model already read
|
|
1189
|
+
separate copies of a tool result — the model's is maintained by `@ag-ui/client`
|
|
1190
|
+
from the same event — so a formatter changes what the person reads and nothing
|
|
1191
|
+
the agent reads. That is what makes restyling safe here, and it is also why
|
|
1192
|
+
*rewording* belongs on the server: renamed there, the new wording reaches the
|
|
1193
|
+
model's prose too, instead of leaving the card disagreeing with the answer beside
|
|
1194
|
+
it. A returned string is set as text, never parsed as markup — this is not a
|
|
1195
|
+
second HTML channel into the transcript.
|
|
1196
|
+
|
|
1034
1197
|
A gated call carries the decision (`approved by you` / `declined by you`, part
|
|
1035
1198
|
`tool-card-decision`, attribute `data-decision`) — from the client-side confirmation card and
|
|
1036
1199
|
from the server-side approval interrupt alike. The prompt itself disappears once answered: a
|
|
@@ -1057,6 +1220,72 @@ speed; the spin respects `prefers-reduced-motion`).
|
|
|
1057
1220
|
|
|
1058
1221
|
---
|
|
1059
1222
|
|
|
1223
|
+
## Delegated sub-agents
|
|
1224
|
+
|
|
1225
|
+
A run that hands work to a sub-agent reads as a stall. The parent's
|
|
1226
|
+
`delegate_task` card sits at "running…" for the child's entire duration —
|
|
1227
|
+
however many tools the child calls, however long it takes — with nothing on
|
|
1228
|
+
screen to say anything is happening.
|
|
1229
|
+
|
|
1230
|
+
If your server narrates that, the component draws it. The wire is an ordinary
|
|
1231
|
+
AG-UI `CUSTOM` event named `ag_ui.subagent`, carrying:
|
|
1232
|
+
|
|
1233
|
+
| Key | Meaning |
|
|
1234
|
+
| --- | --- |
|
|
1235
|
+
| `delegationId` | the **parent's own `delegate_task` tool-call id** — not the child's run id |
|
|
1236
|
+
| `agent` | the child agent's name |
|
|
1237
|
+
| `phase` | one of `started`, `tool_call`, `tool_result`, `finished`, `failed` |
|
|
1238
|
+
| `status` | a pre-rendered line, ready to show |
|
|
1239
|
+
| `tool` | `toolCallId`, `name` and `ok`, on the two tool phases only |
|
|
1240
|
+
|
|
1241
|
+
Exactly one `started` opens a delegation and exactly one `finished` or `failed`
|
|
1242
|
+
closes it. `ok` is a tri-state: `null` while the child's call runs, `true` on a
|
|
1243
|
+
result it accepted, `false` on one that came back to it.
|
|
1244
|
+
|
|
1245
|
+
Because the key is the *parent's* call id, the surface attaches to a card that
|
|
1246
|
+
already exists rather than floating a second element with the same identity: one
|
|
1247
|
+
**collapsed row per delegation**, live, carrying the server's `status` line and
|
|
1248
|
+
nothing else, expanding onto the child's own tool calls. A ten-step child costs
|
|
1249
|
+
one row until somebody opens it, and there is no second visual language — it
|
|
1250
|
+
reads the way tool cards already read.
|
|
1251
|
+
|
|
1252
|
+
`status` is why the collapsed row needs no wording of its own. The structured
|
|
1253
|
+
keys are there for a host that would rather write its own.
|
|
1254
|
+
|
|
1255
|
+
The row shows in **every** [display mode](#tool-call-display-modes), including
|
|
1256
|
+
`minimal`. It sits outside the card body rather than in it, because the body is
|
|
1257
|
+
what the density modes hide — and a live progress line that only appeared in
|
|
1258
|
+
`full` would leave exactly the stall it exists to end. Same reasoning that shows
|
|
1259
|
+
a deferred card's arguments whatever the mode.
|
|
1260
|
+
|
|
1261
|
+
**A failure carries no exception text on this channel, deliberately** — the same
|
|
1262
|
+
reasoning that redacts a `RUN_ERROR`, since an exception's words are written for
|
|
1263
|
+
an operator. The detail rides the ordinary tool result for that delegation, which
|
|
1264
|
+
lands in the same card's `Result` region a few pixels below. Nothing here invents
|
|
1265
|
+
words the server declined to send.
|
|
1266
|
+
|
|
1267
|
+
**None of it is persisted.** A `CUSTOM` event never enters the message list, so
|
|
1268
|
+
nothing replays on a thread restore — which is the right half of the
|
|
1269
|
+
[carrier split](#which-carrier-should-the-server-use): a delegation that was live
|
|
1270
|
+
an hour ago is not live now, and replaying its progress would be a lie about a
|
|
1271
|
+
run that is over. Reload mid-run and the tool card is still there; the nested
|
|
1272
|
+
detail is not. That is the intended behaviour.
|
|
1273
|
+
|
|
1274
|
+
Like `ag_ui.invalidate`, this name is **routed** rather than forwarded: it draws
|
|
1275
|
+
itself and does not also arrive as an `ag-ui-custom` event. Every other name
|
|
1276
|
+
still reaches your page untouched.
|
|
1277
|
+
|
|
1278
|
+
Style it through `tool-card-subagent` (the region inside the card), `subagent`,
|
|
1279
|
+
`subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`,
|
|
1280
|
+
`subagent-step`, `subagent-step-icon` and `subagent-step-name`. The two glyph
|
|
1281
|
+
states reuse the card's own `--ag-ui-tool-icon-done` / `--ag-ui-tool-icon-error`
|
|
1282
|
+
properties and its spinner speed, so re-theming the cards re-themes these. The
|
|
1283
|
+
row's own chrome comes from `subAgentWorking` and `subAgentSteps` in
|
|
1284
|
+
[`UiStrings`](#internationalization-i18n); everything else on the row is the
|
|
1285
|
+
server's text.
|
|
1286
|
+
|
|
1287
|
+
---
|
|
1288
|
+
|
|
1060
1289
|
## Resizing the panel
|
|
1061
1290
|
|
|
1062
1291
|
The panel carries a drag handle on its leading corner (or leading edge, docked),
|
|
@@ -1127,6 +1356,197 @@ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
|
|
|
1127
1356
|
|
|
1128
1357
|
---
|
|
1129
1358
|
|
|
1359
|
+
## Follow-up suggestions
|
|
1360
|
+
|
|
1361
|
+
The agent can offer the *next* question, not just answer this one. Registered
|
|
1362
|
+
skill chips are static and host-configured, so they can say "summarize this" but
|
|
1363
|
+
never "want me to update the shipping address too?" after a tool has run.
|
|
1364
|
+
|
|
1365
|
+
The server pushes an `ACTIVITY_SNAPSHOT` under the `suggestions` type — with
|
|
1366
|
+
django-ag-ui, `suggestions_activity([...])`. The component draws each prompt as
|
|
1367
|
+
a chip; clicking one sends it as the user's message, exactly as if they had
|
|
1368
|
+
typed it.
|
|
1369
|
+
|
|
1370
|
+
```json
|
|
1371
|
+
{ "activityType": "suggestions",
|
|
1372
|
+
"content": { "prompts": ["Update the shipping address too"] } }
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
**Chips are content.** They ride the same activity envelope as charts, so they
|
|
1376
|
+
persist and a reload puts them back, and a set pushed under an id already on
|
|
1377
|
+
screen replaces that row rather than adding a second one.
|
|
1378
|
+
|
|
1379
|
+
At most **4** prompts of **120** characters each — `MAX_SUGGESTIONS` and
|
|
1380
|
+
`MAX_SUGGESTION_CHARS`, both exported. The server-side helper *raises* past
|
|
1381
|
+
those bounds while this side silently drops: that asymmetry is deliberate, since
|
|
1382
|
+
the producer can report the problem and the client cannot.
|
|
1383
|
+
|
|
1384
|
+
Nothing to enable. A `suggestions` activity from a server that pushes one is
|
|
1385
|
+
drawn; an `activity_type` this component does not know is ignored, which is what
|
|
1386
|
+
the open field is for.
|
|
1387
|
+
|
|
1388
|
+
## Editing a gated call before approving it
|
|
1389
|
+
|
|
1390
|
+
AG-UI's resume payload carries `editedArgs`, and the protocol gates it on the
|
|
1391
|
+
agent's own `approveWithEdits` capability. The approval card can offer it:
|
|
1392
|
+
|
|
1393
|
+
```js
|
|
1394
|
+
chat.approveWithEdits = true; // your server accepts editedArgs
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
**Off by default, and an assertion about your server rather than a
|
|
1398
|
+
negotiation** — capabilities are not on the wire this component reads, so it
|
|
1399
|
+
cannot check. Turned on against a server that ignores `editedArgs`, a user would
|
|
1400
|
+
edit arguments it silently discards, which is worse than not offering.
|
|
1401
|
+
|
|
1402
|
+
The card then shows the call's arguments as editable JSON. `editedArgs` rides
|
|
1403
|
+
the resume payload **only when something actually changed**, so a server can tell
|
|
1404
|
+
"approved as proposed" from "approved, but like this" without diffing what it
|
|
1405
|
+
already sent. Unparseable JSON, or JSON that is not an object, keeps the card
|
|
1406
|
+
open with the reason on it rather than approving the original behind the user's
|
|
1407
|
+
back.
|
|
1408
|
+
|
|
1409
|
+
Only offered for an interrupt naming a tool call this component holds a card
|
|
1410
|
+
for — the card is where the arguments still are.
|
|
1411
|
+
|
|
1412
|
+
## Localizing the timestamps
|
|
1413
|
+
|
|
1414
|
+
There is **no `Intl` anywhere in this component**. The relative timestamps in the
|
|
1415
|
+
thread drawer and checkpoint panel (`"5m ago"`, `"2d ago"`) are deliberately
|
|
1416
|
+
locale-neutral: a component that guessed a locale would disagree with the page
|
|
1417
|
+
it is embedded in, and being wrong in a second language is worse than being
|
|
1418
|
+
neutral in one.
|
|
1419
|
+
|
|
1420
|
+
That is a good default and a bad requirement, so it is replaceable:
|
|
1421
|
+
|
|
1422
|
+
```js
|
|
1423
|
+
const rtf = new Intl.RelativeTimeFormat("de", { numeric: "auto" });
|
|
1424
|
+
chat.formatRelativeTime = (ts) =>
|
|
1425
|
+
rtf.format(Math.round((ts - Date.now()) / 60000), "minute");
|
|
1426
|
+
```
|
|
1427
|
+
|
|
1428
|
+
`relativeTime` is exported too, for a host that wants to build on the built-in
|
|
1429
|
+
rather than replace it.
|
|
1430
|
+
|
|
1431
|
+
## Message actions: copy, retry, feedback
|
|
1432
|
+
|
|
1433
|
+
Every finished assistant message carries a small row of actions beneath it —
|
|
1434
|
+
a **sibling** of the bubble, never a child, so the buttons never join the
|
|
1435
|
+
message's own text.
|
|
1436
|
+
|
|
1437
|
+
- **Retry** re-asks the question. History is truncated to the most recent user
|
|
1438
|
+
message inclusive and the run repeats, so the agent answers what it was asked
|
|
1439
|
+
rather than being told its last answer was wrong.
|
|
1440
|
+
- **Copy** puts the message's text on the clipboard, and says so on the button.
|
|
1441
|
+
A refused clipboard permission is reported there too, rather than thrown.
|
|
1442
|
+
- **Thumbs up / down** fire `ag-ui-feedback` (wired below) and **store nothing**.
|
|
1443
|
+
|
|
1444
|
+
Retry sits on the **last** answer only. Re-running an older turn is branching,
|
|
1445
|
+
and for a page-driving agent editing a past turn is not neutral — those turns
|
|
1446
|
+
clicked buttons, and re-running turn 3 does not un-save what turn 5 saved.
|
|
1447
|
+
|
|
1448
|
+
!!! note
|
|
1449
|
+
A retried turn **re-runs its tools.** The previous attempt already did what
|
|
1450
|
+
it did, and this does not undo it. Confirmation still applies, so a
|
|
1451
|
+
destructive tool asks again — unless the user waived it for this session
|
|
1452
|
+
with *Always allow*.
|
|
1453
|
+
|
|
1454
|
+
A failed run gets the same row, with Retry and Copy and no rating: error text is
|
|
1455
|
+
what people paste into a bug report, but "the connection dropped" is not a
|
|
1456
|
+
statement about answer quality and mixing it into feedback makes that signal say
|
|
1457
|
+
less. This is why a dropped connection is still rendered as an **error** rather
|
|
1458
|
+
than demoted to a run notice — a notice "never settles, takes no action, and
|
|
1459
|
+
carries no controls", and a failure with a way back needs one.
|
|
1460
|
+
|
|
1461
|
+
The row can be trimmed, or removed, with `data-message-actions` — a comma list of
|
|
1462
|
+
the actions to keep, or `="false"` for none at all:
|
|
1463
|
+
|
|
1464
|
+
```html
|
|
1465
|
+
<!-- copy only: nothing here listens for a rating, and the surface forbids re-runs -->
|
|
1466
|
+
<ag-ui-chat endpoint="/agent/" data-message-actions="copy"></ag-ui-chat>
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
It is per-action rather than one switch because the three disappear for
|
|
1470
|
+
different reasons. Thumbs are only useful to a host listening for
|
|
1471
|
+
`ag-ui-feedback`, and two buttons that lead nowhere are worse than none. Retry
|
|
1472
|
+
re-runs the agent, which a constrained surface may not permit. Copy is the one
|
|
1473
|
+
nobody objects to — and with a single switch, dropping either of the others would
|
|
1474
|
+
have cost it too. Nothing survives, and no row is built at all: an empty row
|
|
1475
|
+
still takes its margin and still announces itself as a group of actions.
|
|
1476
|
+
|
|
1477
|
+
`retryLastTurn()` is public, for a host driving its own message UI.
|
|
1478
|
+
|
|
1479
|
+
```js
|
|
1480
|
+
chat.addEventListener("ag-ui-feedback", (e) => {
|
|
1481
|
+
analytics.track("assistant_rating", e.detail); // { content, rating }
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
await chat.retryLastTurn(); // false when there is nothing to ask again
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
## Quoting a selection
|
|
1488
|
+
|
|
1489
|
+
Select any text in the transcript and a small **Quote** offer floats beside it.
|
|
1490
|
+
Taking it drops the selection into the composer as a markdown blockquote and
|
|
1491
|
+
leaves the caret on a fresh line under it — a quotation is how a question
|
|
1492
|
+
narrows to one part of an answer, so nothing is sent until you say what you are
|
|
1493
|
+
asking.
|
|
1494
|
+
|
|
1495
|
+
Quoting **appends**, after whatever is already typed, so a second quotation is a
|
|
1496
|
+
second thing being asked about rather than a replacement for the first. Long
|
|
1497
|
+
selections are capped at 500 characters: select-all-then-quote is a gesture the
|
|
1498
|
+
transcript already answers, and pasting the whole conversation back costs tokens
|
|
1499
|
+
to say nothing.
|
|
1500
|
+
|
|
1501
|
+
Set `data-quote-selection="false"` to turn the offer off. The `quote-selection`
|
|
1502
|
+
`part` styles it.
|
|
1503
|
+
|
|
1504
|
+
### The half that matters: selection in **your** page
|
|
1505
|
+
|
|
1506
|
+
The transcript is the easy half. A chat mounted beside a table, a diff or a
|
|
1507
|
+
report is sitting in the surface the user actually works in — and *that*
|
|
1508
|
+
selection is one no hosted chat can reach.
|
|
1509
|
+
|
|
1510
|
+
`offerQuoteInPage()` extends the same select-then-offer gesture to the whole
|
|
1511
|
+
page. It is opt-in, because it listens on your document:
|
|
1512
|
+
|
|
1513
|
+
```js
|
|
1514
|
+
const stop = chat.offerQuoteInPage(); // the whole page
|
|
1515
|
+
chat.offerQuoteInPage(document.querySelector("#report")); // or one region
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
For a deliberate trigger instead of a selection, `quote(text)` is the seam
|
|
1519
|
+
underneath:
|
|
1520
|
+
|
|
1521
|
+
```js
|
|
1522
|
+
// "Ask about this row" — a button on each row of your own table.
|
|
1523
|
+
row.querySelector(".ask").addEventListener("click", () => {
|
|
1524
|
+
chat.quote(row.innerText);
|
|
1525
|
+
});
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
`quote()` never sends — pair it with [`sendMessage()`](#sending-from-your-own-ui)
|
|
1529
|
+
if you want a one-click "explain this" that skips the composer entirely.
|
|
1530
|
+
|
|
1531
|
+
> **Do not write the four-line version of `offerQuoteInPage()`.**
|
|
1532
|
+
> A `mouseup` listener that quotes every settled selection appends to the
|
|
1533
|
+
> composer on every drag the user made to *read*, to copy, or to fix a typo —
|
|
1534
|
+
> and it cannot tell a selection in your prose from one inside the user's own
|
|
1535
|
+
> half-typed `<input>`, because Chrome reports a field's internal selection
|
|
1536
|
+
> through `document.getSelection()` as an ordinary range over the field's
|
|
1537
|
+
> **wrapper**. The text reads back perfectly and nothing about the range says
|
|
1538
|
+
> where it came from; the only signal is `document.activeElement`. That guard,
|
|
1539
|
+
> plus skipping the widget's own transcript, plus retiring a fixed-position
|
|
1540
|
+
> affordance on scroll, is what the method is for.
|
|
1541
|
+
|
|
1542
|
+
> **Reading a selection out of a shadow tree takes care too.**
|
|
1543
|
+
> Engines disagree about what `document.getSelection()` reports for a selection
|
|
1544
|
+
> made *inside* a shadow root: WebKit rescopes the endpoints to the host element,
|
|
1545
|
+
> so you get the whole widget and none of the words, while Chromium hands back
|
|
1546
|
+
> the shadow nodes directly. `getComposedRanges` settles it, and this component
|
|
1547
|
+
> uses it where it exists. `quotableSelection(container, roots)` is exported if
|
|
1548
|
+
> you have the same problem in your own component.
|
|
1549
|
+
|
|
1130
1550
|
## Run notices: compaction and agent skills
|
|
1131
1551
|
|
|
1132
1552
|
Some things a run does are neither text nor a tool the user asked for — the server condensed
|
|
@@ -1467,6 +1887,83 @@ substitute the other way round either, because shared state requires the *agent*
|
|
|
1467
1887
|
`STATE_SNAPSHOT`, which is not the host's decision to make. Use state when the two ends edit one
|
|
1468
1888
|
object; use this when your page owns the data and just needs to know it moved.
|
|
1469
1889
|
|
|
1890
|
+
**`ag-ui-custom`** *(event)* — the agent sent an AG-UI `CUSTOM` event.
|
|
1891
|
+
`detail: { name, value }` (typed `CustomAgentDetail`), both verbatim and uninterpreted.
|
|
1892
|
+
|
|
1893
|
+
```js
|
|
1894
|
+
chat.addEventListener("ag-ui-custom", (e) => {
|
|
1895
|
+
if (e.detail.name === "invalidate") {
|
|
1896
|
+
void refetch(e.detail.value);
|
|
1897
|
+
}
|
|
1898
|
+
// Any other name: no listener, nothing happens. That is the intended outcome.
|
|
1899
|
+
});
|
|
1900
|
+
```
|
|
1901
|
+
|
|
1902
|
+
`CUSTOM` is one of exactly two AG-UI carriers whose payload name is an open
|
|
1903
|
+
string the protocol does not enumerate, and it is the **imperative** one:
|
|
1904
|
+
something for your page to *do*. Its sibling `ACTIVITY_SNAPSHOT` carries
|
|
1905
|
+
transcript **content**, which is why an activity is materialised into a message,
|
|
1906
|
+
persisted with the thread and replayed on restore, and this is not.
|
|
1907
|
+
|
|
1908
|
+
**That asymmetry is the rule for choosing between them.** Content has a place in
|
|
1909
|
+
the conversation and should replay. An imperative has no place and no meaning
|
|
1910
|
+
once acted on — replaying "refetch the board" on every thread load is a bug, not
|
|
1911
|
+
a feature. If it must survive a reload, it belongs on the other carrier.
|
|
1912
|
+
|
|
1913
|
+
The element takes no view of what a name means and forwards every one, so a name
|
|
1914
|
+
it has never heard of reaches you unchanged. A host with no listener for a name
|
|
1915
|
+
simply ignores it, which is the graceful outcome an open field exists for.
|
|
1916
|
+
|
|
1917
|
+
**`ag-ui-invalidate`** *(event)* — the agent named resources its write moved.
|
|
1918
|
+
`detail: { keys, reason }` (typed `InvalidateDetail`).
|
|
1919
|
+
|
|
1920
|
+
One `CUSTOM` name routed to its own event so you do not have to string-match;
|
|
1921
|
+
every other name still arrives as `ag-ui-custom`. It fires **as each
|
|
1922
|
+
announcement arrives**, during the run, and the same keys ride
|
|
1923
|
+
`ag-ui-run-finished` again at the end as `invalidated`, de-duplicated.
|
|
1924
|
+
|
|
1925
|
+
> **Do not reload the page on this.** The user was probably typing. An
|
|
1926
|
+
> agent-triggered reload or a blind refetch into a live form destroys unsaved
|
|
1927
|
+
> input, and from their side the page threw their work away on its own. Check
|
|
1928
|
+
> first, and offer rather than act:
|
|
1929
|
+
|
|
1930
|
+
```js
|
|
1931
|
+
chat.addEventListener("ag-ui-invalidate", (e) => {
|
|
1932
|
+
if (formIsDirty()) {
|
|
1933
|
+
showBanner("This data changed. Refresh when you're ready.", e.detail.keys);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
refetch(e.detail.keys); // e.detail.keys → ["orders", "orders/42"]
|
|
1937
|
+
});
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
**Keys are opaque and matching is exact.** `orders/42` does not imply `orders` —
|
|
1941
|
+
a prefix rule would be the component guessing at a scheme it does not own, and
|
|
1942
|
+
`orders/1` would match `orders/11`. A server that wants the collection refreshed
|
|
1943
|
+
names it. Your own matching may be hierarchical, because in your vocabulary the
|
|
1944
|
+
scheme is known; that is what TanStack query keys are built for.
|
|
1945
|
+
|
|
1946
|
+
Already listening on `ag-ui-run-finished`? Upgrading is one line, and the `else`
|
|
1947
|
+
is the whole compatibility story:
|
|
1948
|
+
|
|
1949
|
+
```js
|
|
1950
|
+
if (detail.invalidated.length > 0) refetchOnly(detail.invalidated);
|
|
1951
|
+
else if (detail.tools.some((t) => t.side === "server")) refetchEverything();
|
|
1952
|
+
```
|
|
1953
|
+
|
|
1954
|
+
| Server | Client | Result |
|
|
1955
|
+
| --- | --- | --- |
|
|
1956
|
+
| old | old | coarse refetch, as today |
|
|
1957
|
+
| new | old | the `CUSTOM` event is ignored; coarse refetch still fires |
|
|
1958
|
+
| old | new | `invalidated` is empty; the `else` branch runs |
|
|
1959
|
+
| new | new | precise, and live during the run |
|
|
1960
|
+
|
|
1961
|
+
Nothing negotiates and nothing handshakes, which is what makes this shippable
|
|
1962
|
+
across repos with independent release cadences.
|
|
1963
|
+
|
|
1964
|
+
**Note:** it reaches **the page that started the run**, during the run. There is one
|
|
1965
|
+
response stream per run and no channel to anybody else's browser.
|
|
1966
|
+
|
|
1470
1967
|
## Resuming a run
|
|
1471
1968
|
|
|
1472
1969
|
When the server persists run checkpoints (django-ag-ui's `step_store`), a run
|
|
@@ -1624,9 +2121,9 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1624
2121
|
| `isNavigates(parameters)` | function | Read the `x-navigates` flag. |
|
|
1625
2122
|
| `createPageActionTools(enabled, resolveTarget)` | function | Build the opt-in `scroll_to` / `drag_and_drop` tools. |
|
|
1626
2123
|
| `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
|
|
1627
|
-
| `ResolvePageTarget` | type | `(target) => HTMLElement
|
|
2124
|
+
| `ResolvePageTarget` | type | `(target) => HTMLElement \| null` — the page-target resolver. |
|
|
1628
2125
|
| `X_DESTRUCTIVE_KEY` / `X_NAVIGATES_KEY` | const | The JSON-Schema extension keys. |
|
|
1629
|
-
| `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `
|
|
2126
|
+
| `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `Record<string, ToolCatalogEntry>` — whole entries, not bare summaries, so a caller can reach `description` too. Malformed input yields an empty map rather than throwing. |
|
|
1630
2127
|
| `ToolCatalogEntry` | type | One row of that catalog. |
|
|
1631
2128
|
| `prettifyToolName(name)` | function | Last fallback of the tool-card label chain (`delete_record` reads as *Delete record*). |
|
|
1632
2129
|
|
|
@@ -1643,6 +2140,18 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1643
2140
|
| `PageState` | type | A page-state binding declaration. |
|
|
1644
2141
|
| `Skill` | type | A launchable prompt (chip / `/`-command). |
|
|
1645
2142
|
| `RunFinishedDetail` / `ToolRun` | type | `ag-ui-run-finished` detail: the tools an interaction ran, and which side ran them. |
|
|
2143
|
+
| `CustomAgentDetail` | type | `ag-ui-custom` detail: an AG-UI `CUSTOM` event's `name` and `value`, verbatim. |
|
|
2144
|
+
| `InvalidateDetail` | type | `ag-ui-invalidate` detail: the resource `keys` that moved, and the `reason`. |
|
|
2145
|
+
| `FeedbackDetail` | type | `ag-ui-feedback` detail: the rated message's `content` and the `rating`. |
|
|
2146
|
+
| `relativeTime` | function | The built-in locale-neutral timestamp formatter (`"5m ago"`), for a host building on it. |
|
|
2147
|
+
| `RelativeTimeFormatter` | type | What `formatRelativeTime` takes: an epoch-ms timestamp in, row text out. |
|
|
2148
|
+
| `renderSuggestionChips` | function | Draw a `suggestions` activity as chips that send themselves; `null` when nothing survives. |
|
|
2149
|
+
| `suggestionPrompts` | function | The usable prompts in a `suggestions` activity's content, bounded and trimmed. |
|
|
2150
|
+
| `attachMessageActions` | function | Give a finished bubble its action row (copy, and feedback when a handler is passed). |
|
|
2151
|
+
| `messageActionBar` | function | The empty action row for a bubble, created if it has none — the shared shell both callers use. |
|
|
2152
|
+
| `MessageActionsOptions` | type | What `attachMessageActions` takes: `strings`, a `text` source, an optional `onFeedback`. |
|
|
2153
|
+
| `ActivityRenderer` | type | Draws one activity from its `content`. Pure: it runs again on every restore. |
|
|
2154
|
+
| `ActivityRegistration` | type | What `registerActivityRenderer` takes: `type`, `render`, and an optional `removedNotice`. |
|
|
1646
2155
|
| `createStateHookTools(binding)` / `StateHook` | deprecated | The former names for `createPageStateTools` / `PageState`. |
|
|
1647
2156
|
|
|
1648
2157
|
### Durability
|
|
@@ -1657,15 +2166,15 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1657
2166
|
| `RunIndex` | class | Reads a `data-runs-url` run index and derives its resume / fork endpoints. |
|
|
1658
2167
|
| `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable, preview? }`). |
|
|
1659
2168
|
| `CheckpointMenu` | class | The *Continue a run* panel. |
|
|
1660
|
-
| `CheckpointVerb` | type | `"resume"
|
|
2169
|
+
| `CheckpointVerb` | type | `"resume" \| "fork"`. |
|
|
1661
2170
|
|
|
1662
2171
|
### Attachments
|
|
1663
2172
|
|
|
1664
2173
|
| Export | Kind | Summary |
|
|
1665
2174
|
| --- | --- | --- |
|
|
1666
2175
|
| `uploadAttachment(file, options)` | function | The built-in upload (multipart, progress) → `AttachmentRef`. |
|
|
1667
|
-
| `UploadOptions` | type | `{ url, headers?, onProgress?, signal? }`. |
|
|
1668
|
-
| `UploadHandler` | type | `(file, onProgress) => Promise<AttachmentRef>` — the `uploadHandler` swap seam (
|
|
2176
|
+
| `UploadOptions` | type | `{ url, headers?, credentials?, onProgress?, signal? }`. `credentials` is spelled as a fetch mode but carried by `XMLHttpRequest.withCredentials`, so only `"include"` is distinguishable. |
|
|
2177
|
+
| `UploadHandler` | type | `(file, onProgress, signal?) => Promise<AttachmentRef>` — the `uploadHandler` swap seam (tus / S3). The signal fires when the tray removes a chip or the element is torn down; a handler that honours it aborts its own transport, so a cancelled upload leaves no orphaned file on the server. |
|
|
1669
2178
|
| `AttachmentRef` | type | The durable upload ref (`{ id, name, mime, size, url? }`). |
|
|
1670
2179
|
| `messageAttachments(message)` | function | Read the refs a restored user message carries. |
|
|
1671
2180
|
|
|
@@ -1674,7 +2183,7 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1674
2183
|
| Export | Kind | Summary |
|
|
1675
2184
|
| --- | --- | --- |
|
|
1676
2185
|
| `transcribeAudio(audio, options)` | function | The built-in transcription POST (multipart) → the transcript text. |
|
|
1677
|
-
| `TranscribeOptions` | type | `{ url, headers? }
|
|
2186
|
+
| `TranscribeOptions` | type | `{ url, headers?, credentials? }` — `credentials` as fetch's own cookie mode. |
|
|
1678
2187
|
| `TranscribeHandler` | type | `(audio) => Promise<string>` — the `transcribeHandler` swap seam (Web Speech, direct-to-provider). |
|
|
1679
2188
|
|
|
1680
2189
|
### UI & DOM primitives
|
|
@@ -1683,25 +2192,34 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1683
2192
|
| --- | --- | --- |
|
|
1684
2193
|
| `ToolCallCard` | class | A live tool-call card for the transcript. |
|
|
1685
2194
|
| `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
|
|
2195
|
+
| `ToolPayloadFormatter` | type | Draws one region of a card's body (`AgUiChat.formatToolPayload`); `null` falls through to the built-in pretty-print. |
|
|
2196
|
+
| `ToolPayload` | type | The region being drawn: `arguments` (the parsed record) or `result` (the raw string and its outcome). |
|
|
2197
|
+
| `ToolCallCardOptions` | type | Per-card wiring beyond name / args / label / strings — currently `formatPayload`. |
|
|
1686
2198
|
| `requestConfirmation(host, request, options?)` | function | Append the inline confirmation card to the transcript. |
|
|
1687
2199
|
| `ConfirmationRequest` | type | What the card displays. |
|
|
1688
|
-
| `ConfirmationOptions` | type | `{ signal?, strings? }` — abort resolves the card as declined; `strings` localizes it. |
|
|
2200
|
+
| `ConfirmationOptions` | type | `{ signal?, strings?, onAlwaysAllow? }` — abort resolves the card as declined; `strings` localizes it; passing `onAlwaysAllow` is what adds the third button. |
|
|
1689
2201
|
| `UiStrings` | type | The flat table of every user-facing string. |
|
|
1690
2202
|
| `DEFAULT_UI_STRINGS` | const | The English defaults (the override floor). |
|
|
1691
2203
|
| `mergeUiStrings(overrides)` | function | Merge a partial override over the defaults. |
|
|
1692
2204
|
| `renderMarkdown(text, options?)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
|
|
1693
2205
|
| `RenderMarkdownOptions` | type | `{ allowImages? }` — opt `<img>` back into the sanitized output. |
|
|
1694
2206
|
| `requestApproval(host, request, options?)` | function | Append the inline approval card that gates a server-side tool. |
|
|
1695
|
-
| `ApprovalRequest` | type | What that card displays (`{ message?, toolName? }`). |
|
|
1696
|
-
| `ApprovalOptions` | type | `{ signal?, strings? }` — abort resolves the card as denied; `strings` localizes it. |
|
|
2207
|
+
| `ApprovalRequest` | type | What that card displays (`{ message?, toolName?, args? }`). |
|
|
2208
|
+
| `ApprovalOptions` | type | `{ signal?, strings?, onEdit? }` — abort resolves the card as denied; `strings` localizes it; passing `onEdit` offers the call's arguments for editing and is called only when they actually changed. |
|
|
1697
2209
|
| `ApprovalRenderer` | type | Replace the built-in approval card outright (`AgUiChat.approvalRenderer`). |
|
|
1698
2210
|
| `requestQuestion(host, request, options?)` | function | Append the inline `ask_user` card (radios and/or free text). |
|
|
1699
2211
|
| `QuestionRequest` | type | What that card asks. |
|
|
1700
2212
|
| `QuestionOptions` | type | `{ signal?, strings? }` — abort resolves it with an empty answer. |
|
|
1701
|
-
| `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.
|
|
2213
|
+
| `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.askUserRenderer`). |
|
|
1702
2214
|
| `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
|
|
1703
2215
|
| `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
|
|
1704
2216
|
| `ChartSpec` / `ChartSeries` / `ChartKind` | type | A chart as data, one named series, and how it is drawn. |
|
|
2217
|
+
| `attachQuoteOffer(options)` | function | The page-side select-then-quote offer, with its guards. `AgUiChat.offerQuoteInPage()` is the one-line form. |
|
|
2218
|
+
| `PageQuoteOffer` / `PageQuoteOfferOptions` | type | The live offer (`{ element, detach }`) and what it takes. |
|
|
2219
|
+
| `quotableSelection(container, roots, near?)` | function | The current selection when it lies inside `container`, read through the shadow-aware API where the engine has one. `near` is where the gesture ended, used to pick the line the offer hangs from. |
|
|
2220
|
+
| `QuotableSelection` | type | `{ text, rect }` — what was selected, and where it sits. |
|
|
2221
|
+
| `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
|
|
2222
|
+
| `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
|
|
1705
2223
|
| `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
|
|
1706
2224
|
| `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
|
|
1707
2225
|
| `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
|
|
@@ -1716,6 +2234,13 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1716
2234
|
| `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
|
|
1717
2235
|
| `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
|
|
1718
2236
|
| `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
|
|
2237
|
+
| `CUSTOM_AGENT_EVENT` | The agent-`CUSTOM` CustomEvent name (`ag-ui-custom`). |
|
|
2238
|
+
| `INVALIDATE_EVENT` | The resource-invalidation CustomEvent name (`ag-ui-invalidate`). |
|
|
2239
|
+
| `FEEDBACK_EVENT` | The message-rating CustomEvent name (`ag-ui-feedback`). |
|
|
2240
|
+
| `SUGGESTIONS_ACTIVITY_TYPE` | The `activity_type` carrying follow-up prompts (`suggestions`). |
|
|
2241
|
+
| `MAX_SUGGESTIONS` | Most prompts one push draws (4). Mirrors the server's cap. |
|
|
2242
|
+
| `MAX_SUGGESTION_CHARS` | Longest one prompt may be (120). Mirrors the server's cap. |
|
|
2243
|
+
| `INVALIDATE_CUSTOM_NAME` | The AG-UI `CUSTOM` `name` that carries one (`ag_ui.invalidate`). |
|
|
1719
2244
|
| `ATTACHMENT_EVENT` | The attachments-changed CustomEvent name (`ag-ui-attachments`). |
|
|
1720
2245
|
| `STATE_EVENT` | The shared-state CustomEvent name (`ag-ui-state`). |
|
|
1721
2246
|
| `CHART_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type a server sets to push a chart. |
|
|
@@ -1723,8 +2248,9 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1723
2248
|
| `COMPACTION_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type reporting a trimmed history. |
|
|
1724
2249
|
| `LOAD_CAPABILITY_TOOL` | The agent-side capability-loading tool's name. |
|
|
1725
2250
|
| `MESSAGE_ROLE` | Message role constants. |
|
|
2251
|
+
| `MESSAGE_ACTIONS` | The message-action tokens `data-message-actions` selects by (`copy` / `retry` / `feedback`). |
|
|
1726
2252
|
| `TOOL_CALL_STATUS` | Tool-call card status constants. |
|
|
1727
|
-
| `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
|
|
2253
|
+
| `TOOL_DISPLAY` | Tool-call display-mode constants (`inline` / `minimal` / `compact` / `full`). |
|
|
1728
2254
|
| `X_CONFIRM_KEY` | Confirmation-prompt key: on a tool's JSON Schema for a client-side confirmation, and in an AG-UI interrupt's `metadata` for a server-side approval. |
|
|
1729
2255
|
| `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
|
|
1730
2256
|
| `MAX_TOOL_ROUNDS` | Upper bound on tool-call → re-run rounds per send. |
|
|
@@ -1855,15 +2381,18 @@ component sets, so a new one cannot ship undocumented.
|
|
|
1855
2381
|
|
|
1856
2382
|
| Feature | Parts |
|
|
1857
2383
|
| --- | --- |
|
|
1858
|
-
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `resize-handle` |
|
|
2384
|
+
| Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `jump-latest`, `resize-handle` |
|
|
1859
2385
|
| Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
|
|
1860
2386
|
| Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
|
|
1861
2387
|
| Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
|
|
1862
2388
|
| Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
|
|
1863
|
-
|
|
|
1864
|
-
|
|
|
1865
|
-
|
|
|
1866
|
-
|
|
|
2389
|
+
| Follow-up suggestions | `suggestions`, `suggestion-chip` |
|
|
2390
|
+
| Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`) |
|
|
2391
|
+
| 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` |
|
|
2392
|
+
| 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`, `tool-card-subagent` |
|
|
2393
|
+
| Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
|
|
2394
|
+
| Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
|
|
2395
|
+
| Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
|
|
1867
2396
|
| Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
|
|
1868
2397
|
| Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
|
|
1869
2398
|
| 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` |
|