@useago/sdk 1.4.3 → 1.5.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/{AgoClient-S8oeNnAE.js → AgoClient-q39FIjSC.js} +105 -81
  3. package/dist/AgoClient-wcPRaIMs.cjs +8 -0
  4. package/dist/angular.cjs +1 -1
  5. package/dist/angular.d.cts +75 -5
  6. package/dist/angular.d.ts +75 -5
  7. package/dist/angular.js +5 -1
  8. package/dist/autoContinue-CcCTva-D.cjs +1 -0
  9. package/dist/autoContinue-D3Mmaaca.js +63 -0
  10. package/dist/{createConversationSession-CmBq7uGi.cjs → createConversationSession-BBYfQLLS.cjs} +1 -1
  11. package/dist/{createConversationSession-DoUHdVNR.js → createConversationSession-DCSWShaW.js} +1 -1
  12. package/dist/{createFormCollector-Bz_0IJ0Q.cjs → createFormCollector-eimwwWEi.cjs} +1 -1
  13. package/dist/{createFormCollector-vy-QvFja.js → createFormCollector-sB6rMccD.js} +1 -1
  14. package/dist/devtools.d.cts +32 -4
  15. package/dist/devtools.d.ts +32 -4
  16. package/dist/helpers.cjs +1 -1
  17. package/dist/helpers.d.cts +6 -0
  18. package/dist/helpers.d.ts +6 -0
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.d.cts +101 -5
  21. package/dist/index.d.ts +101 -5
  22. package/dist/index.js +45 -43
  23. package/dist/react.cjs +3 -3
  24. package/dist/react.d.cts +40 -18
  25. package/dist/react.d.ts +40 -18
  26. package/dist/react.js +415 -471
  27. package/dist/testing.cjs +1 -1
  28. package/dist/testing.d.cts +32 -4
  29. package/dist/testing.d.ts +32 -4
  30. package/dist/testing.js +1 -1
  31. package/dist/vue.cjs +1 -1
  32. package/dist/vue.d.cts +38 -4
  33. package/dist/vue.d.ts +38 -4
  34. package/dist/vue.js +22 -21
  35. package/dist/widget.cjs +1 -1
  36. package/dist/widget.d.cts +32 -4
  37. package/dist/widget.d.ts +32 -4
  38. package/dist/widget.js +3 -3
  39. package/llms-full.txt +1932 -0
  40. package/package.json +5 -3
  41. package/dist/AgoClient-CMDVqNaH.cjs +0 -8
  42. /package/dist/{createMockClient-bAZ9KBHC.js → createMockClient-BUc9PSbo.js} +0 -0
  43. /package/dist/{createMockClient-BSr-UV8R.cjs → createMockClient-CjdCqixj.cjs} +0 -0
  44. /package/dist/{functions-Djnq-JAo.cjs → functions-IGSjjvF4.cjs} +0 -0
