@artooi/ag-ui-web-component 0.28.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 +417 -1
- package/README.md +371 -5
- package/dist/ag-ui-web-component.bundle.js +308 -50
- 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 +207 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +38 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1516 -76
- 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 +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/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 +40 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +75 -0
- package/src/core/ag_ui_chat.ts +927 -73
- package/src/core/agui_client.ts +63 -0
- package/src/index.ts +39 -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 +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/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 +60 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,11 @@ 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)
|
|
@@ -177,6 +182,7 @@ another origin, add `credentials="include"` too; see
|
|
|
177
182
|
| `data-icon-url` | — | Header (and launcher) icon image URL. A slotted `slot="icon"` wins; see [Header & launcher icon](#header-and-launcher-icon). |
|
|
178
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. |
|
|
179
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). |
|
|
180
186
|
|
|
181
187
|
Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
|
|
182
188
|
`icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
|
|
@@ -561,6 +567,8 @@ transcript as a resolved record after the decision:
|
|
|
561
567
|
- **Confirm** → the handler runs and the result is posted back.
|
|
562
568
|
- **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
|
|
563
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.
|
|
564
572
|
|
|
565
573
|
Whether a call is gated is decided in this order:
|
|
566
574
|
|
|
@@ -568,9 +576,27 @@ Whether a call is gated is decided in this order:
|
|
|
568
576
|
2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
|
|
569
577
|
name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
|
|
570
578
|
for others, which a static flag can't express).
|
|
571
|
-
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),
|
|
572
581
|
which reads the `x-destructive` JSON-Schema flag.
|
|
573
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
|
+
|
|
574
600
|
AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
|
|
575
601
|
**schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
|
|
576
602
|
constant). There is no parallel metadata channel and no name heuristic — destructiveness is exactly
|
|
@@ -1009,6 +1035,67 @@ it runs again every time the conversation is restored.
|
|
|
1009
1035
|
|
|
1010
1036
|
---
|
|
1011
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
|
+
|
|
1012
1099
|
## Tool-call display modes
|
|
1013
1100
|
|
|
1014
1101
|
How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
|
|
@@ -1127,6 +1214,181 @@ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
|
|
|
1127
1214
|
|
|
1128
1215
|
---
|
|
1129
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
|
+
|
|
1130
1392
|
## Run notices: compaction and agent skills
|
|
1131
1393
|
|
|
1132
1394
|
Some things a run does are neither text nor a tool the user asked for — the server condensed
|
|
@@ -1467,6 +1729,83 @@ substitute the other way round either, because shared state requires the *agent*
|
|
|
1467
1729
|
`STATE_SNAPSHOT`, which is not the host's decision to make. Use state when the two ends edit one
|
|
1468
1730
|
object; use this when your page owns the data and just needs to know it moved.
|
|
1469
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
|
+
|
|
1470
1809
|
## Resuming a run
|
|
1471
1810
|
|
|
1472
1811
|
When the server persists run checkpoints (django-ag-ui's `step_store`), a run
|
|
@@ -1643,6 +1982,18 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1643
1982
|
| `PageState` | type | A page-state binding declaration. |
|
|
1644
1983
|
| `Skill` | type | A launchable prompt (chip / `/`-command). |
|
|
1645
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`. |
|
|
1646
1997
|
| `createStateHookTools(binding)` / `StateHook` | deprecated | The former names for `createPageStateTools` / `PageState`. |
|
|
1647
1998
|
|
|
1648
1999
|
### Durability
|
|
@@ -1702,6 +2053,12 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1702
2053
|
| `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
|
|
1703
2054
|
| `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
|
|
1704
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). |
|
|
1705
2062
|
| `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
|
|
1706
2063
|
| `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
|
|
1707
2064
|
| `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
|
|
@@ -1716,6 +2073,13 @@ re-export point. Internal modules import from leaf paths.
|
|
|
1716
2073
|
| `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
|
|
1717
2074
|
| `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
|
|
1718
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`). |
|
|
1719
2083
|
| `ATTACHMENT_EVENT` | The attachments-changed CustomEvent name (`ag-ui-attachments`). |
|
|
1720
2084
|
| `STATE_EVENT` | The shared-state CustomEvent name (`ag-ui-state`). |
|
|
1721
2085
|
| `CHART_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type a server sets to push a chart. |
|
|
@@ -1855,15 +2219,17 @@ component sets, so a new one cannot ship undocumented.
|
|
|
1855
2219
|
|
|
1856
2220
|
| Feature | Parts |
|
|
1857
2221
|
| --- | --- |
|
|
1858
|
-
| 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` |
|
|
1859
2223
|
| Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
|
|
1860
2224
|
| Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
|
|
1861
2225
|
| Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
|
|
1862
2226
|
| Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
|
|
1863
|
-
|
|
|
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` |
|
|
1864
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` |
|
|
1865
|
-
| Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`) |
|
|
1866
|
-
| 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` |
|
|
1867
2233
|
| Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
|
|
1868
2234
|
| Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
|
|
1869
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` |
|