@useago/sdk 1.4.3 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms-full.txt ADDED
@@ -0,0 +1,1910 @@
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.registerPageStateFunction(controls, opts?) / ago.unregisterPageStateFunction(functionName?)
1090
+
1091
+ ago.submitToolCallForm(toolCallId, formData)
1092
+ ago.confirmToolCall(toolCallId)
1093
+ ago.rejectToolCall(toolCallId)
1094
+ ago.submitFeedback(messageId, "positive" | "negative")
1095
+
1096
+ ago.on(event, handler) / ago.off(event, handler)
1097
+ ago.updateConfig(partialConfig)
1098
+ ago.getClient() // underlying AgoClient
1099
+ ago.destroy()
1100
+ ```
1101
+
1102
+ ## 5. Functions, navigation and context
1103
+
1104
+ Register a client-side function the agent can call:
1105
+
1106
+ ```ts
1107
+ this.ago.registerFunction({
1108
+ name: "lookupOrder",
1109
+ description: "Look up an order by ID",
1110
+ parameters: {
1111
+ type: "object",
1112
+ properties: { id: { type: "string", description: "Order ID" } },
1113
+ required: ["id"],
1114
+ },
1115
+ handler: async (args) => this.orders.fetch(args.id as string),
1116
+ });
1117
+ ```
1118
+
1119
+ Rules for functions:
1120
+ - Every parameter property needs a `description`. The agent reads it to decide
1121
+ what to pass.
1122
+ - `required` lists only the params the function truly needs.
1123
+ - The handler must return serializable data (no DOM nodes, no circular refs).
1124
+
1125
+ Register navigation (works with Angular's `Router`):
1126
+
1127
+ ```ts
1128
+ const router = inject(Router);
1129
+ this.ago.registerNavigationFunction((path) => router.navigateByUrl(path), [
1130
+ { name: "dashboard", path: "/dashboard", description: "Main dashboard" },
1131
+ ]);
1132
+ ```
1133
+
1134
+ Register page state, the mirror of navigation (change the current page's state
1135
+ and read it back):
1136
+
1137
+ ```ts
1138
+ this.ago.registerPageStateFunction([
1139
+ {
1140
+ name: "statusFilter",
1141
+ description: "Filter the list by status",
1142
+ schema: { type: "string", enum: ["all", "paid", "overdue"] },
1143
+ get: () => this.status,
1144
+ set: (v) => { this.status = v as string; },
1145
+ },
1146
+ ]);
1147
+ ```
1148
+
1149
+ For client context, reach the client via `getClient()`:
1150
+
1151
+ ```ts
1152
+ this.ago.getClient().enableAutoPageContext();
1153
+ this.ago.getClient().setContext("order", { name: "Order", data: { id } });
1154
+ ```
1155
+
1156
+ ## Exports cheat-sheet (`@useago/sdk/angular`)
1157
+
1158
+ - `AgoService`, `provideAgo`, `AgoProvideOptions`
1159
+ - Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
1160
+ `ToolCallData`, `AgoClientEvents`, `AgoEventName`
1161
+
1162
+ ## Checklist before you finish
1163
+
1164
+ 1. `provideAgo(...)` is registered (or `AgoService` constructed directly).
1165
+ 2. `baseUrl` comes from the environment, not a hardcoded tenant URL.
1166
+ 3. Every subscription is torn down in `ngOnDestroy`, and `ago.destroy()` is called.
1167
+ 4. Every registered function's parameters have descriptions and the handler
1168
+ returns serializable data.
1169
+ 5. Run the project's typecheck and lint.
1170
+
1171
+ ---
1172
+
1173
+ # CONCEPTS
1174
+
1175
+ ---
1176
+
1177
+ <!-- source: docs/general/functions-and-context.md -->
1178
+
1179
+ # Client-side functions & context
1180
+
1181
+ Two capabilities that turn AGO from a chatbot into an agent that acts inside
1182
+ **your** app. Both work in every framework; this page is the canonical
1183
+ reference, and the framework guides show the idiomatic wrappers.
1184
+
1185
+ - [Client-side functions](#client-side-functions): the agent runs code in the browser
1186
+ - [Pre-built helpers](#pre-built-helpers): a catalogue of ready-made functions
1187
+ - [Client context](#client-context): tell the agent what the user is doing
1188
+
1189
+ ---
1190
+
1191
+ ## Client-side functions
1192
+
1193
+ You expose functions (with a name, description and JSON-Schema parameters). The
1194
+ SDK sends those schemas with each message; when the agent decides to call one,
1195
+ the SDK runs your handler in the browser, then submits the result back so the
1196
+ agent can continue. Great for: looking up data, mutating UI, navigation,
1197
+ triggering app actions.
1198
+
1199
+ ### Define a function
1200
+
1201
+ ```ts
1202
+ import { defineFunction } from "@useago/sdk";
1203
+
1204
+ export const lookupOrder = defineFunction({
1205
+ name: "lookupOrder",
1206
+ description: "Look up an order by its ID and return its status.",
1207
+ parameters: {
1208
+ type: "object",
1209
+ properties: {
1210
+ id: { type: "string", description: "The order ID" },
1211
+ },
1212
+ required: ["id"],
1213
+ },
1214
+ handler: async (args) => {
1215
+ // fetchOrder: your own API call
1216
+ const order = await fetchOrder(args.id as string);
1217
+ return { status: order.status, total: order.total }; // returned to the agent
1218
+ },
1219
+ });
1220
+ ```
1221
+
1222
+ `defineFunction` is an identity helper: it just gives you typing and a reusable
1223
+ object. The shape is:
1224
+
1225
+ ```ts
1226
+ interface ClientFunctionDefinition {
1227
+ name: string; // unique
1228
+ description: string; // the LLM reads this to decide when to call it
1229
+ parameters: {
1230
+ type: "object";
1231
+ properties: Record<string, {
1232
+ type: string; // "string" | "number" | "boolean" | "array" | ...
1233
+ description?: string;
1234
+ enum?: string[];
1235
+ default?: unknown;
1236
+ }>;
1237
+ required?: string[];
1238
+ };
1239
+ handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
1240
+ }
1241
+ ```
1242
+
1243
+ > **Tip:** write descriptions for the model. A clear `description` (and per-field
1244
+ > `description`) is what makes the agent call the function correctly. Return a
1245
+ > small, structured object; it's fed straight back into the conversation.
1246
+
1247
+ ### Result size limit
1248
+
1249
+ Everything a handler returns goes into the LLM context, so the SDK caps the
1250
+ serialized result at 50 KB by default. Over the cap, the agent receives a
1251
+ flagged preview instead of the full payload:
1252
+
1253
+ ```json
1254
+ { "truncated": true, "originalBytes": 412031, "maxBytes": 50000, "preview": "...", "hint": "..." }
1255
+ ```
1256
+
1257
+ A console warning fires at 20 KB (with `debug: true`) so you catch it in dev.
1258
+ Tune the cap globally with `maxFunctionResultBytes` on the client config, or
1259
+ per function:
1260
+
1261
+ ```ts
1262
+ defineFunction({
1263
+ name: "exportReport",
1264
+ maxResultBytes: 200_000, // this one is allowed to be big (Infinity disables)
1265
+ // ...
1266
+ });
1267
+ ```
1268
+
1269
+ If you hit the cap, the fix is usually in the handler: return only the fields
1270
+ the agent needs, add `limit`/filter parameters to the schema, or push the data
1271
+ to the UI (page state) and return a short summary like `{ displayed: 25, total: 1200 }`.
1272
+
1273
+ ### Register a function
1274
+
1275
+ ```ts
1276
+ // Single object (preferred)
1277
+ client.registerFunction(lookupOrder);
1278
+
1279
+ // Short alias, also accepts an array
1280
+ client.register([lookupOrder, cancelOrder]);
1281
+
1282
+ // Classic 3-arg form
1283
+ client.registerFunction("lookupOrder", handler, { description, parameters });
1284
+
1285
+ // Remove it
1286
+ client.unregisterFunction("lookupOrder");
1287
+ ```
1288
+
1289
+ Per framework:
1290
+
1291
+ | Framework | Idiomatic API |
1292
+ | --- | --- |
1293
+ | Core | `client.registerFunction(def)` / `client.register([...])` |
1294
+ | React | `useAgoFunction(def)` (auto-cleanup) or `<AgoProvider tools={[…]}>` |
1295
+ | Vue | `useAgoFunction(name, options)` (auto-cleanup) |
1296
+ | Angular | `agoService.registerFunction(def)` |
1297
+
1298
+ ### Navigation shortcut
1299
+
1300
+ A common case (letting the agent move the user around) has a dedicated helper
1301
+ that builds the function for you:
1302
+
1303
+ ```ts
1304
+ client.registerNavigationFunction(
1305
+ (path) => router.push(path), // your navigate fn
1306
+ [
1307
+ { name: "pricing", path: "/pricing", description: "Pricing page" },
1308
+ { name: "docs", path: "/docs", description: "Documentation" },
1309
+ { name: "orderDetail", path: "/orders/:id", description: "One order's detail page" },
1310
+ ],
1311
+ );
1312
+ ```
1313
+
1314
+ This registers a `navigateToPage` function whose `page` parameter is an enum of
1315
+ your route names. React/Vue offer `useAgoNavigation(navigate, routes)` with
1316
+ lifecycle cleanup.
1317
+
1318
+ Paths can contain `:param` placeholders. Each placeholder becomes an explicit
1319
+ top-level argument of `navigateToPage` (`{ page: "orderDetail", id: "42" }`
1320
+ navigates to `/orders/42`), so one route covers every detail page of an entity.
1321
+ Values are URL-encoded. A call that misses a required param navigates nowhere
1322
+ and returns an error telling the agent what to retry with. Placeholders cannot
1323
+ be named `page` (that argument selects the route).
1324
+
1325
+ It also registers a dynamic context entry ("Current page") that reports the page
1326
+ the user is on (by route name, plus URL and title) on every message, so the agent
1327
+ knows where it is after it navigates, not just how to navigate away. Call
1328
+ `client.unregisterNavigationFunction()` to remove both the function and that
1329
+ context.
1330
+
1331
+ ### Page state shortcut
1332
+
1333
+ The mirror of navigation: instead of moving the user to another page, let the
1334
+ agent change the state of the page they're on (filters, sort, view mode,
1335
+ selection…) and read the current state back. One helper builds both halves.
1336
+
1337
+ ```ts
1338
+ client.registerPageStateFunction([
1339
+ {
1340
+ name: "statusFilter",
1341
+ description: "Filter the list by review status",
1342
+ schema: { type: "string", enum: ["all", "pending", "approved"] },
1343
+ get: () => filters.status, // current value → context
1344
+ set: (v) => setFilters({ ...filters, status: v }), // apply the change
1345
+ },
1346
+ {
1347
+ name: "sort",
1348
+ description: "Sort order of the list",
1349
+ schema: { type: "string", enum: ["newest", "oldest"] },
1350
+ get: () => filters.sort,
1351
+ set: (v) => setFilters({ ...filters, sort: v }),
1352
+ },
1353
+ ]);
1354
+
1355
+ client.unregisterPageStateFunction(); // pass the functionName if you customised it
1356
+ ```
1357
+
1358
+ This does two things:
1359
+
1360
+ - Synthesizes **one** client function (`setPageState` by default; override with
1361
+ `{ functionName }`). Each control becomes one optional property, so the agent
1362
+ sets only the controls the user asked for and leaves the rest untouched.
1363
+ - Registers a dynamic context entry ("Page state") reporting each control's
1364
+ current `get()` value, so the agent knows what to change. Controls without a
1365
+ `get()` are write-only and don't appear in context.
1366
+
1367
+ It's fire-and-forget: the handler applies each `set()` locally and returns
1368
+ `{ success, applied }`. The new state reaches the agent through the context on
1369
+ the next message. React/Vue offer `useAgoPageState(controls, opts?)` with
1370
+ lifecycle cleanup; Angular exposes `agoService.registerPageStateFunction(...)`.
1371
+
1372
+ ### Navigate then change the page in one go
1373
+
1374
+ A request like "open the invoices page and show only the overdue ones" needs two
1375
+ steps: the agent navigates, then changes the new page's state. But the
1376
+ destination page only registers its `setPageState` (and current state) once it
1377
+ mounts, so the agent can't do both in a single turn.
1378
+
1379
+ `useAgoAutoContinueAfterNavigation()` closes the gap. Mount it once inside
1380
+ `AgoProvider`:
1381
+
1382
+ ```tsx
1383
+ function AppShell() {
1384
+ useAgoNavigation(navigate, routes);
1385
+ useAgoPageState(controls); // on each pilotable page
1386
+ useAgoAutoContinueAfterNavigation(); // once, near the provider
1387
+ }
1388
+ ```
1389
+
1390
+ How the gap is bridged depends on the client-functions mode:
1391
+
1392
+ - **Pause mode** (recommended, `clientFunctionsMode: "pause"` in the config —
1393
+ needs a backend with pause/resume support): the backend pauses the turn on the
1394
+ navigation call. The SDK submits the function result and resumes the SAME turn
1395
+ via `POST /messages/{id}/continue`; the hook only delays that resume until the
1396
+ destination's `useAgoPageState` registered. No extra prompt, no second turn,
1397
+ and the agent sees the real function results.
1398
+
1399
+ - **Placeholder mode** (legacy default): when the navigating turn ends, the hook
1400
+ waits for the destination page to register, then sends a continuation message
1401
+ with `{ hidden: true }` — kept in the model's context, never displayed
1402
+ (`useMessages` and `ChatWidget` filter hidden messages). Without backend
1403
+ support for `hidden` the mechanism still works but the nudge shows as a message.
1404
+
1405
+ A paused turn also survives a full page reload: after `getConversation(...)`,
1406
+ call `client.resumePendingClientFunctions(conversation)` to re-run the waiting
1407
+ functions, submit their results, and resume the turn (opt-in — re-running a
1408
+ navigation on reload navigates again).
1409
+
1410
+ ### Observe invocations
1411
+
1412
+ ```ts
1413
+ client.on("function:invoke", ({ functionName, arguments: args }) =>
1414
+ console.log("Agent called", functionName, args),
1415
+ );
1416
+ client.on("function:result", ({ result, error }) =>
1417
+ console.log("Returned", result, error),
1418
+ );
1419
+ ```
1420
+
1421
+ ---
1422
+
1423
+ ## Pre-built helpers
1424
+
1425
+ The SDK ships a catalogue of common browser actions as ready-made function
1426
+ definitions. Import them from `@useago/sdk` (or `@useago/sdk/helpers`), wire up a
1427
+ handler where needed, and register.
1428
+
1429
+ | Helper | What it does | Handler needed? |
1430
+ | --- | --- | --- |
1431
+ | `showToast` | Show a toast notification | ✅ wire to your toast lib |
1432
+ | `showNotification` | Browser `Notification` (asks permission) | built-in |
1433
+ | `openUrl` | Open a URL in a new tab | built-in |
1434
+ | `copyToClipboard` | Copy text to clipboard | built-in |
1435
+ | `setTheme` | Set `data-theme` on `<html>` (light/dark/system) | built-in |
1436
+ | `showConfirmDialog` | `window.confirm` and return the choice | built-in |
1437
+ | `getUserLocation` | Geolocation (asks permission) | built-in |
1438
+ | `scrollToElement` | Scroll to a CSS selector | built-in |
1439
+ | `setLocalStorage` / `getLocalStorage` | Read/write `localStorage` | built-in |
1440
+ | `highlightElement` | Outline an element (guided tours) | built-in |
1441
+ | `submitForm` | Fill & submit an HTML form | built-in |
1442
+ | `trackEvent` | Custom analytics event | ➖ logs to console by default |
1443
+
1444
+ ### Wiring a handler
1445
+
1446
+ Some helpers ship a no-op/console default (`showToast`, `trackEvent`). Attach
1447
+ your implementation without mutating the original with `withHandler`:
1448
+
1449
+ ```ts
1450
+ import { showToast, withHandler } from "@useago/sdk";
1451
+
1452
+ client.register(
1453
+ withHandler(showToast, (args) => {
1454
+ myToast(args.message as string); // myToast: your own toast handler
1455
+ return { shown: true };
1456
+ }),
1457
+ );
1458
+ ```
1459
+
1460
+ In React you can wire helpers declaratively on the provider:
1461
+
1462
+ ```tsx
1463
+ <AgoProvider
1464
+ baseUrl="…"
1465
+ helpers={{
1466
+ copyToClipboard: true, // built-in handler
1467
+ showToast: (args) => toast(args.message as string), // custom
1468
+ }}
1469
+ >
1470
+ ```
1471
+
1472
+ ---
1473
+
1474
+ ## Client context
1475
+
1476
+ Context is structured data describing the user's current situation. It's sent
1477
+ with **every** message so the agent answers in context, without the user having
1478
+ to explain where they are.
1479
+
1480
+ ### Static context
1481
+
1482
+ ```ts
1483
+ client.setContext("order-page", {
1484
+ name: "Order detail",
1485
+ description: "User is viewing an order",
1486
+ data: { orderId: "123", status: "shipped" },
1487
+ });
1488
+
1489
+ client.removeContext("order-page");
1490
+ ```
1491
+
1492
+ Shape:
1493
+
1494
+ ```ts
1495
+ interface ContextEntry {
1496
+ name?: string; // human label, e.g. "Order detail"
1497
+ description?: string; // what this context represents
1498
+ data?: Record<string, unknown>; // structured data for the AI
1499
+ }
1500
+ ```
1501
+
1502
+ ### Dynamic context
1503
+
1504
+ A function evaluated on every send, perfect for data outside your render state
1505
+ (global stores, refs, computed values). Return `null` to skip.
1506
+
1507
+ ```ts
1508
+ // cart / cartTotal(): a variable and helper from your outer scope or store
1509
+ client.addDynamicContext("cart", () => ({
1510
+ name: "Cart",
1511
+ data: { itemCount: cart.length, total: cartTotal() },
1512
+ }));
1513
+
1514
+ client.removeDynamicContext("cart");
1515
+ ```
1516
+
1517
+ ### Auto page context
1518
+
1519
+ One call attaches the current URL + document title on every message:
1520
+
1521
+ ```ts
1522
+ client.enableAutoPageContext();
1523
+ ```
1524
+
1525
+ (In React, set `<AgoProvider pageContext="auto">`.)
1526
+
1527
+ ### Per framework
1528
+
1529
+ | Framework | Idiomatic API |
1530
+ | --- | --- |
1531
+ | Core | `setContext` / `addDynamicContext` / `enableAutoPageContext` |
1532
+ | React | `useAgoContext(entryOrFn, key?)` + `pageContext="auto"` |
1533
+ | Vue | core API inside `onMounted` / `onUnmounted` |
1534
+ | Angular | `agoService.getClient().setContext(…)` |
1535
+
1536
+ ### Inspect what's being sent
1537
+
1538
+ ```ts
1539
+ const snapshot = client.getContextSnapshot();
1540
+ // → { entries: { "order-page": { name, description, data }, … } } | null
1541
+ ```
1542
+
1543
+ ---
1544
+
1545
+ See also: [Events & streaming](events-and-streaming.md) ·
1546
+ [Core API](core.md) · [Testing](testing.md)
1547
+
1548
+ ---
1549
+
1550
+ <!-- source: docs/general/events-and-streaming.md -->
1551
+
1552
+ # Events & streaming
1553
+
1554
+ `AgoClient` streams responses over Server-Sent Events and re-emits them as typed
1555
+ events. You can consume them three ways, from lowest to highest level:
1556
+
1557
+ 1. Raw events: `client.on(event, handler)`
1558
+ 2. Callback helpers: `onMessage`, `onMessageChunk`, …
1559
+ 3. An async generator: `createMessageStream`
1560
+
1561
+ All three are framework-agnostic and live in `@useago/sdk`.
1562
+
1563
+ ---
1564
+
1565
+ ## 1. Raw events
1566
+
1567
+ ```ts
1568
+ client.on("message:chunk", ({ content }) => append(content)); // append: your own render fn
1569
+ client.off("message:chunk", handler);
1570
+ client.once("message:complete", (m) => console.log(m.content));
1571
+
1572
+ const final = await client.waitFor("message:complete", { timeout: 10_000 });
1573
+ ```
1574
+
1575
+ | Event | Payload |
1576
+ | --- | --- |
1577
+ | `message:start` | `{ conversationId, messageId }` |
1578
+ | `message:chunk` | `{ content, conversationId, messageId }` |
1579
+ | `message:answer-complete` | `AgoMessage`: the main answer text is done, but follow-up replies may still be pending; fires once, before `message:complete` |
1580
+ | `message:complete` | `AgoMessage` |
1581
+ | `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 |
1582
+ | `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). |
1583
+ | `message:error` | `{ error, code?, conversationId?, messageId? }`: `code` is the stable [error code](configuration.md#error-codes) when the failure was an `AgoError` |
1584
+ | `conversation:loaded` | `Conversation`: a full conversation was loaded from the server (e.g. after a page reload), with messages and persisted tool calls |
1585
+ | `context:changed` | `ContextSnapshot \| null`: the client-side context changed (entry added/removed, or a stateful helper updated its store) |
1586
+ | `toolCall:received` | `ToolCallData` |
1587
+ | `toolCall:form` | `ToolCallData` (only when the tool call is a form) |
1588
+ | `function:invoke` | `{ invocationId, functionName, arguments, conversationId }` |
1589
+ | `function:result` | `{ invocationId, result, error? }` |
1590
+ | `form:submitted` | `{ name, values, result }`: a form collector submitted successfully; `result` is the submit response (the third-party API's answer) |
1591
+ | `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 |
1592
+ | `connection:status` | `{ connected }` |
1593
+
1594
+ `waitFor` rejects if the optional `timeout` (ms) elapses first.
1595
+
1596
+ ---
1597
+
1598
+ ## 2. Callback helpers
1599
+
1600
+ Thin wrappers that return an **unsubscribe** function, handy when you don't want
1601
+ to keep a reference to the handler.
1602
+
1603
+ ```ts
1604
+ import {
1605
+ onMessage,
1606
+ onMessageChunk,
1607
+ onMessageStart,
1608
+ onMessageError,
1609
+ onToolCall,
1610
+ onFunctionInvoke,
1611
+ } from "@useago/sdk";
1612
+
1613
+ const unsub = onMessageChunk(client, ({ content }) => {
1614
+ outputEl.textContent += content;
1615
+ });
1616
+ // later
1617
+ unsub();
1618
+
1619
+ onMessage(client, (msg) => console.log("complete:", msg.content));
1620
+ ```
1621
+
1622
+ | Helper | Subscribes to |
1623
+ | --- | --- |
1624
+ | `onMessage(client, cb)` | `message:complete` |
1625
+ | `onMessageChunk(client, cb)` | `message:chunk` |
1626
+ | `onMessageStart(client, cb)` | `message:start` |
1627
+ | `onMessageError(client, cb)` | `message:error` |
1628
+ | `onToolCall(client, cb)` | `toolCall:received` |
1629
+ | `onFunctionInvoke(client, cb)` | `function:invoke` |
1630
+ | `onFormSubmitted(client, cb)` | `form:submitted` |
1631
+ | `onFormError(client, cb)` | `form:error` |
1632
+
1633
+ ---
1634
+
1635
+ ## 3. Async generator
1636
+
1637
+ `createMessageStream` fires a message and yields a typed event for each step:
1638
+ no manual subscribe/unsubscribe, just `for await`.
1639
+
1640
+ ```ts
1641
+ import { createMessageStream } from "@useago/sdk";
1642
+
1643
+ for await (const ev of createMessageStream(client, "Hello!", { conversationId })) {
1644
+ switch (ev.type) {
1645
+ case "start": console.log("started", ev.data.messageId); break;
1646
+ case "chunk": process.stdout.write(ev.data.content); break;
1647
+ case "toolCall": console.log("tool call", ev.data); break;
1648
+ case "complete": console.log("\ndone:", ev.data.content); break;
1649
+ case "error": console.error(ev.data.error); break;
1650
+ }
1651
+ }
1652
+ ```
1653
+
1654
+ Options: `{ conversationId?, agentId?, files? }`. The generator cleans up its
1655
+ listeners automatically when the loop ends.
1656
+
1657
+ ---
1658
+
1659
+ ## Tool calls
1660
+
1661
+ When the agent needs structured input (a form), a confirmation, or shows a
1662
+ status/progress indicator, it emits a tool call. Listen and respond:
1663
+
1664
+ ```ts
1665
+ client.on("toolCall:form", async (toolCall) => {
1666
+ // toolCall.formSchema describes the fields to render
1667
+ const values = await renderFormSomehow(toolCall.formSchema); // render your own form UI here
1668
+ await client.submitToolCallForm(toolCall.id, values);
1669
+ });
1670
+
1671
+ client.on("toolCall:received", async (toolCall) => {
1672
+ if (toolCall.type === "confirmation_input") {
1673
+ const ok = window.confirm(toolCall.message ?? "Confirm?");
1674
+ ok ? await client.confirmToolCall(toolCall.id)
1675
+ : await client.rejectToolCall(toolCall.id);
1676
+ }
1677
+ });
1678
+ ```
1679
+
1680
+ `ToolCallType` is one of: `form`, `confirmation_input`, `status_message`,
1681
+ `progress_indicator`, `client_function`, `reasoning`, `mcp_ui_resource`.
1682
+
1683
+ ---
1684
+
1685
+ ## Framework equivalents
1686
+
1687
+ | Framework | How to subscribe |
1688
+ | --- | --- |
1689
+ | Core | `client.on(...)` or the helpers above |
1690
+ | React | `client.on(...)` in `useEffect` (get the client via `useAgoClient()`), or rely on `useChat`/`useMessages` which manage this for you |
1691
+ | Vue | `useAgoEvents(event, handler)` (auto-cleanup) |
1692
+ | Angular | `messages$`, `chunks$`, `errors$`, `messageStart$` Observables, or `ago.on(...)` |
1693
+
1694
+ ---
1695
+
1696
+ See also: [Core API](core.md) · [Functions & context](functions-and-context.md)
1697
+
1698
+ ---
1699
+
1700
+ <!-- source: docs/general/configuration.md -->
1701
+
1702
+ # Configuration & auth
1703
+
1704
+ Every entry point ultimately builds an `AgoClient` from an `AgoConfig`. This
1705
+ page documents each option, how it maps to request headers, runtime updates,
1706
+ auto-detection, and error handling.
1707
+
1708
+ ---
1709
+
1710
+ ## `AgoConfig`
1711
+
1712
+ ```ts
1713
+ interface AgoConfig {
1714
+ baseUrl: string; // required, e.g. https://YOUR-DOMAIN.useago.com
1715
+ widgetId?: string; // X-User-Anon-Id header; auto-generated + persisted if omitted
1716
+ agent?: string; // default agent (id or slug) for new conversations
1717
+ defaultAgentId?: string; // same as `agent`; `agent` is preferred
1718
+ permission?: string; // X-Widget-Permission header
1719
+ userEmail?: string; // X-User-Email header, identify the end user
1720
+ userJwt?: string; // Authorization: Bearer <jwt>, authenticated users
1721
+ debug?: boolean; // enable verbose console logging
1722
+ warnOnEmptyReply?: boolean; // default true; see "Empty replies" below
1723
+ clientFunctionsMode?: "placeholder" | "pause"; // default "placeholder"
1724
+ maxFunctionResultBytes?: number; // default 50000; cap on client function results
1725
+ }
1726
+ ```
1727
+
1728
+ | Option | Required | Maps to | Notes |
1729
+ | --- | :---: | --- | --- |
1730
+ | `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. |
1731
+ | `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. |
1732
+ | `agent` / `defaultAgentId` | — | `agent_id` in the body | Per-message `agentId` overrides it. `agent` wins if both are set. |
1733
+ | `permission` | — | `X-Widget-Permission` | Mirrors the widget's `window.AGO.permission`. |
1734
+ | `userEmail` | — | `X-User-Email` | Identifies the user to AGO. |
1735
+ | `userJwt` | — | `Authorization: Bearer …` | For authenticated sessions. |
1736
+ | `debug` | — | — | Turns on the SDK's logger. |
1737
+ | `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. |
1738
+ | `clientFunctionsMode` | — | `client_functions_mode` in the body | `"pause"` makes the agent stop on client function call(s) and resume 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)). Default `"placeholder"`: the turn continues on a placeholder and results are only visible on later turns. Needs a backend with pause/resume support; older backends ignore the flag. Per-message override via `sendMessage(..., { clientFunctionsMode })`. |
1739
+ | `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). |
1740
+
1741
+ ---
1742
+
1743
+ ## Debug logging
1744
+
1745
+ `debug: true` turns on the SDK's internal logger. While enabled, the client
1746
+ writes verbose messages to the browser console, each prefixed with `[AGO SDK]`:
1747
+ client init, HTTP/stream requests, and registered functions/context. Examples:
1748
+
1749
+ ```
1750
+ [AGO SDK] AgoClient initialized
1751
+ [AGO SDK] POST (stream) https://acme.useago.com/api/sdk/v1/messages {…}
1752
+ [AGO SDK] ClientContext set: order-page
1753
+ ```
1754
+
1755
+ - **Default is `false`**: all of those messages are silent.
1756
+ - **Errors are always logged** (`console.error`), whether `debug` is on or off.
1757
+ - Use it in development; **leave it off in production** to keep users' consoles
1758
+ clean.
1759
+ - Toggle it at runtime with `client.updateConfig({ debug: true })` (or `false`).
1760
+
1761
+ ```ts
1762
+ const ago = new AgoClient({ baseUrl: "https://acme.useago.com", debug: true });
1763
+
1764
+ // Or detect config from the DOM and only override `debug`:
1765
+ import { createAgo } from "@useago/sdk";
1766
+ const ago2 = createAgo({ debug: true });
1767
+ ```
1768
+
1769
+ ---
1770
+
1771
+ ## Examples per framework
1772
+
1773
+ ```ts
1774
+ // Core
1775
+ const ago = new AgoClient({ baseUrl: "https://acme.useago.com", agent: "support" });
1776
+ ```
1777
+
1778
+ ```tsx
1779
+ // React
1780
+ <AgoProvider baseUrl="https://acme.useago.com" agent="support" />
1781
+ ```
1782
+
1783
+ ```ts
1784
+ // Vue
1785
+ app.use(AgoPlugin, { baseUrl: "https://acme.useago.com", agent: "support" });
1786
+ ```
1787
+
1788
+ ```ts
1789
+ // Angular
1790
+ provideAgo({ baseUrl: "https://acme.useago.com", agent: "support" });
1791
+ ```
1792
+
1793
+ ---
1794
+
1795
+ ## Zero-config auto-detection
1796
+
1797
+ `createAgo()` and `autoDetectConfig()` resolve config from the page, in priority
1798
+ order:
1799
+
1800
+ 1. **`window.AGO`** (the widget config object): `basepath`, `widgetId`,
1801
+ `agent`/`defaultAgent`, `permission`, `email`, `jwt`.
1802
+ 2. **`<meta>` tags**: `ago-base-url`, `ago-widget-id`, `ago-agent`
1803
+ (or `ago-agent-id`), `ago-permission`, `ago-user-email`.
1804
+ 3. **`data-ago-*` attributes** on `<body>` or a `<script>` tag:
1805
+ `data-ago-base-url`, `data-ago-widget-id`, `data-ago-agent`
1806
+ (or `data-ago-agent-id`), `data-ago-permission`, `data-ago-user-email`.
1807
+ 4. **Explicit overrides** passed to the function.
1808
+
1809
+ ```html
1810
+ <meta name="ago-base-url" content="https://acme.useago.com" />
1811
+ <meta name="ago-agent" content="support" />
1812
+ ```
1813
+
1814
+ ```ts
1815
+ import { createAgo, autoDetectConfig } from "@useago/sdk";
1816
+
1817
+ const client = createAgo(); // throws if no baseUrl found
1818
+ const client2 = createAgo({ debug: true }); // detect + override
1819
+
1820
+ const config = autoDetectConfig(); // → AgoConfig | null (no throw)
1821
+ ```
1822
+
1823
+ ---
1824
+
1825
+ ## Updating config at runtime
1826
+
1827
+ Common after login (attaching a JWT) or switching agents:
1828
+
1829
+ ```ts
1830
+ client.updateConfig({ userJwt: token, userEmail: "jane@acme.com" });
1831
+ ```
1832
+
1833
+ Changing `permission` to a falsy value removes the header. In React, the
1834
+ `useAgo` hook re-applies `agent`, `permission`, `userEmail`, `userJwt`,
1835
+ `debug` and `warnOnEmptyReply` automatically when those props change (changing
1836
+ `baseUrl`/`widgetId` recreates the client).
1837
+
1838
+ `updateConfig` validates the merged result: omitting a key (or passing it as
1839
+ `undefined`) keeps the current value; an explicit empty or non-string `baseUrl`
1840
+ throws `config_missing_base_url` and leaves the client unchanged.
1841
+
1842
+ ---
1843
+
1844
+ ## Errors
1845
+
1846
+ All SDK errors extend `AgoError` (`code`, optional `statusCode`). Catch and
1847
+ branch on the subclass:
1848
+
1849
+ ```ts
1850
+ import {
1851
+ AgoError,
1852
+ AgoApiError, // non-2xx HTTP response (has type, statusCode, param, docUrl)
1853
+ AgoNetworkError, // fetch failed / offline (has originalError)
1854
+ AgoStreamError, // SSE stream failure
1855
+ AgoFunctionError, // a client-side function handler threw (has functionName)
1856
+ } from "@useago/sdk";
1857
+
1858
+ try {
1859
+ await client.sendMessage("hi");
1860
+ } catch (err) {
1861
+ if (err instanceof AgoApiError) {
1862
+ console.error(`API ${err.statusCode}: ${err.message} (${err.code})`);
1863
+ } else if (err instanceof AgoNetworkError) {
1864
+ console.error("Network problem", err.originalError);
1865
+ } else if (err instanceof AgoError) {
1866
+ console.error(err.code, err.message);
1867
+ }
1868
+ }
1869
+ ```
1870
+
1871
+ `isStreamNetworkError(err)` is exported for detecting recoverable stream drops.
1872
+
1873
+ ### Error codes
1874
+
1875
+ `code` is the compatibility surface: match on it, never on message text, which
1876
+ may be reworded in any release.
1877
+
1878
+ | Code | Thrown by | Meaning | Fix |
1879
+ | --- | --- | --- | --- |
1880
+ | `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) |
1881
+ | `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 |
1882
+ | `network_error` | `AgoNetworkError` | fetch failed (offline, DNS, unreachable host) | Check `baseUrl` is reachable and includes the protocol |
1883
+ | `http_error` | `AgoApiError` | non-2xx without a structured error body | 401/403: check `userJwt`. 404: check `baseUrl` points at the API root |
1884
+ | `stream_no_body` | `AgoStreamError` | the endpoint returned no streamable body | Check `baseUrl` targets an AGO API and no proxy/mock strips the SSE stream |
1885
+ | `stream_error` | `AgoStreamError` | the stream failed mid-flight | Usually a dropped connection; retry the message |
1886
+ | `function_invalid_registration` | `AgoError` | `registerFunction` called without a handler or schema | Use `registerFunction({ name, parameters, handler })` |
1887
+ | `function_error` | `AgoFunctionError` | a registered client function threw | Inspect `functionName` and `originalError` |
1888
+
1889
+ Server-supplied errors (`AgoApiError` with a structured body) keep the
1890
+ backend's own `code` and, when provided, link their docs in the message
1891
+ (`docUrl`).
1892
+
1893
+ ### Empty replies
1894
+
1895
+ The backend currently answers an unknown `agent` slug with an empty 200, which
1896
+ would otherwise fail silently (an empty bubble, nothing thrown). When a reply
1897
+ completes as `DONE` with no content, no tool calls, no client-function
1898
+ invocations and no follow-up replies, the SDK:
1899
+
1900
+ 1. emits [`message:empty`](events-and-streaming.md#events) (always), and
1901
+ 2. warns on the console, once per conversation, unless
1902
+ `warnOnEmptyReply: false`.
1903
+
1904
+ Listen for `message:empty` to handle it in your UI. This client-side detection
1905
+ is temporary: it goes away once the backend returns an explicit error for
1906
+ unknown agents.
1907
+
1908
+ ---
1909
+
1910
+ See also: [Core API](core.md) · [Events & streaming](events-and-streaming.md) · [Custom domain (reverse proxy)](custom-domain.md)