@useago/sdk 1.5.6 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/README.md +59 -0
  3. package/dist/{AgoClient-B_R_qi2s.js → AgoClient-BJjLTOw4.js} +658 -388
  4. package/dist/AgoClient-B_u_d-XS.cjs +9 -0
  5. package/dist/angular.cjs +1 -1
  6. package/dist/angular.d.cts +230 -5
  7. package/dist/angular.d.ts +230 -5
  8. package/dist/angular.js +10 -1
  9. package/dist/{createConversationSession-Dqh7NXK4.cjs → createConversationSession-BCdwAhLF.cjs} +1 -1
  10. package/dist/{createConversationSession-CdtI4vrf.js → createConversationSession-Bgvx753t.js} +1 -1
  11. package/dist/{createFormCollector-YwaZ78wU.js → createFormCollector-CM3qjGcM.js} +1 -1
  12. package/dist/{createFormCollector-BjO3oB63.cjs → createFormCollector-ssBP8u0p.cjs} +1 -1
  13. package/dist/{createMockClient-DcuNlufr.js → createMockClient-BDLW6tVc.js} +5 -2
  14. package/dist/createMockClient-BSNuN3ys.cjs +1 -0
  15. package/dist/devtools.d.cts +220 -5
  16. package/dist/devtools.d.ts +220 -5
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.cts +238 -6
  19. package/dist/index.d.ts +238 -6
  20. package/dist/index.js +111 -109
  21. package/dist/react.cjs +40 -3
  22. package/dist/react.d.cts +419 -7
  23. package/dist/react.d.ts +419 -7
  24. package/dist/react.js +1195 -743
  25. package/dist/sheetController-CBUVOroo.js +205 -0
  26. package/dist/sheetController-DY-sC13T.cjs +1 -0
  27. package/dist/testing.cjs +1 -1
  28. package/dist/testing.d.cts +220 -5
  29. package/dist/testing.d.ts +220 -5
  30. package/dist/testing.js +1 -1
  31. package/dist/vue.cjs +1 -1
  32. package/dist/vue.d.cts +235 -5
  33. package/dist/vue.d.ts +235 -5
  34. package/dist/vue.js +121 -100
  35. package/dist/widget.cjs +4 -4
  36. package/dist/widget.d.cts +328 -5
  37. package/dist/widget.d.ts +328 -5
  38. package/dist/widget.js +721 -629
  39. package/llms-full.txt +202 -14
  40. package/package.json +1 -1
  41. package/dist/AgoClient-DLB9icvv.cjs +0 -8
  42. package/dist/createMockClient-Dw-cyISN.cjs +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,239 @@ All notable changes to `@useago/sdk` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.7.1] - 2026-08-10
