@artooi/ag-ui-web-component 0.39.0 → 0.41.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 +208 -1
- package/README.md +116 -36
- package/dist/ag-ui-web-component.bundle.js +255 -101
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +3 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +7 -3
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +29 -8
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +11 -4
- package/dist/core/attachment.d.ts.map +1 -1
- package/dist/core/client_seed.d.ts +14 -2
- package/dist/core/client_seed.d.ts.map +1 -1
- package/dist/core/tool_outcome.d.ts +4 -3
- package/dist/core/tool_outcome.d.ts.map +1 -1
- package/dist/core/utils.d.ts +19 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.js +1078 -664
- package/dist/index.js.map +4 -4
- package/dist/tools/tool_catalog.d.ts.map +1 -1
- package/dist/ui/composer/composer_attachments.d.ts +6 -1
- package/dist/ui/composer/composer_attachments.d.ts.map +1 -1
- package/dist/ui/composer/composer_voice.d.ts +3 -0
- package/dist/ui/composer/composer_voice.d.ts.map +1 -1
- package/dist/ui/excerpts/transcript_quote_offer.d.ts +7 -2
- package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -1
- package/dist/ui/history/conversation_history.d.ts +30 -4
- package/dist/ui/history/conversation_history.d.ts.map +1 -1
- package/dist/ui/placement/launcher_drag.d.ts +6 -0
- package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
- package/dist/ui/placement/panel_placement.d.ts +1 -1
- package/dist/ui/placement/panel_placement.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/transcript/transcript.d.ts +3 -2
- package/dist/ui/transcript/transcript.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +2 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +3 -2
- package/src/core/ag_ui_chat.ts +217 -64
- package/src/core/agui_client.ts +155 -125
- package/src/core/attachment.ts +13 -5
- package/src/core/client_seed.ts +14 -2
- package/src/core/tool_outcome.ts +4 -3
- package/src/core/utils.ts +23 -0
- package/src/tools/tool_catalog.ts +63 -12
- package/src/ui/composer/composer_attachments.ts +64 -41
- package/src/ui/composer/composer_voice.ts +5 -0
- package/src/ui/excerpts/transcript_quote_offer.ts +26 -13
- package/src/ui/history/conversation_history.ts +149 -38
- package/src/ui/placement/launcher_drag.ts +104 -89
- package/src/ui/placement/panel_placement.ts +27 -3
- package/src/ui/styles.ts +137 -14
- package/src/ui/transcript/transcript.ts +10 -5
- package/src/ui/ui_strings.ts +3 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,211 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.41.0] — 2026-09-22
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Requires `@ag-ui/client` and `@ag-ui/core` 1.0.** Both ranges are now
|
|
15
|
+
`>=1.0.0 <2`. A host that bundles the package itself resolves 1.0 beside it;
|
|
16
|
+
the vendored bundle carries its own copy.
|
|
17
|
+
|
|
18
|
+
- **A tool result whose content arrives as parts shows its text.** Since 1.0 a
|
|
19
|
+
tool may return an ordered list of parts rather than a string, so a result can
|
|
20
|
+
carry an image beside its text. The card shows the text parts joined, both
|
|
21
|
+
while the run streams and when a reload replays it, and the saved conversation
|
|
22
|
+
keeps the message whole, parts included.
|
|
23
|
+
|
|
24
|
+
- **A tool call's outcome and a message's attachment refs travel in
|
|
25
|
+
`metadata`.** `@ag-ui/client` 1.0 strips every key its schemas do not declare,
|
|
26
|
+
from an inbound event before any subscriber sees it and from the outgoing run
|
|
27
|
+
input before it is sent, and `metadata` is the one field on events and
|
|
28
|
+
messages it declares open by key. The element now reads a `TOOL_CALL_RESULT`'s
|
|
29
|
+
outcome from `metadata.outcome` only, which is also what
|
|
30
|
+
`AgUiClientHandlers.onToolResult` receives, and `sendMessage` puts a user
|
|
31
|
+
message's refs at `metadata.attachments`.
|
|
32
|
+
|
|
33
|
+
The client folds a result's metadata onto the tool message it keeps, so a
|
|
34
|
+
server's outcome is now saved on the message itself, and the element writes
|
|
35
|
+
the outcomes it decides on its own -- a declined confirmation, a frontend tool
|
|
36
|
+
that threw, a call the run left unfinished -- in the same place. The outcome
|
|
37
|
+
therefore goes back to the server with the rest of the history on every run,
|
|
38
|
+
which it never did before. `metadata` is a declared field, and a server that
|
|
39
|
+
does not read the key ignores it.
|
|
40
|
+
|
|
41
|
+
- **Needs django-ag-ui 0.63.0 or later for outcomes and attachments.** An older
|
|
42
|
+
server writes the outcome, and reads the refs, at the top level, where 1.0
|
|
43
|
+
strips them. The chat still runs against one, but the agent is never told a
|
|
44
|
+
file was attached, and a failed or declined server-side tool call settles as
|
|
45
|
+
done.
|
|
46
|
+
|
|
47
|
+
- **Conversations stored by an earlier release keep both.** Their tool messages
|
|
48
|
+
carry `outcome`, and their user messages `attachments`, at the top level. The
|
|
49
|
+
replay reads `metadata` first and falls back to the top-level key, so a
|
|
50
|
+
declined card stays declined after the upgrade and a bubble keeps its chips.
|
|
51
|
+
A client seeded from such a conversation moves both keys into `metadata`
|
|
52
|
+
before its first request, so the server is still told about every file
|
|
53
|
+
attached before the upgrade, and the next save writes the new shape.
|
|
54
|
+
|
|
55
|
+
### Deprecated
|
|
56
|
+
|
|
57
|
+
- **`AgUiClient.annotatedMessages` is the same list as `messages`.** It returned
|
|
58
|
+
a copy with each tool call's outcome added for the store, which the outcome
|
|
59
|
+
being on the message has made unnecessary. It is kept so code written against
|
|
60
|
+
it compiles and gets the same history.
|
|
61
|
+
|
|
62
|
+
## [0.40.0] — 2026-09-17
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **An `askUserRenderer` that throws now hands the question to the built-in
|
|
67
|
+
question card.** A throw, or a rejected promise, used to escape the `ask_user`
|
|
68
|
+
tool: its card settled as an error quoting the host's message, that message
|
|
69
|
+
went on to the agent as the tool result although it was never written for the
|
|
70
|
+
model, and the pending wait was never closed, so the next Stop aborted the
|
|
71
|
+
signal of a question that had already ended.
|
|
72
|
+
|
|
73
|
+
The renderer decides how the question looks, not whether it is asked -- the
|
|
74
|
+
same rule `approvalRenderer` follows -- so it now falls back to the built-in
|
|
75
|
+
card, with a `console.warn` naming the tool call, and the run carries on as if
|
|
76
|
+
no renderer had been set. The exception is a rejection after the signal has
|
|
77
|
+
fired, which is how a renderer is expected to honour Stop: that wait resolves
|
|
78
|
+
with an empty answer, as the built-in card does on the same signal, with no
|
|
79
|
+
card drawn and no warning.
|
|
80
|
+
|
|
81
|
+
- **On a small viewport the composer stays at the foot of the panel, with the
|
|
82
|
+
greeting over the space above it.** Until a conversation had something in it,
|
|
83
|
+
`page` -- and an `embedded` panel that opts into a greeting -- centred the
|
|
84
|
+
composer between the greeting and the foot at every width. Centring is a shape
|
|
85
|
+
for a screen with room to spare: on a phone the on-screen keyboard takes the
|
|
86
|
+
room, and the composer was left halfway up what remained with an empty band
|
|
87
|
+
under it, which is the part of the screen a thumb is already on. At 600px wide
|
|
88
|
+
and below it now stays at the foot, directly over the keyboard, and the
|
|
89
|
+
greeting takes the space above it. The prompts in `data-starters`, or
|
|
90
|
+
whatever a host slots as `empty`, go to the foot with it and sit against the
|
|
91
|
+
composer: a prompt chip is a way into the conversation, and next to the field
|
|
92
|
+
it starts it reads as one. The greeting takes the middle of what they leave,
|
|
93
|
+
and the middle of the transcript where there are none.
|
|
94
|
+
`data-small-viewport="off"` keeps the centred shape at every width, as it
|
|
95
|
+
keeps the rest of the desktop layout.
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- **A tool card called with no arguments stops drawing an empty ARGUMENTS
|
|
100
|
+
heading.** The card hides the region rather than framing an empty object, but
|
|
101
|
+
the region declares its own `display`, which beats the user-agent rule for
|
|
102
|
+
the hidden attribute, so 42px of card was drawn holding a heading over
|
|
103
|
+
nothing -- on every call an agent made with no arguments, in the display mode
|
|
104
|
+
that shows arguments by default, and on a pending card's result region too.
|
|
105
|
+
|
|
106
|
+
- **A checkpoint continuation is let go of when its first save fails.** The
|
|
107
|
+
save runs through `conversationStore` synchronously inside the send, and a
|
|
108
|
+
store is the host's to replace: the built-in one swallows a write the browser
|
|
109
|
+
refused, a server-backed one need not. A throw there left the element holding
|
|
110
|
+
a client that would never run, so every later Resume or Fork was refused for
|
|
111
|
+
it with no way to clear it -- the composer's button is Send until a run
|
|
112
|
+
reports a start, so Stop was never offered -- and `sharedState` went on
|
|
113
|
+
writing into the dead client, which the next run then sent stale. The failure
|
|
114
|
+
is now reported with a `console.warn` and the panel takes another pick.
|
|
115
|
+
|
|
116
|
+
- **A turn typed between picking a checkpoint and its run starting is parked
|
|
117
|
+
rather than raced.** The composer learns a run is going from its first event,
|
|
118
|
+
a request round trip behind the pick, so its button was still Send: a turn
|
|
119
|
+
sent in that window started a second run against the conversation the
|
|
120
|
+
continuation had already frozen to save against, and whichever saved last
|
|
121
|
+
dropped the other's turn from the store while both answers streamed into one
|
|
122
|
+
transcript. The composer now queues it, as it does during a run, and sends it
|
|
123
|
+
when the continuation settles. `sendMessage` no-ops in the same window, as it
|
|
124
|
+
already did during a run.
|
|
125
|
+
|
|
126
|
+
- **An empty row of skill chips stops taking space above the composer.** The
|
|
127
|
+
row is hidden whenever the host offers no skills, which is most elements most
|
|
128
|
+
of the time, but it declares its own `display`, and an author display beats
|
|
129
|
+
the user-agent rule for the hidden attribute. So it kept its 20px of padding
|
|
130
|
+
under every placement: a band of panel between the transcript and the
|
|
131
|
+
composer, reading as the gap under whatever the transcript ended with. The
|
|
132
|
+
palette and the queued row beside it were fixed for this reason already, and
|
|
133
|
+
a tool card's payload regions turned out to be a fourth case, below.
|
|
134
|
+
|
|
135
|
+
- **A checkpoint continuation waits for the run in flight.** Picking Resume or
|
|
136
|
+
Fork while an answer was streaming started a second run beside it, and an
|
|
137
|
+
earlier continuation kept streaming where Stop no longer reached it. The pick
|
|
138
|
+
is now refused with a composer hint, as Retry is; the run in flight is left
|
|
139
|
+
alone and the typed turn stays in the composer. `continueWhileRunning` is new
|
|
140
|
+
in the string table.
|
|
141
|
+
|
|
142
|
+
- **A continued exchange is kept with its conversation.** It was never saved,
|
|
143
|
+
so a reload lost it, and the next message went out without it and with the
|
|
144
|
+
shared state from before the continuation. Each save now writes the
|
|
145
|
+
conversation with the exchange after it, through the existing `saveMessages`
|
|
146
|
+
-- `ClientConversationStore` is unchanged -- and the next message carries
|
|
147
|
+
both. After continuing from an earlier run, what is saved is what the
|
|
148
|
+
transcript shows, not only the turns up to that run. `sharedState` reads a
|
|
149
|
+
running continuation, and `AgUiClient.annotatedMessages` is new: the history
|
|
150
|
+
in the form a save writes it, with how each tool call ended.
|
|
151
|
+
|
|
152
|
+
- **A full-screen panel stays on the visible screen when a keyboard opens under
|
|
153
|
+
the field.** To show the composer, iOS Safari pans the visible area down the
|
|
154
|
+
page, and a panel anchored at the top of the screen stayed where it was: sized
|
|
155
|
+
to the visible height, it showed only its lower part, with the header and the
|
|
156
|
+
greeting off the top and an empty band above the keyboard. That covered
|
|
157
|
+
`page`, `full`, `side` and `sidebar`, and at phone width the corner placements
|
|
158
|
+
too, because they become the whole screen there, and their keyboard lift never
|
|
159
|
+
moved them: a box with its top, height and bottom all set ignores the bottom.
|
|
160
|
+
The element now also publishes how far the visible area is panned, as
|
|
161
|
+
`--ag-ui-visual-viewport-inset-top`, and every panel anchored at the top moves
|
|
162
|
+
down by as much of it as goes past the host's `--ag-ui-viewport-inset-top`.
|
|
163
|
+
`--ag-ui-keyboard-inset-top` outranks that, as `--ag-ui-keyboard-inset` does
|
|
164
|
+
at the bottom; `0px` opts out.
|
|
165
|
+
|
|
166
|
+
The same panel with a bar reserved at the top also ran past the bottom of the
|
|
167
|
+
visible area by the bar's height whenever a keyboard was up, pan or no pan:
|
|
168
|
+
the measured height replaced the host's box rather than being cut from it, so
|
|
169
|
+
the bar stayed in the position and left the height. The height is now the
|
|
170
|
+
host's box cut to the visible area.
|
|
171
|
+
|
|
172
|
+
At the deepest pan, the one that shows a composer docked at the foot of the
|
|
173
|
+
screen, the panel still went back to the top of the page at full height. The
|
|
174
|
+
element read the page's height from `innerHeight`, which iOS Safari reports
|
|
175
|
+
less the pan for as long as the pan lasts; at that depth it equals the visible
|
|
176
|
+
height, so the element took the two viewports to agree and withdrew its
|
|
177
|
+
measurements. The band below the visible area came out as nothing at any pan
|
|
178
|
+
too, leaving a bottom-anchored panel behind the keyboard. The page's height
|
|
179
|
+
is now read from the root element's `clientHeight`, which an iPhone reported
|
|
180
|
+
unchanged through the pan.
|
|
181
|
+
|
|
182
|
+
- **A full-screen panel no longer runs under Safari's bars with no keyboard
|
|
183
|
+
up.** Where nothing has been measured, the panel's height fell back to
|
|
184
|
+
`100vh`, which iOS Safari resolves to the screen with its bars collapsed, so
|
|
185
|
+
on a page that never scrolls them away the foot of the panel sat under the
|
|
186
|
+
address bar: 40px on the iPhone measured, with a docked composer in it. The
|
|
187
|
+
fallback is now `100dvh`, the screen with the bars as they are.
|
|
188
|
+
|
|
189
|
+
- **An element removed from the document and inserted again is one element.**
|
|
190
|
+
Every insertion built the chrome again into the same shadow root, so a
|
|
191
|
+
re-inserted `<ag-ui-chat>` had two headers, two composers, a second set of
|
|
192
|
+
resize grips, and the conversation drawn a second time beneath the first. The
|
|
193
|
+
listeners on its long-lived parts were added again too:
|
|
194
|
+
- one click on Stop cancelled the run three times;
|
|
195
|
+
- one press of the built-in theme toggle flipped it twice, so it appeared to
|
|
196
|
+
do nothing;
|
|
197
|
+
- a dropped or pasted file was added twice;
|
|
198
|
+
- one arrow press moved the launcher two steps.
|
|
199
|
+
|
|
200
|
+
With `data-threads-url`, a second remote store wrapped the first, so every
|
|
201
|
+
rename and delete reached the server twice. Frameworks do this without being
|
|
202
|
+
asked (a portal, a keyed list, a move between containers), and removing and
|
|
203
|
+
re-inserting is the documented way to apply a connect-time attribute written
|
|
204
|
+
late.
|
|
205
|
+
|
|
206
|
+
Connecting now builds the chrome in place of the last connection's, and ties
|
|
207
|
+
every listener on a long-lived part to the connection, so leaving the document
|
|
208
|
+
removes them. The upload tray and the mic are mounted afresh. The thread store
|
|
209
|
+
is wrapped once, and a `data-threads-url` removed while detached takes its
|
|
210
|
+
remote store away. The transcript is rebuilt from history, as `reload()`
|
|
211
|
+
rebuilds it. The composer's recall history is kept. Re-inserting still applies
|
|
212
|
+
the attributes as they stand when the element comes back. A host that called
|
|
213
|
+
`offerQuoteInPage()` calls it again after re-inserting, as before.
|
|
214
|
+
|
|
10
215
|
## [0.39.0] — 2026-09-17
|
|
11
216
|
|
|
12
217
|
### Changed
|
|
@@ -3973,7 +4178,9 @@ hosts that both arrange the page the way it expects.
|
|
|
3973
4178
|
### Notes
|
|
3974
4179
|
- First release — exercising the automated npm OIDC publish pipeline end-to-end.
|
|
3975
4180
|
|
|
3976
|
-
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.
|
|
4181
|
+
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.0...HEAD
|
|
4182
|
+
[0.41.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.40.0...v0.41.0
|
|
4183
|
+
[0.40.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.39.0...v0.40.0
|
|
3977
4184
|
[0.39.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.38.0...v0.39.0
|
|
3978
4185
|
[0.38.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.37.0...v0.38.0
|
|
3979
4186
|
[0.37.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.36.0...v0.37.0
|
package/README.md
CHANGED
|
@@ -253,6 +253,11 @@ The list: `data-attachments-url`, `data-attachment-accept`, `data-attachment-max
|
|
|
253
253
|
`data-skills-url`, `data-skills`, `data-prompt-chips`, `data-slash-commands`, `data-theme-toggle`,
|
|
254
254
|
`data-strings`, `data-icon-url`.
|
|
255
255
|
|
|
256
|
+
What re-inserting costs: the chrome is built again from the attributes as they stand, a run in
|
|
257
|
+
progress is cancelled, and the conversation is redrawn from its stored history — the same rebuild
|
|
258
|
+
`reload()` performs, and the reason that method exists for a host that can only configure the
|
|
259
|
+
element after the fact.
|
|
260
|
+
|
|
256
261
|
**Properties** (JS only, not attributes): `headers`, `getHeaders`, `trustedOrigins`, `allowImages`,
|
|
257
262
|
`autoConfirm`, `confirmPredicate`, `askUser`, `askUserRenderer`, `approvalRenderer`,
|
|
258
263
|
`approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
|
|
@@ -300,10 +305,13 @@ the `copyCode` / `copied` / `copyFailed` strings.
|
|
|
300
305
|
|
|
301
306
|
`sendMessage(content, attachments?)` sends as if the user had typed it — user bubble,
|
|
302
307
|
`ag-ui-submit` event, run started. Use it for an "Ask about this order" button, a command
|
|
303
|
-
palette, or a composer of your own replacing the built-in one. It no-ops
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
palette, or a composer of your own replacing the built-in one. It no-ops for an entirely
|
|
309
|
+
empty message, and while a run — or a checkpoint continuation picked from the panel — is in
|
|
310
|
+
flight; a continuation counts from the pick, not from its first event, so the gap where two
|
|
311
|
+
runs could start against one conversation is closed. Unlike the built-in Send it does **not**
|
|
312
|
+
queue, so your composer keeps what it tried to send, and it does **not** consult the
|
|
313
|
+
attachment tray: what you pass is what is sent, so your composer stays in charge of its own
|
|
314
|
+
state.
|
|
307
315
|
|
|
308
316
|
`attachFile(file)` queues a file into the tray exactly as the picker and drag-and-drop do, with
|
|
309
317
|
the same validation and progress chip. It returns `false` when uploads are not configured
|
|
@@ -561,8 +569,9 @@ the current page state.
|
|
|
561
569
|
|
|
562
570
|
#### A tool call that failed
|
|
563
571
|
|
|
564
|
-
A `TOOL_CALL_RESULT` may
|
|
565
|
-
pydantic-ai's own `ToolReturnPart`
|
|
572
|
+
A `TOOL_CALL_RESULT` may say how the call ended, as an optional **`outcome`** key in the event's
|
|
573
|
+
`metadata` (`"metadata": { "outcome": "failed" }`). It takes pydantic-ai's own `ToolReturnPart`
|
|
574
|
+
vocabulary — read the values off `TOOL_OUTCOME`:
|
|
566
575
|
|
|
567
576
|
| `outcome` | Card | Means |
|
|
568
577
|
| --- | --- | --- |
|
|
@@ -578,18 +587,23 @@ recognise a word is not grounds for claiming failure. So no server has to change
|
|
|
578
587
|
rendering it has today, and a server that adds the field gets the truth on screen instead of a
|
|
579
588
|
green card with a refusal folded inside it.
|
|
580
589
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
590
|
+
It has to be in `metadata`. `@ag-ui/client` 1.0 strips every key its schemas do not declare from an
|
|
591
|
+
event before any subscriber sees it, with a `[ag-ui][enforce]` console warning, and `metadata` is
|
|
592
|
+
the one place on an event it declares open by key. An `outcome` at the top level of the event —
|
|
593
|
+
where a server wrote it for the 0.x client, and where a django-ag-ui older than 0.63.0 still does —
|
|
594
|
+
never arrives, so the card settles as done. The key arrives on `AgUiClientHandlers.onToolResult` as
|
|
595
|
+
an optional third argument, typed `unknown` because the protocol validates nothing inside
|
|
596
|
+
`metadata`; `toolStatusFromOutcome` does the narrowing.
|
|
585
597
|
|
|
586
598
|
A **frontend** tool never streams this event — the client posts its own result — so its outcome is
|
|
587
599
|
recorded on the `ToolExecution` the executor returns (`{ content, outcome: "denied" }`). That covers
|
|
588
600
|
the two refusals the component makes itself: a confirmation card the user declined, and a call
|
|
589
601
|
blocked because the page moved under the round.
|
|
590
602
|
|
|
591
|
-
Either way the outcome is **persisted
|
|
592
|
-
settled rather than as a plain result.
|
|
603
|
+
Either way the outcome is **persisted on the tool message, in its `metadata`**, so a reload replays
|
|
604
|
+
the card as it settled rather than as a plain result. For a server's result `@ag-ui/client` puts it
|
|
605
|
+
there itself, folding the event's metadata onto the tool message it appends; for its own results the
|
|
606
|
+
client writes it in the same place. See
|
|
593
607
|
[MPA durability](#mpa-durability-surviving-full-page-reloads).
|
|
594
608
|
|
|
595
609
|
**Every request carries a result for every tool call in it.** Several model providers reject a
|
|
@@ -869,6 +883,13 @@ chat.askUserRenderer = (request, { signal }) =>
|
|
|
869
883
|
myModal.ask(request.question, request.options, { allowCustom: request.allowCustom, signal });
|
|
870
884
|
```
|
|
871
885
|
|
|
886
|
+
**A renderer that throws or rejects falls back to the built-in card** for that call, with a
|
|
887
|
+
`console.warn` naming the tool call. As with `approvalRenderer`, the renderer decides how the
|
|
888
|
+
question looks, not whether it is asked, so the run carries on as if no renderer were set. The one
|
|
889
|
+
exception is a rejection after the signal fired, which is how a renderer is expected to honour Stop:
|
|
890
|
+
that wait resolves with an empty answer, as the built-in card does on the same signal, with no card
|
|
891
|
+
and no warning.
|
|
892
|
+
|
|
872
893
|
### DOM-driver and animation primitives
|
|
873
894
|
|
|
874
895
|
So the agent can visibly drive the host page, the package ships generic, framework-free
|
|
@@ -1082,7 +1103,10 @@ chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
|
|
|
1082
1103
|
### The composer's own keys
|
|
1083
1104
|
|
|
1084
1105
|
**Enter during a run queues.** A second run cannot start while one is in flight —
|
|
1085
|
-
it would orphan the first — so that key used to do nothing at all, silently.
|
|
1106
|
+
it would orphan the first — so that key used to do nothing at all, silently. A
|
|
1107
|
+
checkpoint continuation picked from the panel counts as in flight from the pick
|
|
1108
|
+
rather than from its first event, which is a request later, so a turn typed in
|
|
1109
|
+
that gap is parked too rather than racing it. What
|
|
1086
1110
|
is waiting shows above the composer as chips, each of which takes its message
|
|
1087
1111
|
back when pressed, and the next one is sent when the run settles. Stopping the
|
|
1088
1112
|
run discards them: sending into a conversation someone has just stopped is the
|
|
@@ -2001,16 +2025,23 @@ On mount the element rehydrates the transcript from the store, so the chat looks
|
|
|
2001
2025
|
including tool-call cards and their results (reconstructed from the persisted `toolCalls` and `tool`
|
|
2002
2026
|
messages), not just the text turns.
|
|
2003
2027
|
|
|
2004
|
-
A tool message is saved with an
|
|
2005
|
-
the same vocabulary the wire uses (see [A tool call that failed](#a-tool-call-that-failed)),
|
|
2006
|
-
failed or declined card replays as failed or declined instead of turning green on reload.
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2028
|
+
A tool message is saved with an **`outcome`** in its `metadata` when the call did not simply
|
|
2029
|
+
succeed, in the same vocabulary the wire uses (see [A tool call that failed](#a-tool-call-that-failed)),
|
|
2030
|
+
so a failed or declined card replays as failed or declined instead of turning green on reload. A user
|
|
2031
|
+
message keeps the refs of its uploads the same way, under `attachments` in its `metadata`. Both
|
|
2032
|
+
round-trip through `JSON.stringify` in the default store. **A store that drops `metadata` loses only
|
|
2033
|
+
the distinction** — the card falls back to *done*, which is what it did before, and the bubble loses
|
|
2034
|
+
its chips.
|
|
2035
|
+
|
|
2036
|
+
What is saved is exactly what the next request sends, so both go back to the server on every run.
|
|
2037
|
+
That is the protocol's own behaviour for a declared field rather than a client-side addition: a server
|
|
2038
|
+
validates `metadata` as an open object, and one that does not read a key ignores it — django-ag-ui
|
|
2039
|
+
reads `attachments` and ignores `outcome`.
|
|
2040
|
+
|
|
2041
|
+
A conversation stored by an earlier release has both keys at the top level of the message instead.
|
|
2042
|
+
The history replay reads them from there when the metadata has none, and a client seeded from such a
|
|
2043
|
+
conversation moves them into `metadata` before its first request, because `@ag-ui/client` 1.0 would
|
|
2044
|
+
strip them from the request as it stands. The next save writes them in the new shape.
|
|
2014
2045
|
|
|
2015
2046
|
**3. Resumable loop (`x-navigates` + `navigationResult`).** A tool whose schema carries
|
|
2016
2047
|
`x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/tools/is_navigates.ts))
|
|
@@ -2384,6 +2415,12 @@ Picking a row with an empty composer says so above the input and puts the caret
|
|
|
2384
2415
|
there, rather than closing the panel over nothing: a continuation sends **only**
|
|
2385
2416
|
the new turn, so with nothing typed there is nothing to send.
|
|
2386
2417
|
|
|
2418
|
+
Picking a row while an answer is still streaming — the conversation's own, or
|
|
2419
|
+
another continuation's — is refused the same way: the hint asks you to wait or
|
|
2420
|
+
stop it, and what you typed stays in the composer for the pick after. The run in
|
|
2421
|
+
flight is not cancelled for you, since a continuation starting over it would
|
|
2422
|
+
leave two answers streaming into one transcript with Stop reaching only one.
|
|
2423
|
+
|
|
2387
2424
|
### One URL, three endpoints
|
|
2388
2425
|
|
|
2389
2426
|
`data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`
|
|
@@ -2401,7 +2438,7 @@ The component satisfies that structurally rather than by remembering a rule. A
|
|
|
2401
2438
|
continuation runs on its own short-lived agent, built pointing at the resume
|
|
2402
2439
|
endpoint and seeded with **no** history — so "only the new turn" is the only
|
|
2403
2440
|
thing it *can* send, and the fresh run id comes free because a new agent mints
|
|
2404
|
-
one.
|
|
2441
|
+
one. Nothing of the conversation's history goes out on its request.
|
|
2405
2442
|
|
|
2406
2443
|
A resumed run is a normal run in every other respect: frontend tools execute,
|
|
2407
2444
|
approval interrupts render their card, `headers` are re-read per request so
|
|
@@ -2411,9 +2448,14 @@ a rotated CSRF token or JWT still reaches the endpoint, it carries and updates
|
|
|
2411
2448
|
construction as the conversation's own, differing only in the endpoint and the
|
|
2412
2449
|
empty seed.
|
|
2413
2450
|
|
|
2414
|
-
|
|
2415
|
-
the answer, and a store keeps one message list per thread, so
|
|
2416
|
-
|
|
2451
|
+
The continued exchange joins the conversation. Its agent holds only the new turn
|
|
2452
|
+
and the answer, and a store keeps one message list per thread, so each save
|
|
2453
|
+
writes the conversation as it stood when you picked the row with the exchange
|
|
2454
|
+
after it — through the same `saveMessages` as any other run, and as far as it
|
|
2455
|
+
got if it was stopped. A reload then replays the exchange, and the next message
|
|
2456
|
+
you send, now to `endpoint` again, carries it together with the shared state the
|
|
2457
|
+
continuation left. What is saved is what the transcript shows: a fork's exchange
|
|
2458
|
+
follows the conversation it was picked from, in that same thread.
|
|
2417
2459
|
|
|
2418
2460
|
If the index can't be reached, the panel shows its empty state rather than an
|
|
2419
2461
|
error — a history affordance that fails is empty, not broken.
|
|
@@ -2426,7 +2468,10 @@ out-of-band (multipart, with the element's `headers`) and shows a chip in a pend
|
|
|
2426
2468
|
`uploading` (with a progress bar) → `ready`, or `error` with a retry. On send, the ready files'
|
|
2427
2469
|
**refs** ride on the user bubble as read-only chips and the agent reads their contents
|
|
2428
2470
|
server-side via the `read_attachment` tool. The wire stays vanilla AG-UI: only lightweight refs
|
|
2429
|
-
(`{ id, name, mime, size }`) travel, never the bytes
|
|
2471
|
+
(`{ id, name, mime, size }`) travel, never the bytes, in the user message's `metadata` under
|
|
2472
|
+
`attachments` — the one place on a message `@ag-ui/client` 1.0 does not strip an unknown key from.
|
|
2473
|
+
django-ag-ui reads them from there as of 0.63.0; an older one reads only the top-level key 1.0
|
|
2474
|
+
removes, so against it the agent is never told a file was attached.
|
|
2430
2475
|
|
|
2431
2476
|
### Pasting
|
|
2432
2477
|
|
|
@@ -2597,7 +2642,7 @@ re-export point. Internal modules import from leaf paths.
|
|
|
2597
2642
|
| `UploadOptions` | type | `{ url, headers?, credentials?, onProgress?, signal? }`. `credentials` is spelled as a fetch mode but carried by `XMLHttpRequest.withCredentials`, so only `"include"` is distinguishable. |
|
|
2598
2643
|
| `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. |
|
|
2599
2644
|
| `AttachmentRef` | type | The durable upload ref (`{ id, name, mime, size, url? }`). |
|
|
2600
|
-
| `messageAttachments(message)` | function | Read the refs a restored user message carries. |
|
|
2645
|
+
| `messageAttachments(message)` | function | Read the refs a restored user message carries: from its `metadata`, or from the top level where an earlier release stored them. |
|
|
2601
2646
|
|
|
2602
2647
|
### Voice input
|
|
2603
2648
|
|
|
@@ -2745,6 +2790,12 @@ of the screen with a frame drawn round it.
|
|
|
2745
2790
|
`embedded` is left alone deliberately: it sits in a box you sized and placed, and
|
|
2746
2791
|
only you know whether that column should become the whole screen.
|
|
2747
2792
|
|
|
2793
|
+
The [greeting layout](#the-greeting-on-an-empty-page) changes shape here too, and
|
|
2794
|
+
it is the one part of this that `embedded` gets as well, because it moves nothing
|
|
2795
|
+
about the box you placed: an empty conversation keeps its composer at the foot of
|
|
2796
|
+
the panel, with the starters against it and the greeting over the space above
|
|
2797
|
+
them, instead of centring all three together.
|
|
2798
|
+
|
|
2748
2799
|
The corner placements still rest at their launcher, so a full-bleed panel is
|
|
2749
2800
|
something the user opens rather than something they are given.
|
|
2750
2801
|
|
|
@@ -2831,11 +2882,25 @@ widget measures the hidden band and publishes it as
|
|
|
2831
2882
|
`--ag-ui-visual-viewport-inset-bottom`; state this one instead to outrank that
|
|
2832
2883
|
measurement, or set it to `0px` to opt out of the lift entirely.
|
|
2833
2884
|
|
|
2885
|
+
`--ag-ui-keyboard-inset-top` does the same at the top. To show a field a
|
|
2886
|
+
keyboard would cover, a mobile browser pans the visible area down the page, and
|
|
2887
|
+
a panel anchored to the screen (`page`, `full`, `side`, `sidebar`, and at phone
|
|
2888
|
+
width the corner placements too) moves down with it. An `embedded` panel renders
|
|
2889
|
+
in your own box rather than against the screen, so it does not move at any width.
|
|
2890
|
+
The widget publishes the pan as `--ag-ui-visual-viewport-inset-top`, and the
|
|
2891
|
+
panel moves by as much of it as goes past your `--ag-ui-viewport-inset-top`,
|
|
2892
|
+
since the pan scrolls a reserved bar away with the page. State
|
|
2893
|
+
`--ag-ui-keyboard-inset-top` to outrank that distance, or `0px` to keep the
|
|
2894
|
+
panel below your reserved top.
|
|
2895
|
+
|
|
2834
2896
|
`--ag-ui-viewport-height` and `--ag-ui-viewport-width` state the usable box
|
|
2835
2897
|
outright, for the case where no viewport-percentage length describes it. An
|
|
2836
2898
|
on-screen keyboard is the one that matters: it changes neither `vh` nor `dvh` nor
|
|
2837
2899
|
`svh` on any current mobile browser, so a full-bleed panel has to be told the
|
|
2838
|
-
visual viewport's height rather than deriving it.
|
|
2900
|
+
visual viewport's height rather than deriving it. The widget does that itself;
|
|
2901
|
+
where it has nothing to report, the panel falls back to `100dvh` rather than
|
|
2902
|
+
`100vh`, because iOS Safari resolves `vh` to the screen with its bars collapsed
|
|
2903
|
+
and a panel sized from it runs under them.
|
|
2839
2904
|
|
|
2840
2905
|
Marks are variables too, so one vocabulary covers a re-theme rather than
|
|
2841
2906
|
leaving half the transcript in the built-in set: `--ag-ui-tool-icon-done` /
|
|
@@ -2961,7 +3026,7 @@ component sets, so a new one cannot ship undocumented.
|
|
|
2961
3026
|
| Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
|
|
2962
3027
|
| Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
|
|
2963
3028
|
| 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` |
|
|
2964
|
-
| Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation with nothing typed) |
|
|
3029
|
+
| Skills | `skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`, `skill-item-desc`, `skill-item-token`, `skill-hint` (the composer hint: a skill’s missing placeholders, and a run continuation picked with nothing typed or while a run is in flight) |
|
|
2965
3030
|
| Thread drawer | `drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`, `drawer-close`, `drawer-filter`, `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`, `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`, `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`, `drawer-confirm-yes`, `drawer-confirm-no` |
|
|
2966
3031
|
| Charts | `chart-block`, `chart-title`, `chart-legend` |
|
|
2967
3032
|
| Checkpoints panel | `checkpoints`, `checkpoints-header`, `checkpoints-title`, `checkpoints-list`, `checkpoints-empty`, `checkpoint-row`, `checkpoint-label`, `checkpoint-time`, `checkpoint-id`, `checkpoint-branch`, `checkpoint-action` (plus `checkpoint-resume`, `checkpoint-fork`) |
|
|
@@ -3050,7 +3115,8 @@ naturally with the [answer well](#the-answer-well).
|
|
|
3050
3115
|
#### The greeting on an empty page
|
|
3051
3116
|
|
|
3052
3117
|
Until the first message is sent, a page shows a greeting with the composer centred beneath it,
|
|
3053
|
-
and the composer moves to the foot of the page once the conversation has something in it.
|
|
3118
|
+
and the composer moves to the foot of the page once the conversation has something in it. On a
|
|
3119
|
+
phone it starts at the foot, for the reason below.
|
|
3054
3120
|
|
|
3055
3121
|
```html
|
|
3056
3122
|
<ag-ui-chat endpoint="/agent/" placement="page" user-name="Ada"></ag-ui-chat>
|
|
@@ -3071,6 +3137,14 @@ and the composer moves to the foot of the page once the conversation has somethi
|
|
|
3071
3137
|
restores the plain layout there. `embedded` opts in with `data-greeting` (any value but `off`),
|
|
3072
3138
|
for an app shell that gives the panel a page-sized box. The corner placements and the sidebar
|
|
3073
3139
|
never show it: a panel opened from a launcher is already mid-task.
|
|
3140
|
+
- **On a phone** the composer stays at the foot, the starters (or your own `empty` content) sit
|
|
3141
|
+
against it, and the greeting takes the middle of what they leave — the middle of the
|
|
3142
|
+
transcript when there are none. Centring is a shape for a screen with room to spare; at
|
|
3143
|
+
[600px wide and below](#small-viewports) the room is what the on-screen keyboard takes, and a
|
|
3144
|
+
composer centred over an empty band sits halfway up what is left of the screen, with the
|
|
3145
|
+
prompts that start a conversation nowhere near the field that sends one.
|
|
3146
|
+
`data-small-viewport="off"` keeps the centred shape at every width, as it keeps the rest of
|
|
3147
|
+
the desktop layout.
|
|
3074
3148
|
|
|
3075
3149
|
The composer's rows stay centred while you type, while a draft grows and while attachments are
|
|
3076
3150
|
added, because the centring is two equal flexible halves either side of them and growth splits
|
|
@@ -3107,9 +3181,9 @@ When a reasoning model streams its chain-of-thought (django-ag-ui forwards it as
|
|
|
3107
3181
|
events — enable a thinking budget via `MODEL_SETTINGS`, see its docs), the element renders a muted,
|
|
3108
3182
|
collapsible **thoughts region** (part `thoughts`) at the top of the current answer group. It opens
|
|
3109
3183
|
while the model reasons and folds away on the answer's first token; the reader can reopen it. The
|
|
3110
|
-
web component handles the `REASONING_*` event family (and the
|
|
3111
|
-
`@ag-ui/client`
|
|
3112
|
-
server forwards reasoning.
|
|
3184
|
+
web component handles the `REASONING_*` event family (and the retired `THINKING_*`, which
|
|
3185
|
+
`@ag-ui/client` still converts onto it, with a console warning, during its deprecation window), so
|
|
3186
|
+
no client config is needed — the thoughts appear whenever the server forwards reasoning.
|
|
3113
3187
|
|
|
3114
3188
|
### The composer
|
|
3115
3189
|
|
|
@@ -3240,11 +3314,17 @@ Other workflow targets (all identical in name to the sibling Python packages):
|
|
|
3240
3314
|
| --- | --- | --- |
|
|
3241
3315
|
| Node (tooling/tests only) | 22 | 22, 24 |
|
|
3242
3316
|
| Browsers (runtime target) | ES2022 / evergreen | Chrome / Firefox / Safari 17+ |
|
|
3243
|
-
| `@ag-ui/client` |
|
|
3317
|
+
| `@ag-ui/client`, `@ag-ui/core` | 1.0 | 1.0 |
|
|
3318
|
+
| django-ag-ui (server) | 0.63.0 | — |
|
|
3244
3319
|
|
|
3245
3320
|
The shipped artefact targets evergreen browsers (Shadow DOM, Custom Elements v1, ES2022). Node is
|
|
3246
3321
|
only the build/test runtime, not a runtime target.
|
|
3247
3322
|
|
|
3323
|
+
The server floor is a wire contract rather than a dependency. A tool call's outcome and a user
|
|
3324
|
+
message's attachment refs travel in `metadata`, since `@ag-ui/client` 1.0 strips every key its
|
|
3325
|
+
schemas do not declare. Against an older django-ag-ui the chat still runs, but the agent is never
|
|
3326
|
+
told about an attached file and a failed or declined server-side tool call settles as done.
|
|
3327
|
+
|
|
3248
3328
|
---
|
|
3249
3329
|
|
|
3250
3330
|
## License
|