package/llms-full.txt ADDED
@@ -0,0 +1,1932 @@
1
+ # AGO SDK (@useago/sdk) — coding-agent guide
2
+
3
+ Single-file guide for a coding agent (Cursor, Claude Code, Copilot, Windsurf, ...)
4
+ integrating AGO chat into an app. It has two parts:
5
+
6
+ 1. SKILLS — one self-contained section per stack. Jump to the one that matches
7
+ the project, follow it exactly. Each is everything you need to wire AGO in.
8
+ 2. CONCEPTS — cross-cutting reference (functions/context, events/streaming,
9
+ configuration) to understand what the skills do.
10
+
11
+ Pick your stack:
12
+ - Embeddable <script> widget, no build step -> "AGO SDK skill: Widget"
13
+ - Plain JavaScript / TypeScript -> "AGO SDK skill: Vanilla"
14
+ - React -> "AGO SDK skill: React"
15
+ - Vue 3 -> "AGO SDK skill: Vue"
16
+ - Angular -> "AGO SDK skill: Angular"
17
+
18
+ Runnable snippets hit the live demo (baseUrl https://playground.api.useago.com,
19
+ agent generic-guide) so they work with zero setup. For a real tenant, swap in
20
+ https://YOUR-DOMAIN.api.useago.com and your own agent slug, and read baseUrl from
21
+ an env var (never hardcode a tenant URL).
22
+
23
+ Source: https://github.com/useago/ago-sdk
24
+
25
+ ---
26
+
27
+ # SKILLS
28
+
29
+ ---
30
+
31
+ <!-- source: docs/agents/ago-widget.md -->
32
+
33
+ # AGO SDK skill: embeddable `<script>` widget
34
+
35
+ You are adding the AGO chat widget to a website with a `<script>` tag. No build
36
+ step and no npm install. This is the right approach for a marketing site, a CMS
37
+ (WordPress, Webflow), or any server-rendered page where you don't control a JS
38
+ bundle. For a React/Vue/Angular app, use the framework binding instead. This file
39
+ is everything you need. Follow it exactly.
40
+
41
+ ## Endpoint
42
+
43
+ Set `basepath` to the project's AGO instance, `https://YOUR-DOMAIN.api.useago.com`.
44
+ Grab the widget API key and the recommended `integrity` hash from the AGO
45
+ dashboard, and add the embedding domain to the dashboard's allowed-domains list.
46
+
47
+ ## 1. Add the widget script
48
+
49
+ Configure `window.AGO` BEFORE loading the script, and place both tags just before
50
+ the closing `</body>` tag (not in `<head>`).
51
+
52
+ ```html
53
+ <script>
54
+ window.AGO = {
55
+ basepath: "https://YOUR-DOMAIN.api.useago.com",
56
+ widgetApiKey: "YOUR-WIDGET-API-KEY",
57
+ defaultAgent: "support-bot",
58
+ title: "Support",
59
+ prompt: "Hi! How can I help?",
60
+ };
61
+ </script>
62
+ <script
63
+ async
64
+ src="https://useago.github.io/widgetjs/frame.js"
65
+ crossorigin="anonymous"
66
+ ></script>
67
+ ```
68
+
69
+ A floating chat bubble appears in the bottom-right corner. The panel itself is an
70
+ iframe, so it cannot leak styles into the host page.
71
+
72
+ ## 2. Customize appearance
73
+
74
+ ```html
75
+ <script>
76
+ window.AGO = {
77
+ basepath: "https://YOUR-DOMAIN.api.useago.com",
78
+ widgetApiKey: "YOUR-WIDGET-API-KEY",
79
+ title: "Support",
80
+ icon: "https://example.com/logo.svg",
81
+ colors: {
82
+ button: "#4F46E5",
83
+ header: "#4F46E5",
84
+ userMessage: "#4F46E5",
85
+ userMessageFont: "#FFFFFF",
86
+ },
87
+ };
88
+ </script>
89
+ ```
90
+
91
+ ## 3. Identify the user (optional)
92
+
93
+ ```html
94
+ <script>
95
+ window.AGO = {
96
+ basepath: "https://YOUR-DOMAIN.api.useago.com",
97
+ widgetApiKey: "YOUR-WIDGET-API-KEY",
98
+ email: "user@example.com",
99
+ jwt: "jwt-token-here",
100
+ metadata: { plan: "pro", company: "Acme Inc" },
101
+ };
102
+ </script>
103
+ ```
104
+
105
+ ## 4. Framework-specific placement
106
+
107
+ **Next.js (App Router)**, in `app/layout.tsx`:
108
+
109
+ ```tsx
110
+ export default function RootLayout({ children }) {
111
+ return (
112
+ <html>
113
+ <body>
114
+ {children}
115
+ <script
116
+ dangerouslySetInnerHTML={{
117
+ __html: `window.AGO = { basepath: "${process.env.NEXT_PUBLIC_AGO_BASE_URL}", widgetApiKey: "${process.env.NEXT_PUBLIC_AGO_WIDGET_KEY}" };`,
118
+ }}
119
+ />
120
+ <script async src="https://useago.github.io/widgetjs/frame.js" crossOrigin="anonymous" />
121
+ </body>
122
+ </html>
123
+ );
124
+ }
125
+ ```
126
+
127
+ **WordPress**: add both tags via the theme's `footer.php` or a "Custom HTML"
128
+ widget placed in a footer area.
129
+
130
+ **Webflow**: Project Settings > Custom Code > Footer Code.
131
+
132
+ ## 5. Type the config (TypeScript projects)
133
+
134
+ The SDK ships the types and augments the global `Window`, so `window.AGO` is typed
135
+ anywhere this import is in scope:
136
+
137
+ ```ts
138
+ import type { AgoWidgetConfig } from "@useago/sdk/widget";
139
+
140
+ const config: AgoWidgetConfig = {
141
+ basepath: "https://YOUR-DOMAIN.api.useago.com",
142
+ widgetApiKey: "YOUR-WIDGET-API-KEY",
143
+ defaultAgent: "support-bot",
144
+ };
145
+ window.AGO = config;
146
+ ```
147
+
148
+ ## `AgoWidgetConfig` reference
149
+
150
+ | Field | Type | Description |
151
+ | ---------------- | ------- | ----------- |
152
+ | `basepath` | string | AGO instance URL (required) |
153
+ | `widgetApiKey` | string | Widget API key from the dashboard (required) |
154
+ | `defaultAgent?` | string | Agent id/slug to start conversations with |
155
+ | `email?` | string | Identify the end user |
156
+ | `title?` | string | Header title |
157
+ | `icon?` | string | Launcher/header icon URL |
158
+ | `prompt?` | string | Greeting / opening message |
159
+ | `notifications?` | boolean | Enable a proactive notification bubble |
160
+ | `colors?` | object | `button`, `header`, `agentMessage`, `background`, `font`, `userMessage`, ... |
161
+ | `hideFooter?` | boolean | Hide the "powered by" footer |
162
+ | `jwt?` | string | Authenticated-session token |
163
+ | `metadata?` | object | Arbitrary key-value pairs sent with the session |
164
+
165
+ ## When to use `mountChatWidget` instead
166
+
167
+ If the project is a pure TS/JS app where you DO control the DOM (but use no
168
+ framework), prefer `mountChatWidget` from `@useago/sdk/widget`: it renders an
169
+ inline panel instead of a floating iframe and supports conversational forms. See
170
+ the plain JS/TS skill for that.
171
+
172
+ ## Checklist before you finish
173
+
174
+ 1. The `window.AGO` config script comes BEFORE the `frame.js` script.
175
+ 2. Both tags are just before `</body>`, not in `<head>`.
176
+ 3. `basepath` and `widgetApiKey` are set.
177
+ 4. The embedding domain is on the dashboard's allowed-domains list.
178
+ 5. Reload the page and confirm the chat bubble appears bottom-right; check the
179
+ browser console for errors if it doesn't.
180
+
181
+ ---
182
+
183
+ <!-- source: docs/agents/ago-vanilla.md -->
184
+
185
+ # AGO SDK skill: plain JavaScript / TypeScript
186
+
187
+ You are integrating the AGO SDK (`@useago/sdk`) into a plain JS/TS app (no React,
188
+ Vue, or Angular), a Node service, or a web component. AGO is a chat agent that can
189
+ answer questions, run functions in the user's browser, and navigate your app. The
190
+ core `AgoClient` documented here is what every framework binding wraps. This file
191
+ is everything you need. Follow it exactly.
192
+
193
+ ## Endpoints
194
+
195
+ Use the live demo for anything runnable so it answers with zero setup:
196
+
197
+ ```
198
+ baseUrl: https://playground.api.useago.com
199
+ agent: generic-guide
200
+ ```
201
+
202
+ When the project has its own tenant, swap to `https://YOUR-DOMAIN.api.useago.com`
203
+ and the project's own agent slug. Read `baseUrl` from an env var, never hardcode a
204
+ real tenant URL.
205
+
206
+ ## Install
207
+
208
+ ```bash
209
+ npm install @useago/sdk
210
+ ```
211
+
212
+ ## 1. Create a client
213
+
214
+ ```ts
215
+ import { AgoClient } from "@useago/sdk";
216
+
217
+ const ago = new AgoClient({
218
+ baseUrl: "https://playground.api.useago.com", // required
219
+ agent: "generic-guide", // optional default agent
220
+ userEmail: "jane@acme.com", // optional end-user identity
221
+ debug: true, // optional verbose logging
222
+ });
223
+ ```
224
+
225
+ If the page already exposes config (a `window.AGO` object, `<meta name="ago-base-url">`
226
+ tags, or `data-ago-*` attributes), let the SDK find it with `createAgo()` (throws
227
+ if it can't resolve a `baseUrl`).
228
+
229
+ ## 2. Send a message and stream the reply
230
+
231
+ `sendMessage` resolves with the final assistant message, but the text arrives
232
+ incrementally through events. Subscribe BEFORE you send.
233
+
234
+ ```ts
235
+ const outputEl = document.getElementById("output");
236
+
237
+ ago.on("message:start", ({ messageId }) => console.log("New message", messageId));
238
+ ago.on("message:chunk", ({ content }) => { outputEl.textContent += content; }); // token-by-token
239
+ ago.on("message:complete", (message) => console.log("sources:", message.sources));
240
+ ago.on("message:error", ({ error }) => console.error(error));
241
+
242
+ const reply = await ago.sendMessage("How do I reset my password?");
243
+ ```
244
+
245
+ Continue a conversation by reusing `conversationId`:
246
+
247
+ ```ts
248
+ const first = await ago.sendMessage("Hi");
249
+ await ago.sendMessage("Tell me more", { conversationId: first.conversationId });
250
+ ```
251
+
252
+ Attach files (`File[]`, sent as multipart) and override the agent per message:
253
+
254
+ ```ts
255
+ await ago.sendMessage("Summarise this", { files: [fileInput.files[0]], conversationId });
256
+ await ago.sendMessage("Escalate this", { agentId: "human-handoff" });
257
+ ```
258
+
259
+ ## 3. Conversations
260
+
261
+ ```ts
262
+ const conversations = await ago.getConversations(); // [{ id, title, lastMessageDate }]
263
+ const thread = await ago.getConversation(conversations[0].id); // includes messages
264
+ const messages = await ago.getMessages(conversations[0].id);
265
+ ```
266
+
267
+ ## 4. Let the agent run code in the browser
268
+
269
+ ```ts
270
+ ago.registerFunction({
271
+ name: "lookupOrder",
272
+ description: "Look up an order by its ID",
273
+ parameters: {
274
+ type: "object",
275
+ properties: { id: { type: "string", description: "Order ID" } },
276
+ required: ["id"],
277
+ },
278
+ handler: async (args) => fetchOrder(args.id as string),
279
+ });
280
+ ```
281
+
282
+ Rules for functions:
283
+ - Every parameter property needs a `description`. The agent reads it to decide
284
+ what to pass.
285
+ - `required` lists only the params the function truly needs.
286
+ - The handler must return serializable data (no DOM nodes, no circular refs).
287
+
288
+ Navigation is a built-in convenience:
289
+
290
+ ```ts
291
+ ago.registerNavigationFunction((path) => (window.location.href = path), [
292
+ { name: "pricing", path: "/pricing", description: "Pricing page" },
293
+ { name: "docs", path: "/docs", description: "Documentation" },
294
+ ]);
295
+ ```
296
+
297
+ Page state is the mirror: let the agent change the current page's state
298
+ (filters, sort, view mode…) and read it back via each control's `get()`. One
299
+ synthesized `setPageState` function, one optional property per control.
300
+
301
+ ```ts
302
+ ago.registerPageStateFunction([
303
+ {
304
+ name: "statusFilter",
305
+ description: "Filter the list by status",
306
+ schema: { type: "string", enum: ["all", "paid", "overdue"] },
307
+ get: () => filters.status,
308
+ set: (v) => { filters.status = v as string; },
309
+ },
310
+ ]);
311
+ ```
312
+
313
+ ## 5. Give the agent context
314
+
315
+ ```ts
316
+ ago.setContext("order-page", {
317
+ name: "Order detail",
318
+ description: "User is viewing an order",
319
+ data: { orderId: "123", status: "shipped" },
320
+ });
321
+
322
+ // Re-evaluated on every send, great for live state:
323
+ ago.addDynamicContext("cart", () => ({ name: "Cart", data: { itemCount: cart.length } }));
324
+
325
+ // Attach the current URL + page title:
326
+ ago.enableAutoPageContext();
327
+ ```
328
+
329
+ Hold shared state both the agent's functions and your UI mutate with
330
+ `createStore` (a tiny `get` / `set` / `subscribe` holder):
331
+
332
+ ```ts
333
+ import { createStore } from "@useago/sdk";
334
+
335
+ const store = createStore({ items: [] as string[] });
336
+ store.subscribe((state) => render(state));
337
+ ago.addDynamicContext("cart", () => ({ name: "Cart", data: store.get() }));
338
+ store.set({ items: [...store.get().items, "SKU-1"] });
339
+ ```
340
+
341
+ Pass `{ key: "cart" }` as a second arg to persist across reloads (localStorage by
342
+ default; pass `storage: sessionStorage` to swap).
343
+
344
+ ## 6. Drop-in chat panel: `mountChatWidget`
345
+
346
+ A complete chat panel (header, streaming messages, input) you mount into any DOM
347
+ element, no framework. Supports conversational forms and clickable suggested
348
+ replies, same as the React `<ChatWidget>`.
349
+
350
+ ```ts
351
+ import { mountChatWidget } from "@useago/sdk/widget";
352
+
353
+ const widget = mountChatWidget("#chat", {
354
+ config: { baseUrl: "https://playground.api.useago.com", agent: "generic-guide" },
355
+ title: "Ask me anything",
356
+ welcomeMessage: "Hi! How can I help?",
357
+ });
358
+
359
+ widget.sendMessage("Hello");
360
+ widget.destroy(); // removes listeners, uninstalls forms, clears the DOM
361
+ ```
362
+
363
+ Set `placement: "left"` or `"right"` for a fixed full-height side panel with a
364
+ launcher button. Set `persistConversation: true` to resume the visitor's last
365
+ thread across reloads. Theme it with the `theme` option or `--ago-*` CSS variables
366
+ on `.ago-chat-widget`. Message text renders as GitHub-flavored markdown and is
367
+ HTML-escaped before it touches the DOM.
368
+
369
+ For a fully custom UI, build from `sendMessage` + the `message:chunk` event (see
370
+ section 2); `renderMarkdown(source)` is exported if you want the same markdown
371
+ rendering.
372
+
373
+ ## 7. Tool calls, feedback, lifecycle
374
+
375
+ ```ts
376
+ await ago.submitToolCallForm(toolCallId, { quantity: 3 });
377
+ await ago.confirmToolCall(toolCallId);
378
+ await ago.rejectToolCall(toolCallId);
379
+ await ago.submitFeedback(messageId, "positive");
380
+ ago.updateConfig({ userJwt: token }); // e.g. after login
381
+ ago.destroy(); // clean up listeners, functions, context
382
+ ```
383
+
384
+ ## Key events
385
+
386
+ `message:start`, `message:chunk` (`{ content }` per token), `message:complete`
387
+ (`AgoMessage`), `message:error`, `conversation:loaded`, `context:changed`,
388
+ `toolCall:received`, `function:invoke`, `function:result`, `connection:status`.
389
+ Use `on` / `off` / `once`, or `await ago.waitFor("message:complete", { timeout })`.
390
+
391
+ ## Key types
392
+
393
+ ```ts
394
+ interface AgoMessage {
395
+ id: string;
396
+ conversationId: string;
397
+ content: string;
398
+ role: "user" | "assistant";
399
+ status: "IN_PROGRESS" | "DONE" | "ERROR" | "TODO" | "CANCELED";
400
+ sources?: AgoSource[]; // knowledge-base citations { id, title, url? }
401
+ toolCalls?: ToolCallData[];
402
+ followUpReplies?: string[]; // suggested next questions
403
+ createdAt: Date;
404
+ }
405
+ ```
406
+
407
+ All types are exported from `@useago/sdk`.
408
+
409
+ ## Checklist before you finish
410
+
411
+ 1. Event subscriptions are added BEFORE the first `sendMessage`.
412
+ 2. `baseUrl` comes from an env var, not a hardcoded tenant URL.
413
+ 3. Every registered function's parameters have descriptions and the handler
414
+ returns serializable data.
415
+ 4. `ago.destroy()` runs when the client is no longer needed.
416
+ 5. Run the project's typecheck and lint.
417
+
418
+ ---
419
+
420
+ <!-- source: docs/agents/ago-react.md -->
421
+
422
+ # AGO SDK skill: React
423
+
424
+ You are integrating the AGO SDK (`@useago/sdk`) into a React app. AGO is a chat
425
+ agent that can answer questions, run functions in the user's browser, and
426
+ navigate your app's routes. This file is everything you need. Follow it exactly.
427
+
428
+ ## Endpoints
429
+
430
+ Use the live demo for anything runnable so it answers with zero setup:
431
+
432
+ ```
433
+ baseUrl: https://playground.api.useago.com
434
+ agent: generic-guide
435
+ ```
436
+
437
+ When the project has its own tenant, swap to `https://YOUR-DOMAIN.api.useago.com`
438
+ and the project's own agent slug. Read `baseUrl` from an env var, never hardcode a
439
+ real tenant URL. React `>=17` is an optional peer dependency of the SDK; the app
440
+ already provides it.
441
+
442
+ ## Install
443
+
444
+ ```bash
445
+ npm install @useago/sdk
446
+ ```
447
+
448
+ ## 1. Wrap the app in `<AgoProvider>`
449
+
450
+ The provider creates one `AgoClient` and shares it with every hook and component.
451
+
452
+ ```tsx
453
+ import { AgoProvider } from "@useago/sdk/react";
454
+
455
+ function Root() {
456
+ return (
457
+ <AgoProvider baseUrl="https://playground.api.useago.com" agent="generic-guide">
458
+ <App />
459
+ </AgoProvider>
460
+ );
461
+ }
462
+ ```
463
+
464
+ It can also wire app-wide tools, pre-built helpers, and page context:
465
+
466
+ ```tsx
467
+ <AgoProvider
468
+ baseUrl={import.meta.env.VITE_AGO_BASE_URL}
469
+ tools={[lookupOrder, cancelOrder]} // registered app-wide
470
+ helpers={{ copyToClipboard: true, showToast: (a) => toast(a.message) }}
471
+ pageContext="auto" // auto-capture URL + title
472
+ >
473
+ <App />
474
+ </AgoProvider>
475
+ ```
476
+
477
+ Pass `client={myClient}` to bring your own client (handy in tests).
478
+
479
+ ## 2. Fastest UI: `<ChatWidget>`
480
+
481
+ A complete, styled chat panel. Drop it anywhere under the provider.
482
+
483
+ ```tsx
484
+ import { ChatWidget } from "@useago/sdk/react";
485
+
486
+ function Support() {
487
+ return (
488
+ <ChatWidget
489
+ title="Support"
490
+ welcomeMessage="Hi! How can I help?"
491
+ placeholder="Ask anything..."
492
+ height={600}
493
+ allowFiles
494
+ />
495
+ );
496
+ }
497
+ ```
498
+
499
+ Common props: `title`, `welcomeMessage`, `placeholder`, `allowFiles`, `height`,
500
+ `logoUrl`, `showAgentName`, `forms`, `onMessageSent`, `onMessageReceived`,
501
+ `onFollowUpClick`, `className`. When the agent returns follow-up suggestions the
502
+ widget renders them as clickable buttons (clicking sends the reply by default).
503
+
504
+ `ChatWidget` is a high-level component. For a custom interface, use `useChat`.
505
+
506
+ ## 3. Custom UI with `useChat`
507
+
508
+ All-in-one state for a custom chat interface (composes `useMessages` +
509
+ `useConversation`).
510
+
511
+ ```tsx
512
+ import { useChat } from "@useago/sdk/react";
513
+
514
+ function Chat() {
515
+ const { messages, sendMessage, isLoading, error } = useChat();
516
+
517
+ return (
518
+ <div>
519
+ {messages.map((m) => (
520
+ <p key={m.id}><b>{m.role}:</b> {m.content}</p>
521
+ ))}
522
+ <button onClick={() => sendMessage("Hello!")} disabled={isLoading}>Send</button>
523
+ {error && <p role="alert">{error.message}</p>}
524
+ </div>
525
+ );
526
+ }
527
+ ```
528
+
529
+ `messages` updates token-by-token as the reply streams (the optimistic user
530
+ message is included). `sendMessage(content, files?)` resolves with the final
531
+ message or `null` on error.
532
+
533
+ Show the knowledge sources the agent retrieved via `m.sources` (an `AgoSource[]`,
534
+ each `{ id, title, url? }`):
535
+
536
+ ```tsx
537
+ {m.sources?.map((s) => (
538
+ <a key={s.id} href={s.url} target="_blank" rel="noreferrer">{s.title || s.url}</a>
539
+ ))}
540
+ ```
541
+
542
+ Finer-grained hooks: `useMessages({ conversationId? })`,
543
+ `useConversation({ autoLoad? })`. All hooks read the client from context; pass
544
+ `{ client }` to override. Get the raw client with `useAgoClient()` (throws
545
+ outside a provider) or `useOptionalAgoClient()`.
546
+
547
+ ## 4. Let the agent call your code: `useAgoFunction`
548
+
549
+ Registers a client-side function on mount, cleans it up on unmount.
550
+
551
+ ```tsx
552
+ import { useAgoFunction } from "@useago/sdk/react";
553
+
554
+ useAgoFunction({
555
+ name: "lookupOrder",
556
+ description: "Look up an order by ID",
557
+ parameters: {
558
+ type: "object",
559
+ properties: { id: { type: "string", description: "Order ID" } },
560
+ required: ["id"],
561
+ },
562
+ handler: async (args) => fetchOrder(args.id as string),
563
+ });
564
+ ```
565
+
566
+ Rules for functions:
567
+ - Every parameter property needs a `description`. The agent reads it to decide
568
+ what to pass.
569
+ - `required` lists only the params the function truly needs.
570
+ - The handler must return serializable data (no DOM nodes, no circular refs).
571
+
572
+ Reuse a definition made with `defineFunction` from `@useago/sdk`:
573
+
574
+ ```tsx
575
+ const lookupOrder = defineFunction({ name: "lookupOrder", /* ... */ });
576
+ useAgoFunction(lookupOrder);
577
+ ```
578
+
579
+ ## 5. Let the agent navigate: `useAgoNavigation`
580
+
581
+ Wire AGO into your router. The agent calls your `navigate`, so guards, auth
582
+ redirects, and lazy loading keep working.
583
+
584
+ Define each route once. React Router's `<Route>` has no `description` prop, so
585
+ keep one route table and read both the router and `useAgoNavigation` off it
586
+ instead of duplicating paths.
587
+
588
+ ```tsx
589
+ import { Routes, Route, useNavigate } from "react-router-dom";
590
+ import { useAgoNavigation } from "@useago/sdk/react";
591
+
592
+ const ROUTES = {
593
+ dashboard: { name: "dashboard", path: "/dashboard", description: "KPIs and recent activity" },
594
+ invoices: { name: "invoices", path: "/invoices", description: "List and download invoices" },
595
+ } as const;
596
+
597
+ function AppShell() {
598
+ const navigate = useNavigate();
599
+ useAgoNavigation(navigate, Object.values(ROUTES)); // agent reads paths + descriptions
600
+
601
+ return (
602
+ <Routes>
603
+ <Route path={ROUTES.dashboard.path} element={<Dashboard />} />
604
+ <Route path={ROUTES.invoices.path} element={<Invoices />} />
605
+ </Routes>
606
+ );
607
+ }
608
+ ```
609
+
610
+ The `description` is what the agent reads to pick a page. Write it like you'd
611
+ explain the page to a colleague. A `/invoices/:id` detail page can be registered
612
+ as-is: `:id` becomes a top-level `id` argument of `navigateToPage`, so one
613
+ route covers every record.
614
+
615
+ ## 5b. Let the agent change the page: `useAgoPageState`
616
+
617
+ The mirror of navigation. Let the agent change the current page's state
618
+ (filters, sort, view mode…) and read it back. Each control becomes one optional
619
+ property of a single synthesized `setPageState` function, and every control's
620
+ `get()` value is sent as context so the agent knows the state before changing it.
621
+
622
+ ```tsx
623
+ import { useAgoPageState } from "@useago/sdk/react";
624
+
625
+ const [status, setStatus] = useState("all");
626
+ useAgoPageState([
627
+ {
628
+ name: "statusFilter",
629
+ description: "Filter the list by invoice status",
630
+ schema: { type: "string", enum: ["all", "paid", "overdue"] },
631
+ get: () => status,
632
+ set: setStatus,
633
+ },
634
+ ]);
635
+ ```
636
+
637
+ ## 6. Give the agent context: `useAgoContext`
638
+
639
+ Expose what the user is looking at, sent with every message.
640
+
641
+ ```tsx
642
+ // Static, captured from props/state
643
+ useAgoContext({
644
+ name: "Order detail",
645
+ description: "The user is viewing a specific order",
646
+ data: { orderId: order.id, status: order.status },
647
+ });
648
+
649
+ // Dynamic, re-evaluated on every send (fresh data from a store)
650
+ useAgoContext(() => ({ name: "App shell", data: { userId: store.getState().auth.userId } }));
651
+ ```
652
+
653
+ ## 7. Reactive external state: `useAgoStore`
654
+
655
+ If shared state lives in a core `createStore`, `useAgoStore` reads it reactively
656
+ (re-renders on every `store.set`). It is SSR-safe.
657
+
658
+ ```tsx
659
+ import { createStore } from "@useago/sdk";
660
+ import { useAgoStore } from "@useago/sdk/react";
661
+
662
+ const cart = createStore({ items: [] as string[] });
663
+
664
+ function CartBadge() {
665
+ const { items } = useAgoStore(cart);
666
+ return <span>{items.length}</span>;
667
+ }
668
+ ```
669
+
670
+ ## 8. Events
671
+
672
+ Use the client from `useAgoClient()` with `on` / `off` in an effect:
673
+
674
+ ```tsx
675
+ useEffect(() => {
676
+ const handler = (m) => toast(`AGO: ${m.content}`);
677
+ client.on("message:complete", handler);
678
+ return () => client.off("message:complete", handler);
679
+ }, [client]);
680
+ ```
681
+
682
+ Key events: `message:start`, `message:chunk` (`{ content }` per token),
683
+ `message:complete` (`AgoMessage`), `message:error`, `toolCall:received`,
684
+ `function:invoke`, `function:result`.
685
+
686
+ ## Exports cheat-sheet (`@useago/sdk/react`)
687
+
688
+ - Provider/context: `AgoProvider`, `useAgoClient`, `useOptionalAgoClient`
689
+ - Hooks: `useAgo`, `useChat`, `useMessages`, `useConversation`, `useAgoFunction`,
690
+ `useAgoNavigation`, `useAgoPageState`, `useAgoContext`, `useAgoStore`, `useFormCollector`
691
+ - Components: `ChatWidget`, `Message`, `ChatInput`, `Markdown`
692
+ - Forms: `createFormCollector`
693
+ - Testing: `createMockClient`
694
+ - Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
695
+ `ToolCallData` (import `AgoAttachment` from `@useago/sdk`, not the `/react` subpath)
696
+
697
+ ## Checklist before you finish
698
+
699
+ 1. `AgoProvider` wraps the component tree.
700
+ 2. `baseUrl` comes from an env var, not a hardcoded tenant URL.
701
+ 3. Every registered function's parameters have descriptions and the handler
702
+ returns serializable data.
703
+ 4. Run `npm run typecheck` and `npm run lint` (or the project's equivalents).
704
+
705
+ ---
706
+
707
+ <!-- source: docs/agents/ago-vue.md -->
708
+
709
+ # AGO SDK skill: Vue 3
710
+
711
+ You are integrating the AGO SDK (`@useago/sdk`) into a Vue 3 app. AGO is a chat
712
+ agent that can answer questions, run functions in the user's browser, and
713
+ navigate your app's routes. This file is everything you need. Follow it exactly.
714
+
715
+ ## Endpoints
716
+
717
+ Use the live demo for anything runnable so it answers with zero setup:
718
+
719
+ ```
720
+ baseUrl: https://playground.api.useago.com
721
+ agent: generic-guide
722
+ ```
723
+
724
+ When the project has its own tenant, swap to `https://YOUR-DOMAIN.api.useago.com`
725
+ and the project's own agent slug. Read `baseUrl` from an env var, never hardcode a
726
+ real tenant URL. Vue `>=3.3` is an optional peer dependency. Composables use the
727
+ Composition API and must be called from `setup` / `<script setup>`.
728
+
729
+ ## Install
730
+
731
+ ```bash
732
+ npm install @useago/sdk
733
+ ```
734
+
735
+ ## 1. Install the plugin
736
+
737
+ `AgoPlugin` creates one `AgoClient` and provides it to the whole app.
738
+
739
+ ```ts
740
+ import { createApp } from "vue";
741
+ import { AgoPlugin } from "@useago/sdk/vue";
742
+ import App from "./App.vue";
743
+
744
+ createApp(App)
745
+ .use(AgoPlugin, {
746
+ baseUrl: "https://playground.api.useago.com",
747
+ agent: "generic-guide",
748
+ })
749
+ .mount("#app");
750
+ ```
751
+
752
+ Prefer no plugin? Every composable accepts an explicit client, or create one
753
+ standalone with `useAgo`:
754
+
755
+ ```ts
756
+ import { useAgo } from "@useago/sdk/vue";
757
+
758
+ const client = useAgo(); // from plugin
759
+ const standalone = useAgo({ baseUrl: "https://..." }); // no plugin needed
760
+ ```
761
+
762
+ There is no polished `<ChatWidget>` component in Vue (it is React-only). Build the
763
+ UI from `useChat`; it is a handful of lines, shown below.
764
+
765
+ ## 2. Build a chat with `useChat`
766
+
767
+ All-in-one reactive state (messages + conversations). Everything returned is a
768
+ `ref`/`computed`, so it stays reactive. `messages` updates token-by-token.
769
+
770
+ ```vue
771
+ <script setup lang="ts">
772
+ import { ref } from "vue";
773
+ import { useChat } from "@useago/sdk/vue";
774
+
775
+ const { messages, sendMessage, isLoading, error } = useChat();
776
+ const draft = ref("");
777
+
778
+ async function onSend() {
779
+ if (!draft.value.trim()) return;
780
+ const text = draft.value;
781
+ draft.value = "";
782
+ await sendMessage(text);
783
+ }
784
+ </script>
785
+
786
+ <template>
787
+ <div v-for="m in messages" :key="m.id">
788
+ <b>{{ m.role }}:</b> {{ m.content }}
789
+ </div>
790
+ <form @submit.prevent="onSend">
791
+ <input v-model="draft" :disabled="isLoading" placeholder="Ask anything..." />
792
+ <button :disabled="isLoading">Send</button>
793
+ </form>
794
+ <p v-if="error" role="alert">{{ error.message }}</p>
795
+ </template>
796
+ ```
797
+
798
+ Each assistant message carries the knowledge sources it used in `m.sources` (an
799
+ `AgoSource[]`, each `{ id, title, url? }`). Render them as links.
800
+
801
+ Finer-grained composables: `useMessages({ conversationId? })`,
802
+ `useConversation({ autoLoad? })`. Each accepts `{ client }` to override the
803
+ injected client.
804
+
805
+ ## 3. Let the agent call your code: `useAgoFunction`
806
+
807
+ Registers on mount, unregisters on unmount.
808
+
809
+ ```vue
810
+ <script setup lang="ts">
811
+ import { useAgoFunction } from "@useago/sdk/vue";
812
+
813
+ useAgoFunction("lookupOrder", {
814
+ description: "Look up an order by ID",
815
+ parameters: {
816
+ type: "object",
817
+ properties: { id: { type: "string", description: "Order ID" } },
818
+ required: ["id"],
819
+ },
820
+ handler: async (args) => fetchOrder(args.id as string),
821
+ });
822
+ </script>
823
+ ```
824
+
825
+ Rules for functions:
826
+ - Every parameter property needs a `description`. The agent reads it to decide
827
+ what to pass.
828
+ - `required` lists only the params the function truly needs.
829
+ - The handler must return serializable data (no DOM nodes, no circular refs).
830
+
831
+ ## 4. Let the agent navigate: `useAgoNavigation`
832
+
833
+ Works with `vue-router`.
834
+
835
+ ```vue
836
+ <script setup lang="ts">
837
+ import { useRouter } from "vue-router";
838
+ import { useAgoNavigation } from "@useago/sdk/vue";
839
+
840
+ const router = useRouter();
841
+ useAgoNavigation((path) => router.push(path), [
842
+ { name: "dashboard", path: "/dashboard", description: "Main dashboard" },
843
+ { name: "settings", path: "/settings", description: "User settings" },
844
+ ]);
845
+ </script>
846
+ ```
847
+
848
+ The `description` is what the agent reads to pick a page. Write it like you'd
849
+ explain the page to a colleague.
850
+
851
+ ## 4b. Let the agent change the page: `useAgoPageState`
852
+
853
+ The mirror of navigation. Let the agent change the current page's state
854
+ (filters, sort, view mode…) and read it back. Each control becomes one optional
855
+ property of a single synthesized `setPageState` function, and every control's
856
+ `get()` value is sent as context.
857
+
858
+ ```vue
859
+ <script setup lang="ts">
860
+ import { ref } from "vue";
861
+ import { useAgoPageState } from "@useago/sdk/vue";
862
+
863
+ const status = ref("all");
864
+ useAgoPageState([
865
+ {
866
+ name: "statusFilter",
867
+ description: "Filter the list by status",
868
+ schema: { type: "string", enum: ["all", "paid", "overdue"] },
869
+ get: () => status.value,
870
+ set: (v) => { status.value = v as string; },
871
+ },
872
+ ]);
873
+ </script>
874
+ ```
875
+
876
+ ## 5. Subscribe to events: `useAgoEvents`
877
+
878
+ Auto-cleans up on unmount.
879
+
880
+ ```vue
881
+ <script setup lang="ts">
882
+ import { useAgoEvents } from "@useago/sdk/vue";
883
+
884
+ useAgoEvents("message:complete", (msg) => console.log("Got reply:", msg.content));
885
+ </script>
886
+ ```
887
+
888
+ Key events: `message:start`, `message:chunk` (`{ content }` per token),
889
+ `message:complete` (`AgoMessage`), `message:error`.
890
+
891
+ ## 6. Client context
892
+
893
+ There is no dedicated Vue composable for context. Use the core client API, which
894
+ is reactive-friendly:
895
+
896
+ ```vue
897
+ <script setup lang="ts">
898
+ import { onMounted, onUnmounted } from "vue";
899
+ import { useAgo } from "@useago/sdk/vue";
900
+
901
+ const client = useAgo();
902
+
903
+ onMounted(() => {
904
+ // Re-evaluated on every send, so it always reflects current state
905
+ client.addDynamicContext("cart", () => ({ name: "Cart", data: { itemCount: cart.value.length } }));
906
+ });
907
+ onUnmounted(() => client.removeDynamicContext("cart"));
908
+ </script>
909
+ ```
910
+
911
+ For a static snapshot use `client.setContext(key, entry)`. Attach the current URL
912
+ + title with `client.enableAutoPageContext()`.
913
+
914
+ ## 7. Reactive external state: `useAgoStore`
915
+
916
+ Hold shared state in a core `createStore` and expose it as a reactive ref.
917
+
918
+ ```vue
919
+ <script setup lang="ts">
920
+ import { createStore } from "@useago/sdk";
921
+ import { useAgoStore } from "@useago/sdk/vue";
922
+
923
+ const cart = createStore({ items: [] as string[] });
924
+ const state = useAgoStore(cart);
925
+ </script>
926
+
927
+ <template><span>{{ state.items.length }}</span></template>
928
+ ```
929
+
930
+ ## Exports cheat-sheet (`@useago/sdk/vue`)
931
+
932
+ - Setup: `AgoPlugin`, `useAgo`, `AGO_CLIENT_KEY`
933
+ - Composables: `useChat`, `useMessages`, `useConversation`, `useAgoFunction`,
934
+ `useAgoNavigation`, `useAgoPageState`, `useAgoEvents`, `useAgoStore`
935
+ - Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
936
+ `ToolCallData`, `AgoPluginOptions`
937
+
938
+ ## Checklist before you finish
939
+
940
+ 1. `AgoPlugin` is installed on the app (or a client is passed explicitly).
941
+ 2. `baseUrl` comes from an env var, not a hardcoded tenant URL.
942
+ 3. Every registered function's parameters have descriptions and the handler
943
+ returns serializable data.
944
+ 4. Run `npm run typecheck` and `npm run lint` (or the project's equivalents).
945
+
946
+ ---
947
+
948
+ <!-- source: docs/agents/ago-angular.md -->
949
+
950
+ # AGO SDK skill: Angular
951
+
952
+ You are integrating the AGO SDK (`@useago/sdk`) into an Angular app. AGO is a chat
953
+ agent that can answer questions, run functions in the user's browser, and
954
+ navigate your app's routes. This file is everything you need. Follow it exactly.
955
+
956
+ ## Endpoints
957
+
958
+ Use the live demo for anything runnable so it answers with zero setup:
959
+
960
+ ```
961
+ baseUrl: https://playground.api.useago.com
962
+ agent: generic-guide
963
+ ```
964
+
965
+ When the project has its own tenant, swap to `https://YOUR-DOMAIN.api.useago.com`
966
+ and the project's own agent slug. Read `baseUrl` from the environment, never
967
+ hardcode a real tenant URL.
968
+
969
+ The Angular bindings have no hard dependency on Angular: they ship a minimal
970
+ Observable shape compatible with RxJS, so streaming events arrive as Observables.
971
+
972
+ ## Install
973
+
974
+ ```bash
975
+ npm install @useago/sdk
976
+ ```
977
+
978
+ ## 1. Provide the service
979
+
980
+ Standalone bootstrap:
981
+
982
+ ```ts
983
+ import { bootstrapApplication } from "@angular/platform-browser";
984
+ import { provideAgo } from "@useago/sdk/angular";
985
+ import { AppComponent } from "./app.component";
986
+
987
+ bootstrapApplication(AppComponent, {
988
+ providers: [
989
+ provideAgo({
990
+ baseUrl: "https://playground.api.useago.com",
991
+ agent: "generic-guide",
992
+ }),
993
+ ],
994
+ });
995
+ ```
996
+
997
+ `provideAgo(config)` returns `{ provide: AgoService, useValue: <singleton> }`,
998
+ which Angular's injector accepts directly. In an `NgModule`, add it to the
999
+ `providers` array the same way. With no DI, construct it:
1000
+ `new AgoService({ baseUrl: "..." })`.
1001
+
1002
+ ## 2. Inject and use
1003
+
1004
+ Subscribe to the streaming Observables to render messages token-by-token.
1005
+
1006
+ ```ts
1007
+ import { Component, inject, OnDestroy } from "@angular/core";
1008
+ import { AgoService } from "@useago/sdk/angular";
1009
+ import type { AgoMessage } from "@useago/sdk/angular";
1010
+
1011
+ @Component({
1012
+ selector: "app-chat",
1013
+ standalone: true,
1014
+ template: `
1015
+ <div *ngFor="let m of messages"><b>{{ m.role }}:</b> {{ m.content }}</div>
1016
+ <form (submit)="send(input.value); input.value = ''">
1017
+ <input #input placeholder="Ask anything..." />
1018
+ <button>Send</button>
1019
+ </form>
1020
+ `,
1021
+ })
1022
+ export class ChatComponent implements OnDestroy {
1023
+ private ago = inject(AgoService);
1024
+ messages: AgoMessage[] = [];
1025
+ private streaming?: AgoMessage;
1026
+
1027
+ private subs = [
1028
+ // A new assistant message starts: push a placeholder to stream into
1029
+ this.ago.messageStart$.subscribe(({ messageId }) => {
1030
+ this.streaming = {
1031
+ id: messageId, conversationId: "", content: "",
1032
+ role: "assistant", status: "IN_PROGRESS", createdAt: new Date(),
1033
+ };
1034
+ this.messages = [...this.messages, this.streaming];
1035
+ }),
1036
+ // Append streamed tokens
1037
+ this.ago.chunks$.subscribe(({ content, messageId }) => {
1038
+ this.messages = this.messages.map((m) =>
1039
+ m.id === messageId ? { ...m, content: m.content + content } : m);
1040
+ }),
1041
+ // Final message replaces the placeholder
1042
+ this.ago.messages$.subscribe((msg) => {
1043
+ this.messages = this.messages.map((m) => (m.id === msg.id ? msg : m));
1044
+ }),
1045
+ this.ago.errors$.subscribe(({ error }) => console.error(error)),
1046
+ ];
1047
+
1048
+ async send(content: string) {
1049
+ if (!content.trim()) return;
1050
+ this.messages = [...this.messages, {
1051
+ id: `u-${Date.now()}`, conversationId: "", content,
1052
+ role: "user", status: "DONE", createdAt: new Date(),
1053
+ }];
1054
+ await this.ago.sendMessage(content);
1055
+ }
1056
+
1057
+ ngOnDestroy() {
1058
+ this.subs.forEach((s) => s.unsubscribe());
1059
+ this.ago.destroy();
1060
+ }
1061
+ }
1062
+ ```
1063
+
1064
+ ## 3. Observables
1065
+
1066
+ | Observable | Emits |
1067
+ | --------------- | ----- |
1068
+ | `messageStart$` | `{ conversationId, messageId }` when a reply begins |
1069
+ | `chunks$` | `{ content, conversationId, messageId }` per streamed token |
1070
+ | `messages$` | the final `AgoMessage` on completion |
1071
+ | `errors$` | `{ error, conversationId?, messageId? }` |
1072
+
1073
+ Each `subscribe()` returns an object with `unsubscribe()`. For any other event,
1074
+ use `ago.on(event, handler)` / `ago.off(event, handler)`.
1075
+
1076
+ ## 4. Methods
1077
+
1078
+ `AgoService` forwards the full client surface:
1079
+
1080
+ ```ts
1081
+ ago.sendMessage(content, options?) // Promise<AgoMessage>
1082
+ ago.getConversations() // Promise<Conversation[]>
1083
+ ago.getConversation(id)
1084
+ ago.getMessages(conversationId)
1085
+
1086
+ ago.registerFunction(definition) // or (name, handler, schema)
1087
+ ago.unregisterFunction(name)
1088
+ ago.registerNavigationFunction(navigate, routes)
1089
+ ago.enableAutoContinueAfterNavigation(options?) // returns a disable fn
1090
+ ago.registerPageStateFunction(controls, opts?) / ago.unregisterPageStateFunction(functionName?)
1091
+
1092
+ ago.submitToolCallForm(toolCallId, formData)
1093
+ ago.confirmToolCall(toolCallId)
1094
+ ago.rejectToolCall(toolCallId)
1095
+ ago.submitFeedback(messageId, "positive" | "negative")
1096
+
1097
+ ago.on(event, handler) / ago.off(event, handler)
1098
+ ago.updateConfig(partialConfig)
1099
+ ago.getClient() // underlying AgoClient
1100
+ ago.destroy()
1101
+ ```
1102
+
1103
+ ## 5. Functions, navigation and context
1104
+
1105
+ Register a client-side function the agent can call:
1106
+
1107
+ ```ts
1108
+ this.ago.registerFunction({
1109
+ name: "lookupOrder",
1110
+ description: "Look up an order by ID",
1111
+ parameters: {
1112
+ type: "object",
1113
+ properties: { id: { type: "string", description: "Order ID" } },
1114
+ required: ["id"],
1115
+ },
1116
+ handler: async (args) => this.orders.fetch(args.id as string),
1117
+ });
1118
+ ```
1119
+
1120
+ Rules for functions:
1121
+ - Every parameter property needs a `description`. The agent reads it to decide
1122
+ what to pass.
1123
+ - `required` lists only the params the function truly needs.
1124
+ - The handler must return serializable data (no DOM nodes, no circular refs).
1125
+
1126
+ Register navigation (works with Angular's `Router`):
1127
+
1128
+ ```ts
1129
+ const router = inject(Router);
1130
+ this.ago.registerNavigationFunction((path) => router.navigateByUrl(path), [
1131
+ { name: "dashboard", path: "/dashboard", description: "Main dashboard" },
1132
+ ]);
1133
+ ```
1134
+
1135
+ To make "go to page B and change X" work in one user message, enable
1136
+ auto-continue once (e.g. in the root component) and disable it on teardown:
1137
+
1138
+ ```ts
1139
+ private stopAutoContinue = this.ago.enableAutoContinueAfterNavigation();
1140
+ // ngOnDestroy: this.stopAutoContinue();
1141
+ ```
1142
+
1143
+ Register page state, the mirror of navigation (change the current page's state
1144
+ and read it back):
1145
+
1146
+ ```ts
1147
+ this.ago.registerPageStateFunction([
1148
+ {
1149
+ name: "statusFilter",
1150
+ description: "Filter the list by status",
1151
+ schema: { type: "string", enum: ["all", "paid", "overdue"] },
1152
+ get: () => this.status,
1153
+ set: (v) => { this.status = v as string; },
1154
+ },
1155
+ ]);
1156
+ ```
1157
+
1158
+ For client context, reach the client via `getClient()`:
1159
+
1160
+ ```ts
1161
+ this.ago.getClient().enableAutoPageContext();
1162
+ this.ago.getClient().setContext("order", { name: "Order", data: { id } });
1163
+ ```
1164
+
1165
+ ## Exports cheat-sheet (`@useago/sdk/angular`)
1166
+
1167
+ - `AgoService`, `provideAgo`, `AgoProvideOptions`
1168
+ - Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
1169
+ `ToolCallData`, `AgoClientEvents`, `AgoEventName`
1170
+
1171
+ ## Checklist before you finish
1172
+
1173
+ 1. `provideAgo(...)` is registered (or `AgoService` constructed directly).
1174
+ 2. `baseUrl` comes from the environment, not a hardcoded tenant URL.
1175
+ 3. Every subscription is torn down in `ngOnDestroy`, and `ago.destroy()` is called.
1176
+ 4. Every registered function's parameters have descriptions and the handler
1177
+ returns serializable data.
1178
+ 5. Run the project's typecheck and lint.
1179
+
1180
+ ---
1181
+
1182
+ # CONCEPTS
1183
+
1184
+ ---
1185
+
1186
+ <!-- source: docs/general/functions-and-context.md -->
1187
+
1188
+ # Client-side functions & context
1189
+
1190
+ Two capabilities that turn AGO from a chatbot into an agent that acts inside
1191
+ **your** app. Both work in every framework; this page is the canonical
1192
+ reference, and the framework guides show the idiomatic wrappers.
1193
+
1194
+ - [Client-side functions](#client-side-functions): the agent runs code in the browser
1195
+ - [Pre-built helpers](#pre-built-helpers): a catalogue of ready-made functions
1196
+ - [Client context](#client-context): tell the agent what the user is doing
1197
+
1198
+ ---
1199
+
1200
+ ## Client-side functions
1201
+
1202
+ You expose functions (with a name, description and JSON-Schema parameters). The
1203
+ SDK sends those schemas with each message; when the agent decides to call one,
1204
+ the SDK runs your handler in the browser, then submits the result back so the
1205
+ agent can continue. Great for: looking up data, mutating UI, navigation,
1206
+ triggering app actions.
1207
+
1208
+ ### Define a function
1209
+
1210
+ ```ts
1211
+ import { defineFunction } from "@useago/sdk";
1212
+
1213
+ export const lookupOrder = defineFunction({
1214
+ name: "lookupOrder",
1215
+ description: "Look up an order by its ID and return its status.",
1216
+ parameters: {
1217
+ type: "object",
1218
+ properties: {
1219
+ id: { type: "string", description: "The order ID" },
1220
+ },
1221
+ required: ["id"],
1222
+ },
1223
+ handler: async (args) => {
1224
+ // fetchOrder: your own API call
1225
+ const order = await fetchOrder(args.id as string);
1226
+ return { status: order.status, total: order.total }; // returned to the agent
1227
+ },
1228
+ });
1229
+ ```
1230
+
1231
+ `defineFunction` is an identity helper: it just gives you typing and a reusable
1232
+ object. The shape is:
1233
+
1234
+ ```ts
1235
+ interface ClientFunctionDefinition {
1236
+ name: string; // unique
1237
+ description: string; // the LLM reads this to decide when to call it
1238
+ parameters: {
1239
+ type: "object";
1240
+ properties: Record<string, {
1241
+ type: string; // "string" | "number" | "boolean" | "array" | ...
1242
+ description?: string;
1243
+ enum?: string[];
1244
+ default?: unknown;
1245
+ }>;
1246
+ required?: string[];
1247
+ };
1248
+ handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
1249
+ }
1250
+ ```
1251
+
1252
+ > **Tip:** write descriptions for the model. A clear `description` (and per-field
1253
+ > `description`) is what makes the agent call the function correctly. Return a
1254
+ > small, structured object; it's fed straight back into the conversation.
1255
+
1256
+ ### Result size limit
1257
+
1258
+ Everything a handler returns goes into the LLM context, so the SDK caps the
1259
+ serialized result at 50 KB by default. Over the cap, the agent receives a
1260
+ flagged preview instead of the full payload:
1261
+
1262
+ ```json
1263
+ { "truncated": true, "originalBytes": 412031, "maxBytes": 50000, "preview": "...", "hint": "..." }
1264
+ ```
1265
+
1266
+ A console warning fires at 20 KB (with `debug: true`) so you catch it in dev.
1267
+ Tune the cap globally with `maxFunctionResultBytes` on the client config, or
1268
+ per function:
1269
+
1270
+ ```ts
1271
+ defineFunction({
1272
+ name: "exportReport",
1273
+ maxResultBytes: 200_000, // this one is allowed to be big (Infinity disables)
1274
+ // ...
1275
+ });
1276
+ ```
1277
+
1278
+ If you hit the cap, the fix is usually in the handler: return only the fields
1279
+ the agent needs, add `limit`/filter parameters to the schema, or push the data
1280
+ to the UI (page state) and return a short summary like `{ displayed: 25, total: 1200 }`.
1281
+
1282
+ ### Register a function
1283
+
1284
+ ```ts
1285
+ // Single object (preferred)
1286
+ client.registerFunction(lookupOrder);
1287
+
1288
+ // Short alias, also accepts an array
1289
+ client.register([lookupOrder, cancelOrder]);
1290
+
1291
+ // Classic 3-arg form
1292
+ client.registerFunction("lookupOrder", handler, { description, parameters });
1293
+
1294
+ // Remove it
1295
+ client.unregisterFunction("lookupOrder");
1296
+ ```
1297
+
1298
+ Per framework:
1299
+
1300
+ | Framework | Idiomatic API |
1301
+ | --- | --- |
1302
+ | Core | `client.registerFunction(def)` / `client.register([...])` |
1303
+ | React | `useAgoFunction(def)` (auto-cleanup) or `<AgoProvider tools={[…]}>` |
1304
+ | Vue | `useAgoFunction(name, options)` (auto-cleanup) |
1305
+ | Angular | `agoService.registerFunction(def)` |
1306
+
1307
+ ### Navigation shortcut
1308
+
1309
+ A common case (letting the agent move the user around) has a dedicated helper
1310
+ that builds the function for you:
1311
+
1312
+ ```ts
1313
+ client.registerNavigationFunction(
1314
+ (path) => router.push(path), // your navigate fn
1315
+ [
1316
+ { name: "pricing", path: "/pricing", description: "Pricing page" },
1317
+ { name: "docs", path: "/docs", description: "Documentation" },
1318
+ { name: "orderDetail", path: "/orders/:id", description: "One order's detail page" },
1319
+ ],
1320
+ );
1321
+ ```
1322
+
1323
+ This registers a `navigateToPage` function whose `page` parameter is an enum of
1324
+ your route names. React/Vue offer `useAgoNavigation(navigate, routes)` with
1325
+ lifecycle cleanup.
1326
+
1327
+ Paths can contain `:param` placeholders. Each placeholder becomes an explicit
1328
+ top-level argument of `navigateToPage` (`{ page: "orderDetail", id: "42" }`
1329
+ navigates to `/orders/42`), so one route covers every detail page of an entity.
1330
+ Values are URL-encoded. A call that misses a required param navigates nowhere
1331
+ and returns an error telling the agent what to retry with. Placeholders cannot
1332
+ be named `page` (that argument selects the route).
1333
+
1334
+ It also registers a dynamic context entry ("Current page") that reports the page
1335
+ the user is on (by route name, plus URL and title) on every message, so the agent
1336
+ knows where it is after it navigates, not just how to navigate away. Call
1337
+ `client.unregisterNavigationFunction()` to remove both the function and that
1338
+ context.
1339
+
1340
+ ### Page state shortcut
1341
+
1342
+ The mirror of navigation: instead of moving the user to another page, let the
1343
+ agent change the state of the page they're on (filters, sort, view mode,
1344
+ selection…) and read the current state back. One helper builds both halves.
1345
+
1346
+ ```ts
1347
+ client.registerPageStateFunction([
1348
+ {
1349
+ name: "statusFilter",
1350
+ description: "Filter the list by review status",
1351
+ schema: { type: "string", enum: ["all", "pending", "approved"] },
1352
+ get: () => filters.status, // current value → context
1353
+ set: (v) => setFilters({ ...filters, status: v }), // apply the change
1354
+ },
1355
+ {
1356
+ name: "sort",
1357
+ description: "Sort order of the list",
1358
+ schema: { type: "string", enum: ["newest", "oldest"] },
1359
+ get: () => filters.sort,
1360
+ set: (v) => setFilters({ ...filters, sort: v }),
1361
+ },
1362
+ ]);
1363
+
1364
+ client.unregisterPageStateFunction(); // pass the functionName if you customised it
1365
+ ```
1366
+
1367
+ This does two things:
1368
+
1369
+ - Synthesizes **one** client function (`setPageState` by default; override with
1370
+ `{ functionName }`). Each control becomes one optional property, so the agent
1371
+ sets only the controls the user asked for and leaves the rest untouched.
1372
+ - Registers a dynamic context entry ("Page state") reporting each control's
1373
+ current `get()` value, so the agent knows what to change. Controls without a
1374
+ `get()` are write-only and don't appear in context.
1375
+
1376
+ It's fire-and-forget: the handler applies each `set()` locally and returns
1377
+ `{ success, applied }`. The new state reaches the agent through the context on
1378
+ the next message. React/Vue offer `useAgoPageState(controls, opts?)` with
1379
+ lifecycle cleanup; Angular exposes `agoService.registerPageStateFunction(...)`.
1380
+
1381
+ ### Navigate then change the page in one go
1382
+
1383
+ A request like "open the invoices page and show only the overdue ones" needs two
1384
+ steps: the agent navigates, then changes the new page's state. But the
1385
+ destination page only registers its `setPageState` (and current state) once it
1386
+ mounts, so the agent can't do both in a single turn.
1387
+
1388
+ `useAgoAutoContinueAfterNavigation()` closes the gap. Mount it once inside
1389
+ `AgoProvider`:
1390
+
1391
+ ```tsx
1392
+ function AppShell() {
1393
+ useAgoNavigation(navigate, routes);
1394
+ useAgoPageState(controls); // on each pilotable page
1395
+ useAgoAutoContinueAfterNavigation(); // once, near the provider
1396
+ }
1397
+ ```
1398
+
1399
+ Outside React, the same behavior ships as a framework-agnostic helper. Attach it
1400
+ once per client; it returns a detach function:
1401
+
1402
+ ```ts
1403
+ import { attachAutoContinueAfterNavigation } from "@useago/sdk";
1404
+
1405
+ const detach = attachAutoContinueAfterNavigation(client);
1406
+ ```
1407
+
1408
+ Angular wraps it as `agoService.enableAutoContinueAfterNavigation(options?)`,
1409
+ which also returns the disable function (call it in `ngOnDestroy`).
1410
+
1411
+ How the gap is bridged depends on the client-functions mode:
1412
+
1413
+ - **Pause mode** (the default, `clientFunctionsMode: "pause"` —
1414
+ needs a backend with pause/resume support): the backend pauses the turn on the
1415
+ navigation call. The SDK submits the function result and resumes the SAME turn
1416
+ via `POST /messages/{id}/continue`; the hook only delays that resume until the
1417
+ destination's `useAgoPageState` registered. No extra prompt, no second turn,
1418
+ and the agent sees the real function results.
1419
+
1420
+ - **Placeholder mode** (legacy, `clientFunctionsMode: "placeholder"`, also what
1421
+ older backends without pause/resume support fall back to): when the navigating turn ends, the hook
1422
+ waits for the destination page to register, then sends a continuation message
1423
+ with `{ hidden: true }` — kept in the model's context, never displayed
1424
+ (`useMessages` and `ChatWidget` filter hidden messages). Without backend
1425
+ support for `hidden` the mechanism still works but the nudge shows as a message.
1426
+
1427
+ A paused turn also survives a full page reload: after `getConversation(...)`,
1428
+ call `client.resumePendingClientFunctions(conversation)` to re-run the waiting
1429
+ functions, submit their results, and resume the turn (opt-in — re-running a
1430
+ navigation on reload navigates again).
1431
+
1432
+ ### Observe invocations
1433
+
1434
+ ```ts
1435
+ client.on("function:invoke", ({ functionName, arguments: args }) =>
1436
+ console.log("Agent called", functionName, args),
1437
+ );
1438
+ client.on("function:result", ({ result, error }) =>
1439
+ console.log("Returned", result, error),
1440
+ );
1441
+ ```
1442
+
1443
+ ---
1444
+
1445
+ ## Pre-built helpers
1446
+
1447
+ The SDK ships a catalogue of common browser actions as ready-made function
1448
+ definitions. Import them from `@useago/sdk` (or `@useago/sdk/helpers`), wire up a
1449
+ handler where needed, and register.
1450
+
1451
+ | Helper | What it does | Handler needed? |
1452
+ | --- | --- | --- |
1453
+ | `showToast` | Show a toast notification | ✅ wire to your toast lib |
1454
+ | `showNotification` | Browser `Notification` (asks permission) | built-in |
1455
+ | `openUrl` | Open a URL in a new tab | built-in |
1456
+ | `copyToClipboard` | Copy text to clipboard | built-in |
1457
+ | `setTheme` | Set `data-theme` on `<html>` (light/dark/system) | built-in |
1458
+ | `showConfirmDialog` | `window.confirm` and return the choice | built-in |
1459
+ | `getUserLocation` | Geolocation (asks permission) | built-in |
1460
+ | `scrollToElement` | Scroll to a CSS selector | built-in |
1461
+ | `setLocalStorage` / `getLocalStorage` | Read/write `localStorage` | built-in |
1462
+ | `highlightElement` | Outline an element (guided tours) | built-in |
1463
+ | `submitForm` | Fill & submit an HTML form | built-in |
1464
+ | `trackEvent` | Custom analytics event | ➖ logs to console by default |
1465
+
1466
+ ### Wiring a handler
1467
+
1468
+ Some helpers ship a no-op/console default (`showToast`, `trackEvent`). Attach
1469
+ your implementation without mutating the original with `withHandler`:
1470
+
1471
+ ```ts
1472
+ import { showToast, withHandler } from "@useago/sdk";
1473
+
1474
+ client.register(
1475
+ withHandler(showToast, (args) => {
1476
+ myToast(args.message as string); // myToast: your own toast handler
1477
+ return { shown: true };
1478
+ }),
1479
+ );
1480
+ ```
1481
+
1482
+ In React you can wire helpers declaratively on the provider:
1483
+
1484
+ ```tsx
1485
+ <AgoProvider
1486
+ baseUrl="…"
1487
+ helpers={{
1488
+ copyToClipboard: true, // built-in handler
1489
+ showToast: (args) => toast(args.message as string), // custom
1490
+ }}
1491
+ >
1492
+ ```
1493
+
1494
+ ---
1495
+
1496
+ ## Client context
1497
+
1498
+ Context is structured data describing the user's current situation. It's sent
1499
+ with **every** message so the agent answers in context, without the user having
1500
+ to explain where they are.
1501
+
1502
+ ### Static context
1503
+
1504
+ ```ts
1505
+ client.setContext("order-page", {
1506
+ name: "Order detail",
1507
+ description: "User is viewing an order",
1508
+ data: { orderId: "123", status: "shipped" },
1509
+ });
1510
+
1511
+ client.removeContext("order-page");
1512
+ ```
1513
+
1514
+ Shape:
1515
+
1516
+ ```ts
1517
+ interface ContextEntry {
1518
+ name?: string; // human label, e.g. "Order detail"
1519
+ description?: string; // what this context represents
1520
+ data?: Record<string, unknown>; // structured data for the AI
1521
+ }
1522
+ ```
1523
+
1524
+ ### Dynamic context
1525
+
1526
+ A function evaluated on every send, perfect for data outside your render state
1527
+ (global stores, refs, computed values). Return `null` to skip.
1528
+
1529
+ ```ts
1530
+ // cart / cartTotal(): a variable and helper from your outer scope or store
1531
+ client.addDynamicContext("cart", () => ({
1532
+ name: "Cart",
1533
+ data: { itemCount: cart.length, total: cartTotal() },
1534
+ }));
1535
+
1536
+ client.removeDynamicContext("cart");
1537
+ ```
1538
+
1539
+ ### Auto page context
1540
+
1541
+ One call attaches the current URL + document title on every message:
1542
+
1543
+ ```ts
1544
+ client.enableAutoPageContext();
1545
+ ```
1546
+
1547
+ (In React, set `<AgoProvider pageContext="auto">`.)
1548
+
1549
+ ### Per framework
1550
+
1551
+ | Framework | Idiomatic API |
1552
+ | --- | --- |
1553
+ | Core | `setContext` / `addDynamicContext` / `enableAutoPageContext` |
1554
+ | React | `useAgoContext(entryOrFn, key?)` + `pageContext="auto"` |
1555
+ | Vue | core API inside `onMounted` / `onUnmounted` |
1556
+ | Angular | `agoService.getClient().setContext(…)` |
1557
+
1558
+ ### Inspect what's being sent
1559
+
1560
+ ```ts
1561
+ const snapshot = client.getContextSnapshot();
1562
+ // → { entries: { "order-page": { name, description, data }, … } } | null
1563
+ ```
1564
+
1565
+ ---
1566
+
1567
+ See also: [Events & streaming](events-and-streaming.md) ·
1568
+ [Core API](core.md) · [Testing](testing.md)
1569
+
1570
+ ---
1571
+
1572
+ <!-- source: docs/general/events-and-streaming.md -->
1573
+
1574
+ # Events & streaming
1575
+
1576
+ `AgoClient` streams responses over Server-Sent Events and re-emits them as typed
1577
+ events. You can consume them three ways, from lowest to highest level:
1578
+
1579
+ 1. Raw events: `client.on(event, handler)`
1580
+ 2. Callback helpers: `onMessage`, `onMessageChunk`, …
1581
+ 3. An async generator: `createMessageStream`
1582
+
1583
+ All three are framework-agnostic and live in `@useago/sdk`.
1584
+
1585
+ ---
1586
+
1587
+ ## 1. Raw events
1588
+
1589
+ ```ts
1590
+ client.on("message:chunk", ({ content }) => append(content)); // append: your own render fn
1591
+ client.off("message:chunk", handler);
1592
+ client.once("message:complete", (m) => console.log(m.content));
1593
+
1594
+ const final = await client.waitFor("message:complete", { timeout: 10_000 });
1595
+ ```
1596
+
1597
+ | Event | Payload |
1598
+ | --- | --- |
1599
+ | `message:start` | `{ conversationId, messageId }` |
1600
+ | `message:chunk` | `{ content, conversationId, messageId }` |
1601
+ | `message:answer-complete` | `AgoMessage`: the main answer text is done, but follow-up replies may still be pending; fires once, before `message:complete` |
1602
+ | `message:complete` | `AgoMessage` |
1603
+ | `message:waiting-client` | `{ conversationId, messageId, waitingToolCallIds }`: the turn paused on client function call(s) (`clientFunctionsMode: "pause"`). `message:complete` does not fire for the paused stream — it fires when the resumed turn concludes. The SDK submits the results and resumes on its own; listen to this to keep a "working…" indicator up |
1604
+ | `message:empty` | `{ conversationId, messageId }`: the reply completed as `DONE` with no content, tool calls, or follow-ups (usually an unknown `agent` slug). Fires after `message:complete` AND after `sendMessage` resolves, so subscribing right after the `await` still catches it. Both ids are `""` when the stream carried no message data at all. See [Empty replies](configuration.md#empty-replies). |
1605
+ | `message:error` | `{ error, code?, conversationId?, messageId? }`: `code` is the stable [error code](configuration.md#error-codes) when the failure was an `AgoError` |
1606
+ | `conversation:loaded` | `Conversation`: a full conversation was loaded from the server (e.g. after a page reload), with messages and persisted tool calls |
1607
+ | `context:changed` | `ContextSnapshot \| null`: the client-side context changed (entry added/removed, or a stateful helper updated its store) |
1608
+ | `toolCall:received` | `ToolCallData` |
1609
+ | `toolCall:form` | `ToolCallData` (only when the tool call is a form) |
1610
+ | `function:invoke` | `{ invocationId, functionName, arguments, conversationId }` |
1611
+ | `function:result` | `{ invocationId, result, error? }` |
1612
+ | `form:submitted` | `{ name, values, result }`: a form collector submitted successfully; `result` is the submit response (the third-party API's answer) |
1613
+ | `form:error` | `{ name, values, error }`: a form collector submit failed at the network/server level (HTTP non-2xx or a thrown handler/fetch). Validation pre-checks (missing fields, no submit target) do not fire this |
1614
+ | `connection:status` | `{ connected }` |
1615
+
1616
+ `waitFor` rejects if the optional `timeout` (ms) elapses first.
1617
+
1618
+ ---
1619
+
1620
+ ## 2. Callback helpers
1621
+
1622
+ Thin wrappers that return an **unsubscribe** function, handy when you don't want
1623
+ to keep a reference to the handler.
1624
+
1625
+ ```ts
1626
+ import {
1627
+ onMessage,
1628
+ onMessageChunk,
1629
+ onMessageStart,
1630
+ onMessageError,
1631
+ onToolCall,
1632
+ onFunctionInvoke,
1633
+ } from "@useago/sdk";
1634
+
1635
+ const unsub = onMessageChunk(client, ({ content }) => {
1636
+ outputEl.textContent += content;
1637
+ });
1638
+ // later
1639
+ unsub();
1640
+
1641
+ onMessage(client, (msg) => console.log("complete:", msg.content));
1642
+ ```
1643
+
1644
+ | Helper | Subscribes to |
1645
+ | --- | --- |
1646
+ | `onMessage(client, cb)` | `message:complete` |
1647
+ | `onMessageChunk(client, cb)` | `message:chunk` |
1648
+ | `onMessageStart(client, cb)` | `message:start` |
1649
+ | `onMessageError(client, cb)` | `message:error` |
1650
+ | `onToolCall(client, cb)` | `toolCall:received` |
1651
+ | `onFunctionInvoke(client, cb)` | `function:invoke` |
1652
+ | `onFormSubmitted(client, cb)` | `form:submitted` |
1653
+ | `onFormError(client, cb)` | `form:error` |
1654
+
1655
+ ---
1656
+
1657
+ ## 3. Async generator
1658
+
1659
+ `createMessageStream` fires a message and yields a typed event for each step:
1660
+ no manual subscribe/unsubscribe, just `for await`.
1661
+
1662
+ ```ts
1663
+ import { createMessageStream } from "@useago/sdk";
1664
+
1665
+ for await (const ev of createMessageStream(client, "Hello!", { conversationId })) {
1666
+ switch (ev.type) {
1667
+ case "start": console.log("started", ev.data.messageId); break;
1668
+ case "chunk": process.stdout.write(ev.data.content); break;
1669
+ case "toolCall": console.log("tool call", ev.data); break;
1670
+ case "complete": console.log("\ndone:", ev.data.content); break;
1671
+ case "error": console.error(ev.data.error); break;
1672
+ }
1673
+ }
1674
+ ```
1675
+
1676
+ Options: `{ conversationId?, agentId?, files? }`. The generator cleans up its
1677
+ listeners automatically when the loop ends.
1678
+
1679
+ ---
1680
+
1681
+ ## Tool calls
1682
+
1683
+ When the agent needs structured input (a form), a confirmation, or shows a
1684
+ status/progress indicator, it emits a tool call. Listen and respond:
1685
+
1686
+ ```ts
1687
+ client.on("toolCall:form", async (toolCall) => {
1688
+ // toolCall.formSchema describes the fields to render
1689
+ const values = await renderFormSomehow(toolCall.formSchema); // render your own form UI here
1690
+ await client.submitToolCallForm(toolCall.id, values);
1691
+ });
1692
+
1693
+ client.on("toolCall:received", async (toolCall) => {
1694
+ if (toolCall.type === "confirmation_input") {
1695
+ const ok = window.confirm(toolCall.message ?? "Confirm?");
1696
+ ok ? await client.confirmToolCall(toolCall.id)
1697
+ : await client.rejectToolCall(toolCall.id);
1698
+ }
1699
+ });
1700
+ ```
1701
+
1702
+ `ToolCallType` is one of: `form`, `confirmation_input`, `status_message`,
1703
+ `progress_indicator`, `client_function`, `reasoning`, `mcp_ui_resource`.
1704
+
1705
+ ---
1706
+
1707
+ ## Framework equivalents
1708
+
1709
+ | Framework | How to subscribe |
1710
+ | --- | --- |
1711
+ | Core | `client.on(...)` or the helpers above |
1712
+ | React | `client.on(...)` in `useEffect` (get the client via `useAgoClient()`), or rely on `useChat`/`useMessages` which manage this for you |
1713
+ | Vue | `useAgoEvents(event, handler)` (auto-cleanup) |
1714
+ | Angular | `messages$`, `chunks$`, `errors$`, `messageStart$` Observables, or `ago.on(...)` |
1715
+
1716
+ ---
1717
+
1718
+ See also: [Core API](core.md) · [Functions & context](functions-and-context.md)
1719
+
1720
+ ---
1721
+
1722
+ <!-- source: docs/general/configuration.md -->
1723
+
1724
+ # Configuration & auth
1725
+
1726
+ Every entry point ultimately builds an `AgoClient` from an `AgoConfig`. This
1727
+ page documents each option, how it maps to request headers, runtime updates,
1728
+ auto-detection, and error handling.
1729
+
1730
+ ---
1731
+
1732
+ ## `AgoConfig`
1733
+
1734
+ ```ts
1735
+ interface AgoConfig {
1736
+ baseUrl: string; // required, e.g. https://YOUR-DOMAIN.useago.com
1737
+ widgetId?: string; // X-User-Anon-Id header; auto-generated + persisted if omitted
1738
+ agent?: string; // default agent (id or slug) for new conversations
1739
+ defaultAgentId?: string; // same as `agent`; `agent` is preferred
1740
+ permission?: string; // X-Widget-Permission header
1741
+ userEmail?: string; // X-User-Email header, identify the end user
1742
+ userJwt?: string; // Authorization: Bearer <jwt>, authenticated users
1743
+ debug?: boolean; // enable verbose console logging
1744
+ warnOnEmptyReply?: boolean; // default true; see "Empty replies" below
1745
+ clientFunctionsMode?: "placeholder" | "pause"; // default "pause"
1746
+ maxFunctionResultBytes?: number; // default 50000; cap on client function results
1747
+ }
1748
+ ```
1749
+
1750
+ | Option | Required | Maps to | Notes |
1751
+ | --- | :---: | --- | --- |
1752
+ | `baseUrl` | ✅ | request URL | Trailing slash is trimmed automatically. Missing/empty throws `config_missing_base_url` at construct time. `/path` values (same-origin proxy) are allowed. |
1753
+ | `widgetId` | — | `X-User-Anon-Id` | Per-visitor anonymous id. If omitted, a UUID is generated and cached in `localStorage` (`ago_widget_id`) so a returning visitor keeps the same id. |
1754
+ | `agent` / `defaultAgentId` | — | `agent_id` in the body | Per-message `agentId` overrides it. `agent` wins if both are set. |
1755
+ | `permission` | — | `X-Widget-Permission` | Mirrors the widget's `window.AGO.permission`. |
1756
+ | `userEmail` | — | `X-User-Email` | Identifies the user to AGO. |
1757
+ | `userJwt` | — | `Authorization: Bearer …` | For authenticated sessions. |
1758
+ | `debug` | — | — | Turns on the SDK's logger. |
1759
+ | `warnOnEmptyReply` | — | — | Default `true`: warn on the console (once per conversation) when a reply completes empty, usually an unknown `agent` slug. The [`message:empty` event](events-and-streaming.md#events) fires regardless. |
1760
+ | `clientFunctionsMode` | — | `client_functions_mode` in the body | Default `"pause"`: the agent stops on client function call(s) and resumes the SAME turn once the results are submitted (the SDK submits and resumes automatically; see the [`message:waiting-client` event](events-and-streaming.md#events)). Needs a backend with pause/resume support; older backends ignore the flag and fall back to placeholder behavior. Legacy `"placeholder"`: the turn continues on a placeholder and results are only visible on later turns. Per-message override via `sendMessage(..., { clientFunctionsMode })`. |
1761
+ | `maxFunctionResultBytes` | — | — | Default `50000`. Client function results larger than this (serialized) are replaced with a truncated preview before reaching the LLM. Override per function with `maxResultBytes`; see [result size limit](functions-and-context.md#result-size-limit). |
1762
+
1763
+ ---
1764
+
1765
+ ## Debug logging
1766
+
1767
+ `debug: true` turns on the SDK's internal logger. While enabled, the client
1768
+ writes verbose messages to the browser console, each prefixed with `[AGO SDK]`:
1769
+ client init, HTTP/stream requests, and registered functions/context. Examples:
1770
+
1771
+ ```
1772
+ [AGO SDK] AgoClient initialized
1773
+ [AGO SDK] POST (stream) https://acme.useago.com/api/sdk/v1/messages {…}
1774
+ [AGO SDK] ClientContext set: order-page
1775
+ ```
1776
+
1777
+ - **Default is `false`**: all of those messages are silent.
1778
+ - **Errors are always logged** (`console.error`), whether `debug` is on or off.
1779
+ - Use it in development; **leave it off in production** to keep users' consoles
1780
+ clean.
1781
+ - Toggle it at runtime with `client.updateConfig({ debug: true })` (or `false`).
1782
+
1783
+ ```ts
1784
+ const ago = new AgoClient({ baseUrl: "https://acme.useago.com", debug: true });
1785
+
1786
+ // Or detect config from the DOM and only override `debug`:
1787
+ import { createAgo } from "@useago/sdk";
1788
+ const ago2 = createAgo({ debug: true });
1789
+ ```
1790
+
1791
+ ---
1792
+
1793
+ ## Examples per framework
1794
+
1795
+ ```ts
1796
+ // Core
1797
+ const ago = new AgoClient({ baseUrl: "https://acme.useago.com", agent: "support" });
1798
+ ```
1799
+
1800
+ ```tsx
1801
+ // React
1802
+ <AgoProvider baseUrl="https://acme.useago.com" agent="support" />
1803
+ ```
1804
+
1805
+ ```ts
1806
+ // Vue
1807
+ app.use(AgoPlugin, { baseUrl: "https://acme.useago.com", agent: "support" });
1808
+ ```
1809
+
1810
+ ```ts
1811
+ // Angular
1812
+ provideAgo({ baseUrl: "https://acme.useago.com", agent: "support" });
1813
+ ```
1814
+
1815
+ ---
1816
+
1817
+ ## Zero-config auto-detection
1818
+
1819
+ `createAgo()` and `autoDetectConfig()` resolve config from the page, in priority
1820
+ order:
1821
+
1822
+ 1. **`window.AGO`** (the widget config object): `basepath`, `widgetId`,
1823
+ `agent`/`defaultAgent`, `permission`, `email`, `jwt`.
1824
+ 2. **`<meta>` tags**: `ago-base-url`, `ago-widget-id`, `ago-agent`
1825
+ (or `ago-agent-id`), `ago-permission`, `ago-user-email`.
1826
+ 3. **`data-ago-*` attributes** on `<body>` or a `<script>` tag:
1827
+ `data-ago-base-url`, `data-ago-widget-id`, `data-ago-agent`
1828
+ (or `data-ago-agent-id`), `data-ago-permission`, `data-ago-user-email`.
1829
+ 4. **Explicit overrides** passed to the function.
1830
+
1831
+ ```html
1832
+ <meta name="ago-base-url" content="https://acme.useago.com" />
1833
+ <meta name="ago-agent" content="support" />
1834
+ ```
1835
+
1836
+ ```ts
1837
+ import { createAgo, autoDetectConfig } from "@useago/sdk";
1838
+
1839
+ const client = createAgo(); // throws if no baseUrl found
1840
+ const client2 = createAgo({ debug: true }); // detect + override
1841
+
1842
+ const config = autoDetectConfig(); // → AgoConfig | null (no throw)
1843
+ ```
1844
+
1845
+ ---
1846
+
1847
+ ## Updating config at runtime
1848
+
1849
+ Common after login (attaching a JWT) or switching agents:
1850
+
1851
+ ```ts
1852
+ client.updateConfig({ userJwt: token, userEmail: "jane@acme.com" });
1853
+ ```
1854
+
1855
+ Changing `permission` to a falsy value removes the header. In React, the
1856
+ `useAgo` hook re-applies `agent`, `permission`, `userEmail`, `userJwt`,
1857
+ `debug` and `warnOnEmptyReply` automatically when those props change (changing
1858
+ `baseUrl`/`widgetId` recreates the client).
1859
+
1860
+ `updateConfig` validates the merged result: omitting a key (or passing it as
1861
+ `undefined`) keeps the current value; an explicit empty or non-string `baseUrl`
1862
+ throws `config_missing_base_url` and leaves the client unchanged.
1863
+
1864
+ ---
1865
+
1866
+ ## Errors
1867
+
1868
+ All SDK errors extend `AgoError` (`code`, optional `statusCode`). Catch and
1869
+ branch on the subclass:
1870
+
1871
+ ```ts
1872
+ import {
1873
+ AgoError,
1874
+ AgoApiError, // non-2xx HTTP response (has type, statusCode, param, docUrl)
1875
+ AgoNetworkError, // fetch failed / offline (has originalError)
1876
+ AgoStreamError, // SSE stream failure
1877
+ AgoFunctionError, // a client-side function handler threw (has functionName)
1878
+ } from "@useago/sdk";
1879
+
1880
+ try {
1881
+ await client.sendMessage("hi");
1882
+ } catch (err) {
1883
+ if (err instanceof AgoApiError) {
1884
+ console.error(`API ${err.statusCode}: ${err.message} (${err.code})`);
1885
+ } else if (err instanceof AgoNetworkError) {
1886
+ console.error("Network problem", err.originalError);
1887
+ } else if (err instanceof AgoError) {
1888
+ console.error(err.code, err.message);
1889
+ }
1890
+ }
1891
+ ```
1892
+
1893
+ `isStreamNetworkError(err)` is exported for detecting recoverable stream drops.
1894
+
1895
+ ### Error codes
1896
+
1897
+ `code` is the compatibility surface: match on it, never on message text, which
1898
+ may be reworded in any release.
1899
+
1900
+ | Code | Thrown by | Meaning | Fix |
1901
+ | --- | --- | --- | --- |
1902
+ | `config_missing_base_url` | `AgoError` | `baseUrl` missing, empty, or not a string (also from `createAgo()` and `mountChatWidget`) | Pass `baseUrl`, e.g. `https://YOUR-DOMAIN.api.useago.com` (the live demo endpoint `https://playground.api.useago.com` works without a tenant) |
1903
+ | `config_suspect_base_url` | console warning only | `baseUrl` has no protocol and is not a `/path` | Add `https://`, or use a `/path` for a same-origin proxy |
1904
+ | `network_error` | `AgoNetworkError` | fetch failed (offline, DNS, unreachable host) | Check `baseUrl` is reachable and includes the protocol |
1905
+ | `http_error` | `AgoApiError` | non-2xx without a structured error body | 401/403: check `userJwt`. 404: check `baseUrl` points at the API root |
1906
+ | `stream_no_body` | `AgoStreamError` | the endpoint returned no streamable body | Check `baseUrl` targets an AGO API and no proxy/mock strips the SSE stream |
1907
+ | `stream_error` | `AgoStreamError` | the stream failed mid-flight | Usually a dropped connection; retry the message |
1908
+ | `function_invalid_registration` | `AgoError` | `registerFunction` called without a handler or schema | Use `registerFunction({ name, parameters, handler })` |
1909
+ | `function_error` | `AgoFunctionError` | a registered client function threw | Inspect `functionName` and `originalError` |
1910
+
1911
+ Server-supplied errors (`AgoApiError` with a structured body) keep the
1912
+ backend's own `code` and, when provided, link their docs in the message
1913
+ (`docUrl`).
1914
+
1915
+ ### Empty replies
1916
+
1917
+ The backend currently answers an unknown `agent` slug with an empty 200, which
1918
+ would otherwise fail silently (an empty bubble, nothing thrown). When a reply
1919
+ completes as `DONE` with no content, no tool calls, no client-function
1920
+ invocations and no follow-up replies, the SDK:
1921
+
1922
+ 1. emits [`message:empty`](events-and-streaming.md#events) (always), and
1923
+ 2. warns on the console, once per conversation, unless
1924
+ `warnOnEmptyReply: false`.
1925
+
1926
+ Listen for `message:empty` to handle it in your UI. This client-side detection
1927
+ is temporary: it goes away once the backend returns an explicit error for
1928
+ unknown agents.
1929
+
1930
+ ---
1931
+
1932
+ See also: [Core API](core.md) · [Events & streaming](events-and-streaming.md) · [Custom domain (reverse proxy)](custom-domain.md)