9
+
10
+ ### Fixed
11
+
12
+ - The `setPageState` function synthesized by `registerPageStateFunction` /
13
+ `useAgoPageState` now sends an explicit `required: []` in its parameter
14
+ schema. Backends that treat a missing `required` array as "every property is
15
+ required" forced the agent to set all controls on every call; controls are
16
+ optional, and the agent sets only the ones the user asked for.
17
+
18
+ ## [1.7.0] - 2026-08-07
19
+
20
+ ### Added
21
+
22
+ - `registerPageStateFunction` / `useAgoPageState` accept a `data` source, so the
23
+ agent gets back **what the page displays** as the result of its own
24
+ `setPageState` call instead of only as context on the next message. An agent
25
+ that filters a list can now say what it found.
26
+
27
+ ```ts
28
+ useAgoPageState(controls, {
29
+ data: {
30
+ description: "The users matching the current filters.",
31
+ get: () => users ?? [],
32
+ isLoading: () => isFetching, // isFetching, not isLoading
33
+ },
34
+ });
35
+ ```
36
+
37
+ `setPageState` then returns `{ success, applied, data }`. Declaring `data`
38
+ also registers a read-only `readPageData` companion (no parameters, changes
39
+ nothing, returns `{ data }`) for "what's on screen?" questions; it is removed
40
+ by `unregisterPageStateFunction`.
41
+ - The SDK waits for the work a control change triggered before reading the
42
+ snapshot. Two ways to tell it when that work is done:
43
+ - **`get()` returns a promise** (preferred): awaiting it *is* the completion
44
+ signal, so the SDK is exact. `get: () => queryClient.ensureQueryData({...})`
45
+ shares the single in-flight request with the UI.
46
+ - **`isLoading` is declared**: the SDK polls it every 50 ms and treats two
47
+ consecutive idle readings as settled. A heuristic, and a flag raised more
48
+ than ~100 ms after the change (a debounced fetch) is missed, so the agent
49
+ reads the previous rows.
50
+
51
+ Both are capped by `settleTimeoutMs` (10 s by default); a promise that never
52
+ settles no longer holds the turn open, and a rejected `get()` is reported to
53
+ the agent as a failed read instead of an empty page.
54
+ - Oversized snapshots are truncated **by item**: the first whole items that fit
55
+ the budget (`data.maxResultBytes`, defaulting to `maxFunctionResultBytes`) plus
56
+ a `truncation` field carrying `{ truncated, returnedItems, totalItems, hint }`.
57
+ The counts are nested so they cannot overwrite a same-named field of your own
58
+ snapshot. `success` and `applied` are never dropped, so a filter that worked is
59
+ never reported as an unusable blob. When the snapshot's other fields overflow
60
+ on their own, it falls back to a size-bounded preview rather than returning
61
+ something over the ceiling.
62
+ - A `readPageData` companion is only removed by the SDK if the SDK registered it,
63
+ so a host app that already has a function by that name keeps it, and
64
+ re-registering a page-state function without `data` drops the stale companion
65
+ instead of letting it answer with the previous page's rows.
66
+ - `AgoPageDataSource` is exported from the root, `/react`, `/vue` and `/angular`
67
+ entries.
68
+ - `<ChatWidget>` now says **what the agent is doing** while it works, instead of
69
+ three dots. While a client function is running it shows a status row naming it
70
+ ("Updating the page", "Looking up prices"), which matters most during a
71
+ pause/resume loop: the per-message dots disappear as soon as the first token
72
+ lands, so a tool pause after some text had been written showed nothing at all.
73
+
74
+ ```tsx
75
+ <ChatWidget functionLabels={{ lookupFlavorPrices: "Je regarde les prix" }} />
76
+ ```
77
+
78
+ The row shows the latest step of the current turn and **keeps it until
79
+ something replaces it** (another call, a reasoning step) or the turn ends.
80
+ Clearing it when the call returned made a fast function flash for a few frames
81
+ and read as nothing at all. The agent's `reasoning` steps are included by
82
+ default (`showReasoning={false}` to leave them out), so the row does not go
83
+ blank between two calls.
84
+
85
+ `functionLabels` takes a map, or a function `(name, args) => string` for full
86
+ control. Unmapped names fall back to a prettified function name, so the row
87
+ works with no configuration. Pass `false` to keep the plain indicator. The row
88
+ is a `role="status"` live region and is driven by the existing
89
+ `useAgoActivity` hook, so it covers server-side tool calls and approval holds
90
+ too. It is scoped to the assistant message being written, so a new turn never
91
+ opens showing the previous one's last step.
92
+
93
+ The label itself is animated: a slow sheen keeps travelling across the text,
94
+ so a step that sits for several seconds (a fetch, a settle wait) reads as
95
+ still working rather than stuck, and each new step fades in as its own node so
96
+ one visibly gives way to the next instead of the characters swapping in place.
97
+ Colours go through `--ago-activity-color` / `--ago-activity-sheen` /
98
+ `--ago-activity-bg`; the sheen is behind `@supports (background-clip: text)`
99
+ and stops under `prefers-reduced-motion`.
100
+
101
+ The label carries no animated dots, and the answer bubble above it drops its
102
+ own while the row is up: the text already reads as progress, and the two
103
+ together were just noise. `<Message>` takes a `showStreamingDots` prop for
104
+ the same reason in a custom UI (`false` removes the empty bubble entirely
105
+ rather than leaving a blank box). It renders simple inline Markdown
106
+ (`**bold**`, `*italic*`, `` `code` ``) so a backend reasoning step keeps its
107
+ emphasis; block Markdown is deliberately not supported there, since a
108
+ paragraph or a table would break a one-line row.
109
+
110
+ Omit `data` and the result stays exactly `{ success, applied }`, byte for byte.
111
+ The snapshot stays out of the `page-state:` context entry, so it travels only
112
+ when the agent asks for it. The intra-turn round trip requires
113
+ `clientFunctionsMode: "pause"` (the default); the SDK warns once if `data` is
114
+ declared in `"placeholder"` mode.
115
+
116
+ ### Fixed
117
+
118
+ - Client functions executed on the resume path (after a reload, via
119
+ `resumePendingClientFunctions`) now emit `function:invoke` and
120
+ `function:result` like the live stream does. A UI driven by those events used
121
+ to sit on a spinner for those calls.
122
+ - An agent calling the same client function twice in one turn no longer strands
123
+ the turn. The per-stream dedupe (which exists because the backend emits one
124
+ call under two SSE shapes) keyed on function name + arguments alone, so a
125
+ second call with identical arguments was swallowed: its result was never
126
+ submitted, the backend never saw every result, and the paused turn stayed in
127
+ `WAITING_CLIENT` forever. It now uses the invocation id to tell a repeat call
128
+ apart from a repeat *emission* of the same call. Zero-argument functions (such
129
+ as the new `readPageData`) hit this every time.
130
+ - Vue's `useAgoPageState` no longer drops `requiresApproval` (and now forwards
131
+ every option, matching React and Angular).
132
+
133
+ ## [1.6.2] - 2026-08-06
134
+
135
+ ### Fixed
136
+
137
+ - Side-placement panels (`placement: "left" | "right"`) now get the mobile
138
+ treatment the inline card already had. On a small viewport the open panel
139
+ tracks the visible viewport, so the on-screen keyboard no longer covers the
140
+ composer, and it is exposed as a real dialog (`role="dialog"`, `aria-modal`,
141
+ Tab trap, Escape to close). Opening it moves focus into the panel rather than
142
+ the text field, so the keyboard no longer opens unprompted.
143
+ - A desktop side panel is explicitly **not** modal: it no longer traps Tab or
144
+ locks the page behind it, so the host page stays usable beside it.
145
+ - The background scroll lock now restores the host page's own inline `<body>` /
146
+ `<html>` styles instead of deleting them. A host that had already pinned its
147
+ own body (for its own modal) is left exactly as it was found.
148
+ - The scroll lock is tracked per owner instead of by a counter, so overlapping
149
+ open / close / destroy paths can no longer release a lock they don't hold,
150
+ and `destroy()` is idempotent.
151
+ - `destroy()` during an opening animation no longer re-pins the page after
152
+ teardown, which could leave `<body>` fixed with no owner left to release it.
153
+ - `destroy()` now drops its keydown and `visualViewport` listeners for every
154
+ placement; side-placement widgets previously leaked them.
155
+ - A phone in landscape (short viewport, e.g. 844x390) now counts as mobile, so
156
+ it keeps the full-screen sheet and the keyboard handling instead of falling
157
+ back to the desktop layout mid-conversation.
158
+ - A streamed answer no longer rebuilds the whole thread on every token. Screen
159
+ readers announced the entire conversation per chunk, and the rebuild collapsed
160
+ the pane's scroll height, which is what made a "follow the bottom" check latch
161
+ off after the first token.
162
+ - The message pane now follows new content only while you are already at the
163
+ bottom. Scroll up to re-read something and the stream stops yanking you back;
164
+ a "jump to latest" button returns you. Sending always re-attaches.
165
+ - On a compact viewport the composer is no longer re-focused after every answer,
166
+ which was re-opening the on-screen keyboard over the reply you were reading.
167
+ - A failed send no longer destroys the message: the text (and any attachments)
168
+ go back into the composer instead of being lost.
169
+ - Touch targets are at least 44px across both the vanilla widget and the React
170
+ components: send, stop, attach, remove-attachment, suggested replies, and both
171
+ close buttons.
172
+
173
+ ### Added
174
+
175
+ - Bottom-sheet presentation for compact viewports, opt-in via the new `sheet`
176
+ option on the React `<ChatWidget>`. It rests as a `peek` bar pinned to the
177
+ bottom edge (named header, two-line preview of the last reply, composer) and
178
+ expands to a `full` screen with `role="dialog"`, a background scroll lock and
179
+ Escape to collapse. `peek` is deliberately never modal: the page behind it
180
+ stays scrollable and keyboard-reachable.
181
+ - `createSheetController` (`@useago/sdk/widget`): the framework-agnostic state
182
+ machine behind it. It returns a **props bag** rather than touching the DOM, so
183
+ the React binding, the vanilla widget and any Vue or Angular binding drive the
184
+ same implementation.
185
+ - `useSheet` (`@useago/sdk/react`), which reads that controller through
186
+ `useSyncExternalStore` with a non-compact server snapshot, so `matchMedia`
187
+ never runs during render and hydration matches.
188
+ - `<ChatWidget>` now forwards a ref exposing `expand()`, `collapse()`,
189
+ `toggle()` and the current `state`.
190
+ - `sheet.bottomOffset` leaves room for a host page that already owns the bottom
191
+ edge (a bottom nav, a sticky checkout bar). `sheet: false` (the default) keeps
192
+ the widget out of the host's fixed layers entirely.
193
+ - `thinkingLabel` on `<ChatWidget>`: what the collapsed sheet says next to the
194
+ animated dots while the agent is working and has produced no text yet.
195
+ - `--ago-sheet-height`: the collapsed sheet measures itself and publishes its
196
+ footprint as a custom property on the host document. A fixed bar covers the end
197
+ of the page, so hosts need to reserve that space, and a constant in their CSS
198
+ would drift from the real height (which depends on their own composer styling).
199
+ Use it as `padding-bottom: var(--ago-sheet-height, 0px)`.
200
+
201
+ ### Changed
202
+
203
+ - The streaming dots now paint in `currentColor` instead of a fixed grey, so
204
+ they stay legible on a dark surface as well as a light one.
205
+ - React `<ChatInput>`'s `onSend` may now return a boolean (or a promise of one).
206
+ Returning `false` tells the composer the send failed so it can restore the
207
+ draft. Existing handlers that return nothing keep working unchanged.
208
+
209
+ ## [1.6.1] - 2026-08-05
210
+
211
+ ### Added
212
+
213
+ - The React `<ChatInput>` textarea now exposes an
214
+ `ago-chat-input__field` class for stable custom styling.
215
+
216
+ ### Fixed
217
+
218
+ - React `<ChatWidget>` auto-scroll now stays inside the message pane instead of
219
+ moving ancestor containers or the whole page, and preserves the reader's
220
+ position while they review earlier messages.
221
+
222
+ ## [1.6.0] - 2026-08-04
223
+
224
+ ### Added
225
+
226
+ - Stop generation. `client.stop()` interrupts the turn being generated: it
227
+ closes the stream and calls the new `POST /api/sdk/v1/messages/{id}/stop`, so
228
+ the agent really stops instead of finishing in the background. The text
229
+ produced so far is kept and the message ends as `CANCELED`; the in-flight
230
+ `sendMessage` resolves with it rather than rejecting.
231
+ - `message:stopped` event, fired once the stopped turn has unwound.
232
+ - `client.stopMessage(messageId)` to stop a turn by id (e.g. one still running
233
+ after a page reload), and `client.isGenerating()` to know whether there is
234
+ anything to stop.
235
+ - `stop()` on the React `useMessages` / `useChat` hooks, the Vue `useMessages` /
236
+ `useChat` composables, the Angular `AgoService`, and the vanilla widget handle.
237
+ - The React `<ChatWidget>` and the vanilla widget now show a Stop button in
238
+ place of the send button while the agent answers. Opt out with
239
+ `allowStop: false`.
240
+
8
241
  ## [1.5.6] - 2026-07-29
9
242
 
10
243
  ### Fixed
package/README.md CHANGED
@@ -145,6 +145,30 @@ so the agent changes only what the user asked for. The current value from each
145
145
  `get()` is sent with every message, so the agent knows the state before it
146
146
  changes it. Vue and Angular have the same helper.
147
147
 
148
+ Add a `data` source and the agent also gets back **what the page now shows**, as
149
+ the result of its own call, so it can answer "did you find dupont?" in the same
150
+ turn:
151
+
152
+ ```tsx
153
+ const { data: invoices, isFetching } = useQuery({
154
+ queryKey: ["invoices", status, sort],
155
+ queryFn: fetchInvoices,
156
+ });
157
+
158
+ useAgoPageState(controls, {
159
+ data: {
160
+ description: "The invoices matching the current filters.",
161
+ get: () => invoices ?? [],
162
+ isLoading: () => isFetching, // isFetching, not isLoading
163
+ },
164
+ });
165
+ ```
166
+
167
+ The SDK waits for `isLoading` to go false before reading, so the agent never
168
+ receives the previous search's rows, and it truncates a long list by whole rows
169
+ rather than sending a huge payload. Details in
170
+ [functions and context](docs/general/functions-and-context.md#return-what-the-page-displays).
171
+
148
172
  ## Turn a JavaScript function into an agent tool
149
173
 
150
174
  Keep your application logic as a regular JavaScript function, then describe its
@@ -198,6 +222,41 @@ Whenever the agent calls the tool, the SDK runs your handler in the browser
198
222
  with the generated arguments and sends its return value back to the agent. Keep
199
223
  the return value small and structured so the agent can use it reliably.
200
224
 
225
+ ## Let the user stop a long answer
226
+
227
+ `<ChatWidget>` already does this: while the agent answers, its send button turns
228
+ into a **Stop** button (pass `allowStop={false}` to opt out). In a custom UI, wire
229
+ the `stop` returned by `useChat` / `useMessages`:
230
+
231
+ ```tsx
232
+ import { useChat } from "@useago/sdk/react";
233
+
234
+ function Chat() {
235
+ const { messages, sendMessage, stop, isLoading } = useChat();
236
+
237
+ return (
238
+ <div>
239
+ {messages.map((m) => (
240
+ <p key={m.id}><b>{m.role}:</b> {m.content}</p>
241
+ ))}
242
+ {isLoading ? (
243
+ <button onClick={() => void stop()}>Stop</button>
244
+ ) : (
245
+ <button onClick={() => sendMessage("Write me a long report")}>Send</button>
246
+ )}
247
+ </div>
248
+ );
249
+ }
250
+ ```
251
+
252
+ Stopping closes the stream **and** tells the backend to stop generating. Closing
253
+ the stream alone would not stop the agent: it would keep running and the full
254
+ answer would reappear on the next load. The text already produced stays on
255
+ screen, the message ends as `CANCELED`, and the pending `sendMessage` resolves
256
+ with that partial message instead of rejecting. Vue and Angular have the same
257
+ `stop`, and the vanilla `mountChatWidget` handle gained `stop()`. Details in
258
+ [Stop the answer](docs/general/core.md#stop-the-answer).
259
+
201
260
  ## Show the source docs the agent retrieved
202
261
 
203
262
  Each assistant message carries the knowledge sources it used in `m.sources`