@useago/sdk 1.5.2 → 1.5.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -0
  3. package/dist/AgoClient-BW0YZsis.js +1681 -0
  4. package/dist/AgoClient-Ba3ICq4K.cjs +8 -0
  5. package/dist/angular.cjs +1 -1
  6. package/dist/angular.d.cts +352 -0
  7. package/dist/angular.d.ts +352 -0
  8. package/dist/angular.js +1 -1
  9. package/dist/{createConversationSession-DPrrr70o.js → createConversationSession-BjsgjL-v.js} +3 -3
  10. package/dist/createConversationSession-okoz3teC.cjs +1 -0
  11. package/dist/createFormCollector-BWIh1pMl.cjs +1 -0
  12. package/dist/createFormCollector-Bay2UpRv.js +279 -0
  13. package/dist/createMockClient-DcuNlufr.js +97 -0
  14. package/dist/createMockClient-Dw-cyISN.cjs +1 -0
  15. package/dist/devtools.d.cts +352 -0
  16. package/dist/devtools.d.ts +352 -0
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.cts +564 -0
  19. package/dist/index.d.ts +564 -0
  20. package/dist/index.js +97 -90
  21. package/dist/react.cjs +3 -3
  22. package/dist/react.d.cts +430 -0
  23. package/dist/react.d.ts +430 -0
  24. package/dist/react.js +600 -443
  25. package/dist/testing.cjs +1 -1
  26. package/dist/testing.d.cts +352 -0
  27. package/dist/testing.d.ts +352 -0
  28. package/dist/testing.js +1 -1
  29. package/dist/vue.cjs +1 -1
  30. package/dist/vue.d.cts +373 -0
  31. package/dist/vue.d.ts +373 -0
  32. package/dist/vue.js +133 -110
  33. package/dist/widget.cjs +1 -1
  34. package/dist/widget.d.cts +349 -0
  35. package/dist/widget.d.ts +349 -0
  36. package/dist/widget.js +7 -7
  37. package/llms-full.txt +29 -0
  38. package/package.json +1 -1
  39. package/dist/AgoClient-5VGybmvz.cjs +0 -8
  40. package/dist/AgoClient-DXrH2AMi.js +0 -964
  41. package/dist/createConversationSession-0WTqcCLK.cjs +0 -1
  42. package/dist/createFormCollector-1GKKwQV_.cjs +0 -1
  43. package/dist/createFormCollector-DYh90kiG.js +0 -305
  44. package/dist/createMockClient-C-wrjb2C.cjs +0 -1
  45. package/dist/createMockClient-we-XoPom.js +0 -79
package/dist/widget.d.ts CHANGED
@@ -1,3 +1,19 @@
1
+ declare interface ActivityEntry {
2
+ ts: number;
3
+ actor: "user" | "agent";
4
+ name: string;
5
+ summary: string;
6
+ data?: Record<string, unknown>;
7
+ }
8
+
9
+ declare interface ActivityInput {
10
+ actor?: "user" | "agent";
11
+ name: string;
12
+ summary: string;
13
+ data?: Record<string, unknown>;
14
+ ts?: number;
15
+ }
16
+
1
17
  /**
2
18
  * Agent information
3
19
  */
@@ -36,6 +52,8 @@ declare class AgoClient {
36
52
  private functionRegistry;
37
53
  private contextRegistry;
38
54
  private eventEmitter;
55
+ private activityLedger;
56
+ private lastSentPageState;
39
57
  private config;
40
58
  /** Conversations already warned about an empty reply (one warning each). */
41
59
  private warnedEmptyConversations;
@@ -49,6 +67,12 @@ declare class AgoClient {
49
67
  private pendingApprovals;
50
68
  /** App-provided gate deciding WHEN a paused turn resumes (see {@link registerResumeGate}). */
51
69
  private resumeGate;
70
+ /**
71
+ * Proactive-mode controller, set when the client was created with
72
+ * `proactive` options or when `createAgoProactive(client, opts)` was called.
73
+ * `null` otherwise.
74
+ */
75
+ proactive: ProactiveController | null;
52
76
  constructor(config: AgoConfig);
53
77
  /**
54
78
  * Send a message and receive a streaming response
@@ -219,6 +243,14 @@ declare class AgoClient {
219
243
  * Get all registered function schemas
220
244
  */
221
245
  getRegisteredFunctions(): ClientFunctionSchema[];
246
+ /**
247
+ * Execute a registered client function locally, through the same
248
+ * FunctionRegistry path (result-size guard, error wrapping) as agent-invoked
249
+ * calls. Used by proactive nudge actions, where the user's explicit click on
250
+ * the action button constitutes the approval — nothing is submitted to the
251
+ * backend (there is no tool call to answer).
252
+ */
253
+ executeClientFunction(name: string, args: Record<string, unknown>): Promise<unknown>;
222
254
  /**
223
255
  * Register a navigation function that lets AGO navigate users to different pages.
224
256
  *
@@ -320,6 +352,9 @@ declare class AgoClient {
320
352
  */
321
353
  notifyContextChanged(): void;
322
354
  /* Excluded from this release type: emitFormEvent */
355
+ /* Excluded from this release type: emitProactiveEvent */
356
+ /* Excluded from this release type: getHttp */
357
+ /* Excluded from this release type: getConfiguredAgent */
323
358
  /**
324
359
  * Enable automatic capture of the current browser page (URL + title).
325
360
  * Injected as a dynamic context entry named `browser-page`.
@@ -329,6 +364,19 @@ declare class AgoClient {
329
364
  * Get the current context snapshot.
330
365
  */
331
366
  getContextSnapshot(): ContextSnapshot | null;
367
+ private buildSendContext;
368
+ /**
369
+ * Record something the user just did so the agent is aware of it on the next
370
+ * message. `actor` defaults to `"user"`. Keep `summary` short and high-signal
371
+ * (no raw payloads, secrets, or PII).
372
+ */
373
+ recordActivity(entry: ActivityInput): void;
374
+ /** Recent user + agent actions, oldest first (a copy). */
375
+ getRecentActivity(): ActivityEntry[];
376
+ clearActivity(): void;
377
+ private addActivity;
378
+ private recordAgentAction;
379
+ private registerActivityContext;
332
380
  /**
333
381
  * Subscribe to an event
334
382
  */
@@ -364,6 +412,7 @@ declare class AgoClient {
364
412
  * Clean up resources
365
413
  */
366
414
  destroy(): void;
415
+ /* Excluded from this release type: reviveAfterDestroy */
367
416
  }
368
417
 
369
418
  /**
@@ -480,6 +529,27 @@ declare interface AgoClientEvents {
480
529
  "connection:status": {
481
530
  connected: boolean;
482
531
  };
532
+ /**
533
+ * A proactive nudge passed every governor gate and is ready to display.
534
+ * `useProactiveNudge` / `<AgoNudge>` consume this; custom UIs should call
535
+ * `client.proactive.shown(nudge)` when they render it (impression tracking
536
+ * + frequency caps), then `accept(nudge)` / `dismiss(nudge)`.
537
+ */
538
+ "nudge:ready": ProactiveNudgeInstance;
539
+ /** A nudge was displayed to the user (fires once per nudge). */
540
+ "nudge:shown": {
541
+ nudge: ProactiveNudgeInstance;
542
+ };
543
+ /** The user dismissed the nudge (its trigger is suppressed, see governor). */
544
+ "nudge:dismissed": {
545
+ nudge: ProactiveNudgeInstance;
546
+ };
547
+ /** The user accepted the nudge (its action ran / the chat is being seeded). */
548
+ "nudge:accepted": {
549
+ nudge: ProactiveNudgeInstance;
550
+ };
551
+ /** A user- or agent-action was recorded into the activity ledger. */
552
+ "activity:recorded": ActivityEntry;
483
553
  }
484
554
 
485
555
  /**
@@ -541,6 +611,24 @@ declare interface AgoConfig {
541
611
  * with `maxResultBytes` on its definition; `Infinity` disables the guard.
542
612
  */
543
613
  maxFunctionResultBytes?: number;
614
+ /**
615
+ * Max number of recent actions kept in the in-memory activity ledger that
616
+ * rides along as the `activity:recent` context entry. Older entries drop
617
+ * first. Default `10`. Larger values give the agent more history at the cost
618
+ * of tokens on every message. Each entry's `data` is also size-clamped (long
619
+ * strings truncated, large arrays capped) so one event can't bloat the context.
620
+ */
621
+ maxActivityEntries?: number;
622
+ /**
623
+ * Enable the proactive mode: declarative triggers evaluated client-side
624
+ * against friction signals (dwell/idle time, rage clicks, route bounces…)
625
+ * that can surface a nudge before the user opens the chat. Equivalent to
626
+ * calling `createAgoProactive(client, proactive)` — the resulting controller
627
+ * is available as `client.proactive`. Requires the tenant-level kill-switch
628
+ * (`GET /api/sdk/v1/config` → `proactive.enabled`) to be on; disabled by
629
+ * default otherwise.
630
+ */
631
+ proactive?: ProactiveOptions;
544
632
  }
545
633
 
546
634
  declare type AgoEventHandler<K extends AgoEventName> = (data: AgoClientEvents[K]) => void;
@@ -995,6 +1083,43 @@ declare interface FormSchema {
995
1083
  required?: string[];
996
1084
  }
997
1085
 
1086
+ /**
1087
+ * HTTP client with authentication headers
1088
+ */
1089
+ declare class HttpClient {
1090
+ private baseUrl;
1091
+ private headers;
1092
+ constructor(config: AgoConfig);
1093
+ /**
1094
+ * Update configuration (e.g., JWT token)
1095
+ */
1096
+ updateConfig(config: Partial<AgoConfig>): void;
1097
+ /**
1098
+ * Make a GET request
1099
+ */
1100
+ get<T>(path: string): Promise<T>;
1101
+ /**
1102
+ * Make a POST request with JSON body.
1103
+ *
1104
+ * `keepalive: true` lets the request survive a page unload (used by the
1105
+ * proactive event tracker's flush-on-hide — sendBeacon can't carry the auth
1106
+ * headers, so it's a keepalive fetch instead).
1107
+ */
1108
+ post<T>(path: string, body?: unknown, options?: {
1109
+ keepalive?: boolean;
1110
+ }): Promise<T>;
1111
+ /**
1112
+ * Make a POST request and return the raw Response (for streaming)
1113
+ */
1114
+ postStream(path: string, body?: unknown): Promise<Response>;
1115
+ /**
1116
+ * Make a POST request with FormData (for file uploads)
1117
+ */
1118
+ postFormData(path: string, formData: FormData): Promise<Response>;
1119
+ private request;
1120
+ private handleErrorResponse;
1121
+ }
1122
+
998
1123
  /**
999
1124
  * Options for {@link loadFormCollector} / the name-only form of `useFormCollector`:
1000
1125
  * the `name` is required and the definition is fetched from the backend, while any
@@ -1231,6 +1356,182 @@ export declare interface MountChatWidgetOptions {
1231
1356
  }) => void;
1232
1357
  }
1233
1358
 
1359
+ /**
1360
+ * Optional action attached to a nudge. Accepting a `functionName` action
1361
+ * executes the registered client function (same `FunctionRegistry` path as
1362
+ * agent-invoked calls); `setPageState` applies values through the registered
1363
+ * page-state controls.
1364
+ */
1365
+ declare type NudgeAction = {
1366
+ label: string;
1367
+ functionName: string;
1368
+ arguments?: Record<string, unknown>;
1369
+ } | {
1370
+ label: string;
1371
+ setPageState: Record<string, unknown>;
1372
+ };
1373
+
1374
+ /**
1375
+ * Controller returned by {@link createAgoProactive} (also available as
1376
+ * `client.proactive`).
1377
+ */
1378
+ declare interface ProactiveController {
1379
+ /** Persistently opt this browser out of proactive nudges. */
1380
+ optOut(): void;
1381
+ /** Undo {@link optOut}. */
1382
+ optIn(): void;
1383
+ isOptedOut(): boolean;
1384
+ /**
1385
+ * Tell the governor the user is already engaged (e.g. the chat widget is
1386
+ * open) — nudges are suppressed while `true`. Call from the UI layer.
1387
+ */
1388
+ setEngaged(engaged: boolean): void;
1389
+ /** App-pushed field error counter (field NAME only — never values). */
1390
+ signalFieldError(fieldName: string): void;
1391
+ /** Current aggregated friction snapshot. */
1392
+ getSignalsSnapshot(): SignalsSnapshot;
1393
+ /** The nudge currently occupying the single-nudge slot, if any. */
1394
+ getActiveNudge(): ProactiveNudgeInstance | null;
1395
+ /**
1396
+ * Mark the nudge as displayed. Idempotent per nudge; counts it against the
1397
+ * frequency caps, tracks the impression, and emits `nudge:shown`.
1398
+ */
1399
+ shown(nudge: ProactiveNudgeInstance): void;
1400
+ /**
1401
+ * Accept the nudge: run its action (if any), emit `nudge:accepted`, track,
1402
+ * and — for agent nudges or nudges with a `goal` — pre-seed the chat with a
1403
+ * hidden kickoff message so the agent starts the turn with full context.
1404
+ */
1405
+ accept(nudge: ProactiveNudgeInstance): Promise<void>;
1406
+ /** Dismiss the nudge: suppress the trigger, track, emit `nudge:dismissed`. */
1407
+ dismiss(nudge: ProactiveNudgeInstance): void;
1408
+ /** Report a downstream conversion attributable to the nudge (analytics only). */
1409
+ markConverted(nudge: ProactiveNudgeInstance): void;
1410
+ /** Detach everything: listeners, timers, context provider, event flush. */
1411
+ destroy(): void;
1412
+ }
1413
+
1414
+ /** A concrete nudge produced by the engine, carried by every `nudge:*` event. */
1415
+ declare interface ProactiveNudgeInstance {
1416
+ /** Client-generated uuid for this nudge instance (`client_nudge_id`). */
1417
+ id: string;
1418
+ triggerId: string;
1419
+ /** `static` = configured message; `agent` = backend LLM evaluation. */
1420
+ source: "static" | "agent";
1421
+ message: string;
1422
+ action?: NudgeAction;
1423
+ goal?: string;
1424
+ /** See {@link ProactiveTrigger.kickoff}. */
1425
+ kickoff?: string;
1426
+ createdAt: Date;
1427
+ /** Server-side nudge id (agent nudges only). */
1428
+ serverNudgeId?: string;
1429
+ }
1430
+
1431
+ /** Options for {@link createAgoProactive} / `AgoConfig.proactive`. */
1432
+ declare interface ProactiveOptions {
1433
+ triggers: ProactiveTrigger[];
1434
+ /**
1435
+ * Global caps on nudges actually shown. `perSession` is in-memory (page
1436
+ * lifetime); `perDay` is persisted in localStorage with a date rollover —
1437
+ * best-effort by design (private browsing resets it); the costly LLM path is
1438
+ * additionally capped server-side.
1439
+ */
1440
+ frequencyCap?: {
1441
+ perSession?: number;
1442
+ perDay?: number;
1443
+ };
1444
+ /** Evaluation tick, ms. Default 1000. Zero network per tick. */
1445
+ tickMs?: number;
1446
+ /** Storage backend for persisted governance state. Default `localStorage`. */
1447
+ storage?: StorageLike;
1448
+ /** Storage key for persisted governance state. Default `"ago_proactive"`. */
1449
+ storageKey?: string;
1450
+ /** Tuning of the signal detectors (rage-click window, throttles…). */
1451
+ signals?: SignalCollectorTuning;
1452
+ /**
1453
+ * Local demos and tests ONLY: bypass the remote kill-switch. When set, the
1454
+ * `GET /config` fetch is skipped and the kill-switch takes this value.
1455
+ * Leave unset in production — the tenant admin controls the feature, and
1456
+ * the evaluate endpoint stays gated server-side regardless.
1457
+ */
1458
+ enabledOverride?: boolean;
1459
+ }
1460
+
1461
+ /** A declarative proactive trigger. */
1462
+ declare interface ProactiveTrigger {
1463
+ /** Stable id, used for cooldowns, dismissal memory, and analytics. */
1464
+ id: string;
1465
+ /** Conjunction of matchers (see {@link ProactiveTriggerWhen}). */
1466
+ when: ProactiveTriggerWhen;
1467
+ /**
1468
+ * Min time between two firings of this trigger (persisted across reloads).
1469
+ * A firing is the moment the trigger passes the gates — including an agent
1470
+ * evaluation that decides NOT to intervene, so a persistent condition can't
1471
+ * hammer the evaluate endpoint. Default 60 000.
1472
+ */
1473
+ cooldownMs?: number;
1474
+ /**
1475
+ * `'agent'` asks the backend's fast-LLM second filter whether (and how) to
1476
+ * intervene; a `static` object shows the given message with no network call.
1477
+ */
1478
+ intervene: "agent" | {
1479
+ type: "static";
1480
+ message: string;
1481
+ action?: NudgeAction;
1482
+ };
1483
+ /**
1484
+ * What the agent should help with. Sent to the evaluate endpoint, and used to
1485
+ * pre-seed the chat turn when the user accepts the nudge.
1486
+ */
1487
+ goal?: string;
1488
+ /**
1489
+ * User-voice line used to OPEN the conversation when the user accepts this
1490
+ * nudge and no conversation exists yet — a hidden kickoff cannot start one
1491
+ * (the backend rejects it). Sent as a visible user message, so write it in
1492
+ * the app's language ("Oui, aidez-moi à choisir !"). When a conversation is
1493
+ * already open, the SDK sends a hidden meta kickoff instead and this field
1494
+ * is unused. Without it, accepting with no open conversation skips the chat
1495
+ * pre-seed (the nudge action still runs).
1496
+ */
1497
+ kickoff?: string;
1498
+ /**
1499
+ * How long a dismissal of this trigger's nudge suppresses it (persisted).
1500
+ * Default 24h.
1501
+ */
1502
+ resurfaceAfterMs?: number;
1503
+ }
1504
+
1505
+ /**
1506
+ * Conditions of a proactive trigger. Pure conjunction: EVERY specified matcher
1507
+ * must pass against the current {@link SignalsSnapshot}. Numeric fields are
1508
+ * `>=` thresholds.
1509
+ */
1510
+ declare interface ProactiveTriggerWhen {
1511
+ /**
1512
+ * Route pattern the current pathname must match. Supports the SDK's route
1513
+ * styles: exact (`/billing`), `:param` segments (`/users/:id`), single-segment
1514
+ * wildcards (`/a/*` / `/b`), and a trailing `/*` that matches one or more
1515
+ * remaining segments (`/dossier/*`).
1516
+ */
1517
+ route?: string;
1518
+ /** Min time since the last user activity, ms. */
1519
+ idleMs?: number;
1520
+ /** Min time spent on the current route, ms. */
1521
+ dwellMs?: number;
1522
+ /**
1523
+ * Partial deep match against the registered page state (funnel step,
1524
+ * filters…). Every key present here must deep-match the live value.
1525
+ */
1526
+ pageState?: Partial<Record<string, unknown>>;
1527
+ /** Min rage-click bursts on the current route. */
1528
+ rageClicks?: number;
1529
+ /** Min A→B→A→B route oscillations this session. */
1530
+ routeBounces?: number;
1531
+ /** Per-field minimum error counts (field NAME → min count). */
1532
+ fieldErrors?: Record<string, number>;
1533
+ }
1534
+
1234
1535
  /**
1235
1536
  * Tiny, dependency-free markdown → DOM renderer for the vanilla chat widget.
1236
1537
  *
@@ -1291,6 +1592,54 @@ declare interface SendMessageOptions {
1291
1592
  clientFunctionsMode?: ClientFunctionsMode;
1292
1593
  }
1293
1594
 
1595
+ /** Tuning knobs for the collector's detectors. All optional. */
1596
+ declare interface SignalCollectorTuning {
1597
+ /** Min gap between heavy activity work (route checks, notifications), ms. Default 250. */
1598
+ activityThrottleMs?: number;
1599
+ /** Rage click: clicks needed to count a burst. Default 3. */
1600
+ rageClickCount?: number;
1601
+ /** Rage click: max time between first and last click of a burst, ms. Default 1000. */
1602
+ rageClickWindowMs?: number;
1603
+ /** Rage click: max radius between clicks of a burst, px. Default 24. */
1604
+ rageClickRadiusPx?: number;
1605
+ /** Max routes kept in the recent-route sequence. Default 10. */
1606
+ maxRecentRoutes?: number;
1607
+ /** Max typed signals kept in the ring buffer. Default 50. */
1608
+ maxSignals?: number;
1609
+ }
1610
+
1611
+ /**
1612
+ * Aggregated view of the current friction state. Registered as a dynamic
1613
+ * context provider (key `friction_signals`) so it rides along as
1614
+ * `client_context` on every message, and sent as `signals` to the proactive
1615
+ * evaluate endpoint.
1616
+ */
1617
+ declare interface SignalsSnapshot {
1618
+ /** Current pathname. */
1619
+ route: string;
1620
+ /** Time spent on the current route, ms. */
1621
+ dwellMs: number;
1622
+ /** Time since the last user activity (pointer/key/scroll/touch), ms. */
1623
+ idleMs: number;
1624
+ /** Rage-click bursts detected on the current route. */
1625
+ rageClicks: number;
1626
+ /**
1627
+ * A→B→A→B route oscillations detected in the recent route sequence. Rolling
1628
+ * session counter (cross-route by nature); trigger cooldowns and dismissal
1629
+ * memory keep it from re-firing forever.
1630
+ */
1631
+ routeBounces: number;
1632
+ /** App-pushed field error counters (field NAME → count), current route only. */
1633
+ fieldErrors: Record<string, number>;
1634
+ /** Most recent pathnames, oldest first (bounded). */
1635
+ recentRoutes: string[];
1636
+ /**
1637
+ * Merged `AgoStateControl.get()` snapshot of the registered page-state
1638
+ * controls (funnel step, filters…), or `null` when none are registered.
1639
+ */
1640
+ pageState: Record<string, unknown> | null;
1641
+ }
1642
+
1294
1643
  /**
1295
1644
  * SSE stream chunk data
1296
1645
  */
package/dist/widget.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as qt, i as Kt, m as Xt, o as Yt, s as Qt, t as Zt } from "./AgoClient-DXrH2AMi.js";
2
- import { r as Jt, t as en } from "./createFormCollector-DYh90kiG.js";
3
- import { t as tn } from "./createConversationSession-DPrrr70o.js";
1
+ import { a as qt, b as Kt, i as Xt, o as Yt, s as Qt, t as Zt } from "./AgoClient-BW0YZsis.js";
2
+ import { t as Jt } from "./createConversationSession-BjsgjL-v.js";
3
+ import { r as en, t as tn } from "./createFormCollector-Bay2UpRv.js";
4
4
  var nn = '"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', ke = `var(--ago-font, ${nn})`, ut = {
5
5
  font: "--ago-font",
6
6
  radius: "--ago-radius",
@@ -618,10 +618,10 @@ function kn(t, o) {
618
618
  message: r,
619
619
  mode: "static"
620
620
  } : r, Ue = oe.message, Ge = oe.mode ?? "static", It = "speed" in oe && oe.speed || 45, Ve = "followUpReplies" in oe && oe.followUpReplies || void 0;
621
- if (!o.client && !o.config?.baseUrl) throw new Xt("mountChatWidget requires either `client` or `config` (with a baseUrl).", "config_missing_base_url");
621
+ if (!o.client && !o.config?.baseUrl) throw new Kt("mountChatWidget requires either `client` or `config` (with a baseUrl).", "config_missing_base_url");
622
622
  const E = o.client ?? new Zt(o.config), je = wn(t), z = m === "left" || m === "right", At = z && (o.launcher ?? !0);
623
623
  let Z = z ? p : !0, me = !1;
624
- const _t = o.mobile?.breakpoint ?? 768, Le = o.mobile?.trigger ?? "tap", qe = typeof window < "u" && !!window.matchMedia, T = qe ? window.matchMedia(`(max-width: ${_t}px)`) : void 0, Lt = qe ? window.matchMedia("(prefers-reduced-motion: reduce)") : void 0, re = !z && !!T, Ne = o.persistConversation, ge = Ne ? tn(Ne === !0 ? {} : Ne) : void 0;
624
+ const _t = o.mobile?.breakpoint ?? 768, Le = o.mobile?.trigger ?? "tap", qe = typeof window < "u" && !!window.matchMedia, T = qe ? window.matchMedia(`(max-width: ${_t}px)`) : void 0, Lt = qe ? window.matchMedia("(prefers-reduced-motion: reduce)") : void 0, re = !z && !!T, Ne = o.persistConversation, ge = Ne ? Jt(Ne === !0 ? {} : Ne) : void 0;
625
625
  let P = o.conversationId ?? ge?.getLastActiveThread() ?? void 0, u = [], J = !1, le = null, K;
626
626
  const Ke = [];
627
627
  function Nt(e) {
@@ -865,7 +865,7 @@ function kn(t, o) {
865
865
  });
866
866
  $e.push(s);
867
867
  };
868
- for (const e of l ?? []) e.schema != null ? rt(en(e)) : Jt(E, e).then((n) => rt(n)).catch(() => {
868
+ for (const e of l ?? []) e.schema != null ? rt(tn(e)) : en(E, e).then((n) => rt(n)).catch(() => {
869
869
  });
870
870
  async function we(e, n) {
871
871
  const s = e.trim();
@@ -882,7 +882,7 @@ function kn(t, o) {
882
882
  content: s,
883
883
  role: "user",
884
884
  status: "DONE",
885
- attachments: n && n.length > 0 ? Kt(n) : void 0,
885
+ attachments: n && n.length > 0 ? Xt(n) : void 0,
886
886
  createdAt: /* @__PURE__ */ new Date()
887
887
  }), u.push({
888
888
  id: `temp-assistant-${S}`,
package/llms-full.txt CHANGED
@@ -1389,6 +1389,11 @@ It's fire-and-forget: the handler applies each `set()` locally and returns
1389
1389
  the next message. React/Vue offer `useAgoPageState(controls, opts?)` with
1390
1390
  lifecycle cleanup; Angular exposes `agoService.registerPageStateFunction(...)`.
1391
1391
 
1392
+ The SDK also tells the agent **what changed**: on each message it diffs the page
1393
+ state against the previous message and adds a `state:delta` entry listing the
1394
+ fields that changed. A navigation (a different page) resets the baseline instead
1395
+ of reporting a diff, so a page switch never shows up as a spurious change.
1396
+
1392
1397
  ### Navigate then change the page in one go
1393
1398
 
1394
1399
  A request like "open the invoices page and show only the overdue ones" needs two
@@ -1617,6 +1622,29 @@ client.enableAutoPageContext();
1617
1622
 
1618
1623
  (In React, set `<AgoProvider pageContext="auto">`.)
1619
1624
 
1625
+ ### Activity ledger
1626
+
1627
+ Tell the agent what the user (or the agent) just did, so it can reason about
1628
+ recent actions. Call `recordActivity` for meaningful user actions; the agent's
1629
+ own client-function calls (navigation, page-state changes, custom functions) are
1630
+ recorded automatically.
1631
+
1632
+ ```ts
1633
+ client.recordActivity({
1634
+ name: "order.shipped",
1635
+ summary: 'User changed order #123 from "Pending" to "Shipped"',
1636
+ });
1637
+ ```
1638
+
1639
+ The recent window (last 10 by default, oldest dropped first) rides along as the
1640
+ `activity:recent` context entry. Change the size with `maxActivityEntries` in the
1641
+ config. `actor` defaults to `"user"`; keep `summary` short and high-signal (no raw
1642
+ payloads or secrets). Each entry's `data` is size-clamped before it is stored (long
1643
+ strings truncated with a `…[truncated N chars]` marker, large arrays capped), so one
1644
+ event can't bloat the context. Read or clear the log with
1645
+ `client.getRecentActivity()` / `client.clearActivity()`, observe it via
1646
+ `client.on("activity:recorded", …)`, or use the React `useAgoActivityLog()` hook.
1647
+
1620
1648
  ### Per framework
1621
1649
 
1622
1650
  | Framework | Idiomatic API |
@@ -1681,6 +1709,7 @@ const final = await client.waitFor("message:complete", { timeout: 10_000 });
1681
1709
  | `function:invoke` | `{ invocationId, functionName, arguments, conversationId }` |
1682
1710
  | `function:awaiting-approval` | `{ invocationId, functionName, arguments, conversationId }`: a client function call is held pending user approval (pause mode + an `approvalPolicy`/`requiresApproval` match). The turn stays at `WAITING_CLIENT`; call `approveFunction(invocationId)` to run it and resume, or `rejectFunction(invocationId)` to decline. See [Approval gate](functions-and-context.md#approval-gate-ask-before-running) |
1683
1711
  | `function:result` | `{ invocationId, result, error? }` |
1712
+ | `activity:recorded` | `ActivityEntry`: a user- or agent-action was recorded into the activity ledger (via `recordActivity`, or auto-recorded when the agent calls a client function). See [Activity ledger](functions-and-context.md#activity-ledger) |
1684
1713
  | `form:submitted` | `{ name, values, result }`: a form collector submitted successfully; `result` is the submit response (the third-party API's answer) |
1685
1714
  | `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 |
1686
1715
  | `connection:status` | `{ connected }` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useago/sdk",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Create In-APP AI agents that answers question and performs actions. Works with React, Vue, Angular and vanilla JS/TS.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,8 +0,0 @@
1
- var d=class extends Error{constructor(t,e,n){super(t),this.code=e,this.statusCode=n,this.name="AgoError"}},g=class x extends d{constructor(e,n,i,s,o,a){super(e,n,i),this.type=s,this.param=o,this.docUrl=a,this.name="AgoApiError"}static fromResponse(e,n){const i=e.error;return new x(i.doc_url?`${i.message} See ${i.doc_url}`:i.message,i.code,n,i.type,i.param,i.doc_url)}},y=class extends d{constructor(t,e){super(t,"network_error"),this.originalError=e,this.name="AgoNetworkError"}},C=class extends d{constructor(t,e="stream_error"){super(t,e),this.name="AgoStreamError"}},E=class extends d{constructor(t,e,n){super(t,"function_error"),this.functionName=e,this.originalError=n,this.name="AgoFunctionError"}},P=class{constructor(t=!1){this.enabled=t}enable(){this.enabled=!0}disable(){this.enabled=!1}log(...t){this.enabled&&console.log("[AGO SDK]",...t)}warn(...t){this.enabled&&console.warn("[AGO SDK]",...t)}error(...t){console.error("[AGO SDK]",...t)}debug(...t){this.enabled&&console.debug("[AGO SDK]",...t)}},c=new P,S="ago_widget_id";function j(){if(typeof localStorage<"u"){const e=localStorage.getItem(S);if(e)return e}const t=typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`ago-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,12)}`;if(typeof localStorage<"u")try{localStorage.setItem(S,t)}catch{}return t}var O=class{constructor(t){this.baseUrl=t.baseUrl.replace(/\/$/,""),this.headers={"X-User-Anon-Id":t.widgetId||j()},t.userEmail&&(this.headers["X-User-Email"]=t.userEmail),t.userJwt&&(this.headers.Authorization=`Bearer ${t.userJwt}`),t.permission&&(this.headers["X-Widget-Permission"]=t.permission)}updateConfig(t){t.baseUrl&&(this.baseUrl=t.baseUrl.replace(/\/$/,"")),t.widgetId&&(this.headers["X-User-Anon-Id"]=t.widgetId),t.userEmail&&(this.headers["X-User-Email"]=t.userEmail),t.userJwt&&(this.headers.Authorization=`Bearer ${t.userJwt}`),t.permission!==void 0&&(t.permission?this.headers["X-Widget-Permission"]=t.permission:delete this.headers["X-Widget-Permission"])}async get(t){return this.request("GET",t)}async post(t,e){return this.request("POST",t,e)}async postStream(t,e){const n=`${this.baseUrl}${t}`;c.debug("POST (stream)",n,e);try{const i=await fetch(n,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:e?JSON.stringify(e):void 0});return i.ok||await this.handleErrorResponse(i),i}catch(i){throw i instanceof g?i:new y(`Network error: ${i instanceof Error?i.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,i instanceof Error?i:void 0)}}async postFormData(t,e){const n=`${this.baseUrl}${t}`;c.debug("POST (formData)",n);const i={...this.headers};try{const s=await fetch(n,{method:"POST",headers:i,body:e});return s.ok||await this.handleErrorResponse(s),s}catch(s){throw s instanceof g?s:new y(`Network error: ${s instanceof Error?s.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,s instanceof Error?s:void 0)}}async request(t,e,n){const i=`${this.baseUrl}${e}`;c.debug(t,i,n);try{const s=await fetch(i,{method:t,headers:{...this.headers,"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0});return s.ok||await this.handleErrorResponse(s),s.status===204?void 0:s.json()}catch(s){throw s instanceof g?s:new y(`Network error: ${s instanceof Error?s.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,s instanceof Error?s:void 0)}}async handleErrorResponse(t){let e;try{e=await t.json()}catch{}if(e?.error)throw g.fromResponse(e,t.status);let n="";throw t.status===401||t.status===403?n=" Check `userJwt` (and that it has not expired).":t.status===404&&(n=" Check that `baseUrl` points at your AGO API root."),new g(`HTTP ${t.status}: ${t.statusText}.${n}`,"http_error",t.status,"api_error")}},D=5e4,M=2e4,B=2e3,F=class{constructor(t){this.functions=new Map,this.defaultMaxResultBytes=t?.maxResultBytes??D}setDefaultMaxResultBytes(t){this.defaultMaxResultBytes=t}register(t,e,n){if(typeof t=="object"){const{name:r,handler:l,description:u,parameters:h,maxResultBytes:m,requiresApproval:p}=t;return this.register(r,l,{description:u,parameters:h,maxResultBytes:m,requiresApproval:p})}const i=t;if(!e||!n)throw new d(`registerFunction("${i}"): both a handler and a schema are required. Pass them as arguments or use the single-object form: registerFunction({ name, parameters, handler }).`,"function_invalid_registration");this.functions.has(i)&&c.warn(`Function "${i}" is being overwritten`);const{maxResultBytes:s,requiresApproval:o,...a}=n;this.functions.set(i,{schema:{...a,name:i},handler:e,maxResultBytes:s,requiresApproval:o}),c.log(`Registered function: ${i}`)}unregister(t){const e=this.functions.delete(t);return e&&c.log(`Unregistered function: ${t}`),e}get(t){return this.functions.get(t)}has(t){return this.functions.has(t)}getSchemas(){return Array.from(this.functions.values()).map(t=>t.schema)}async execute(t,e){const n=this.functions.get(t);if(!n)throw new E(`Function "${t}" is not registered`,t);c.log(`Executing function: ${t}`,e);try{const i=await n.handler(e);return c.log(`Function ${t} completed:`,i),this.guardResultSize(t,i,n.maxResultBytes)}catch(i){throw c.error(`Function ${t} failed:`,i),new E(`Function "${t}" execution failed: ${i instanceof Error?i.message:"Unknown error"}`,t,i instanceof Error?i:void 0)}}guardResultSize(t,e,n){let i;try{i=JSON.stringify(e)}catch{return e}if(i===void 0)return e;const s=new TextEncoder().encode(i).length,o=n??this.defaultMaxResultBytes;return s>o?(c.warn(`Function "${t}" result is ${s} bytes, over the ${o}-byte limit; a truncated preview was sent to the agent instead. Return fewer fields, paginate, or raise maxResultBytes for this function.`),{truncated:!0,originalBytes:s,maxBytes:o,preview:i.slice(0,B),hint:`The result of "${t}" exceeded the ${o}-byte limit and was truncated. Request less data (use limit or filter parameters if available), or tell the user the result is too large to process.`}):(s>M&&c.warn(`Function "${t}" returned a large result (${Math.round(s/1024)} KB). Large results consume LLM context: return fewer fields, paginate, or push the data to the UI and return a summary.`),e)}clear(){this.functions.clear(),c.log("Cleared all registered functions")}get size(){return this.functions.size}},k=class{constructor(){this.entries=new Map,this.dynamicProviders=new Map}set(t,e){this.entries.set(t,e),c.log(`ClientContext set: ${t}`)}remove(t){const e=this.entries.delete(t);return e&&c.log(`ClientContext removed: ${t}`),e}addDynamicProvider(t,e){this.dynamicProviders.set(t,e),c.log(`DynamicContext provider added: ${t}`)}removeDynamicProvider(t){const e=this.dynamicProviders.delete(t);return e&&c.log(`DynamicContext provider removed: ${t}`),e}getSnapshot(){if(this.entries.size===0&&this.dynamicProviders.size===0)return null;const t={};for(const[e,n]of this.entries)t[e]=n;for(const[e,n]of this.dynamicProviders)try{const i=n();i&&(t[e]=i)}catch(i){c.error(`DynamicContext provider "${e}" threw:`,i)}return Object.keys(t).length===0?null:{entries:t}}clear(){this.entries.clear(),this.dynamicProviders.clear(),c.log("ClientContext cleared")}};function _(t){return t===null||typeof t!="object"?JSON.stringify(t)??"null":Array.isArray(t)?`[${t.map(_).join(",")}]`:`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${_(t[e])}`).join(",")}}`}var I=class{constructor(t){this.callbacks=t,this.buffer="",this.message={},this.toolCalls=[],this.sources=[],this.followUpReplies=[],this.isFirstChunk=!0,this.answerCompleteEmitted=!1,this.waitingClientEmitted=!1,this.firedClientFunctions=new Set}async processStream(t){if(!t.body)throw new C("Response has no body. The endpoint did not return a stream: check that `baseUrl` points at an AGO API and nothing (proxy, mock) strips the SSE body.","stream_no_body");const e=t.body.getReader(),n=new TextDecoder;try{for(;;){const{done:i,value:s}=await e.read();if(i)break;const o=n.decode(s,{stream:!0});this.buffer+=o,this.processBuffer()}return this.buffer.trim()&&this.processBuffer(),this.buildFinalMessage()}catch(i){const s=i instanceof Error?i:new C("Stream processing failed mid-flight. The connection may have dropped; retry the message.");throw this.callbacks.onError?.(s),s}finally{e.releaseLock()}}processBuffer(){const t=this.buffer.split(`
2
-
3
- `);this.buffer=t.pop()||"";for(const e of t){if(!e.trim())continue;const n=e.split(`
4
- `);for(const i of n)if(!i.startsWith(": ")&&i.startsWith("data: ")){const s=i.slice(6);try{const o=JSON.parse(s);this.handleChunk(o)}catch(o){c.warn("Failed to parse SSE data:",s,o)}}}}handleChunk(t){if(this.callbacks.onRawChunk?.(t),t.message_id&&!this.message.id&&(this.message.id=t.message_id),t.thread?.id&&(this.message.conversationId=t.thread.id,this.isFirstChunk&&this.message.id&&(this.isFirstChunk=!1,this.callbacks.onStart?.({conversationId:this.message.conversationId,messageId:this.message.id}))),t.content!==void 0&&(this.message.content=(this.message.content||"")+t.content,this.message.conversationId&&this.message.id&&this.callbacks.onChunk?.({content:t.content,conversationId:this.message.conversationId,messageId:this.message.id})),t.full_content!==void 0&&(this.message.content=t.full_content),t.status&&(this.message.status=t.status),t.agent&&(this.message.agent={id:t.agent.id,name:t.agent.name,displayName:t.agent.display_name}),t.knowledge_sources&&(this.sources=t.knowledge_sources.map(e=>{const n=e.knowledge_document;return{id:n.id,title:n.title,url:n.use_external_link?n.external_link_url:n.internal_link_url}})),t.type==="client_function"&&t.function_name){const e=`${t.function_name}::${_(t.arguments??{})}`;this.firedClientFunctions.has(e)||(this.firedClientFunctions.add(e),this.callbacks.onClientFunction?.({invocationId:t.id||"",functionName:t.function_name,arguments:t.arguments||{},conversationId:this.message.conversationId||""}))}if(t.tool_call_data&&t.type&&t.type!=="client_function"){const e=this.parseToolCall(t);this.callbacks.onToolCall?.(e);const n=this.toolCalls.findIndex(i=>i.id===e.id);n>=0?this.toolCalls[n]=e:this.toolCalls.push(e)}t.follow_up_replies&&(this.followUpReplies=t.follow_up_replies),this.message.status==="DONE"&&!this.answerCompleteEmitted&&this.message.id&&(this.answerCompleteEmitted=!0,this.callbacks.onAnswerComplete?.(this.buildMessage())),this.message.status==="WAITING_CLIENT"&&!this.waitingClientEmitted&&this.message.id&&(this.waitingClientEmitted=!0,this.callbacks.onWaitingClient?.({conversationId:this.message.conversationId||"",messageId:this.message.id,waitingToolCallIds:t.waiting_tool_call_ids??[]}))}parseToolCall(t){return{id:t.id||"",type:t.type||"status_message",status:t.status||"unknown",toolName:t.tool_name||"",toolDisplayName:t.tool_display_name,message:t.message,formSchema:t.form_schema,data:t.data,functionName:t.function_name,arguments:t.arguments}}buildMessage(){return{id:this.message.id||"",conversationId:this.message.conversationId||"",content:this.message.content||"",role:"assistant",status:this.message.status||"DONE",agent:this.message.agent,sources:this.sources.length>0?this.sources:void 0,toolCalls:this.toolCalls.length>0?this.toolCalls:void 0,followUpReplies:this.followUpReplies.length>0?this.followUpReplies:void 0,createdAt:new Date}}buildFinalMessage(){const t=this.buildMessage();return t.status!=="WAITING_CLIENT"&&this.callbacks.onComplete?.(t),t}};function L(t){const e=t.message.toLowerCase();return e.includes("load failed")||e.includes("failed to fetch")||e.includes("network")||e.includes("abort")}var G=new Set(["image/jpeg","image/png","image/gif","image/webp","image/bmp","image/tiff"]);function U(t){if(!t)return;const e=typeof window<"u"&&window.location?window.location.href:"https://ago.local/";try{const n=new URL(t,e);if(n.protocol==="http:"||n.protocol==="https:"||n.protocol==="blob:")return t}catch{}}function W(t){return t.isSafeImage===!0&&typeof t.contentType=="string"&&t.contentType.startsWith("image/")&&U(t.url)!==void 0}function q(t){if(t==null||t<0)return"";if(t<1024)return`${t} B`;const e=t/1024;return e<1024?`${e.toFixed(1)} KB`:`${(e/1024).toFixed(1)} MB`}function z(t){return{id:String(t.id??""),name:typeof t.name=="string"&&t.name?t.name:"file",contentType:typeof t.content_type=="string"?t.content_type:void 0,fileSize:typeof t.file_size=="number"?t.file_size:void 0,url:typeof t.file_url=="string"?t.file_url:void 0,isSafeImage:t.is_safe_image===!0}}function J(t){return t.map((e,n)=>({id:`local-${n}-${e.name}`,name:e.name,contentType:e.type||void 0,fileSize:e.size,url:typeof URL<"u"&&URL.createObjectURL?URL.createObjectURL(e):void 0,isSafeImage:G.has(e.type)}))}var N=class{constructor(){this.handlers=new Map}on(t,e){this.handlers.has(t)||this.handlers.set(t,new Set),this.handlers.get(t).add(e)}off(t,e){const n=this.handlers.get(t);n&&n.delete(e)}emit(t,e){const n=this.handlers.get(t);n&&n.forEach(i=>{try{i(e)}catch(s){console.error(`Error in event handler for ${String(t)}:`,s)}})}once(t,e){const n=(i=>{this.off(t,n),e(i)});this.on(t,n)}waitFor(t,e){return new Promise((n,i)=>{let s;const o=(a=>{s&&clearTimeout(s),n(a)});this.once(t,o),e?.timeout&&(s=setTimeout(()=>{this.off(t,o),i(new Error(`waitFor("${String(t)}") timed out after ${e.timeout}ms`))},e.timeout))})}removeAllListeners(t){t?this.handlers.delete(t):this.handlers.clear()}},A='`baseUrl` is required, e.g. new AgoClient({ baseUrl: "https://YOUR-DOMAIN.api.useago.com" }). Try the live demo endpoint https://playground.api.useago.com. See docs/general/configuration.md#error-codes (config_missing_base_url).',H=/^[a-z][a-z0-9+.-]*:\/\//i,$=!1;function R(t,e="AgoClient"){if(typeof t!="object"||t===null)throw new d(`${e}: ${A}`,"config_missing_base_url");const n=t.baseUrl;if(typeof n!="string"||n.trim()==="")throw new d(`${e}: ${A}`,"config_missing_base_url");!n.startsWith("/")&&!H.test(n)&&($||($=!0,console.warn(`[AGO] ${e}: \`baseUrl\` has no protocol and is not a relative path (config_suspect_base_url). Requests will fail: Node throws a URL parse error, browsers fetch it as a relative path. Use "https://..." (or a "/path" for a same-origin proxy).`)))}function K(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function b(t){return t.split("/").filter(e=>e.startsWith(":")).map(e=>e.slice(1))}function X(t,e){const n=[];return{path:t.split("/").map(i=>{if(!i.startsWith(":"))return i;const s=i.slice(1),o=e[s];return o==null||o===""?(n.push(s),i):encodeURIComponent(String(o))}).join("/"),missing:n}}function Y(t,e){const n=e.find(s=>s.path===t);if(n)return n;const i=e.find(s=>{if(!s.path.includes(":"))return!1;const o="^"+s.path.split("/").map(a=>a.startsWith(":")?"[^/]+":K(a)).join("/")+"/?$";return new RegExp(o).test(t)});return i||e.filter(s=>s.path!=="/"&&t.startsWith(s.path)).sort((s,o)=>o.path.length-s.path.length)[0]}var V=class T{constructor(e){this.warnedEmptyConversations=new Set,this.pendingResumes=new Map,this.pendingApprovals=new Map,this.resumeGate=null,R(e,"AgoClient"),this.config=e,this.httpClient=new O(e),this.functionRegistry=new F({maxResultBytes:e.maxFunctionResultBytes}),this.contextRegistry=new k,this.eventEmitter=new N,e.debug&&c.enable(),c.log("AgoClient initialized")}async sendMessage(e,n){const i=this.functionRegistry.getSchemas(),s=this.contextRegistry.getSnapshot(),o=this.config.agent||this.config.defaultAgentId,a=this.resolveClientFunctionsMode(n),r={content:e,conversation_id:n?.conversationId,agent_id:n?.agentId||o};i.length>0&&(r.client_functions=i,a==="pause"&&(r.client_functions_mode=a)),s&&(r.client_context=s),n?.hidden&&(r.hidden=!0);let l;if(n?.files&&n.files.length>0){const u=new FormData;u.append("content",e),n.conversationId&&u.append("conversation_id",n.conversationId),(n.agentId||o)&&u.append("agent_id",n.agentId||o||""),i.length>0&&(u.append("client_functions",JSON.stringify(i)),a==="pause"&&u.append("client_functions_mode",a)),s&&u.append("client_context",JSON.stringify(s)),n.hidden&&u.append("hidden","true");for(const h of n.files)u.append("files",h);l=await this.httpClient.postFormData("/api/sdk/v1/messages",u)}else l=await this.httpClient.postStream("/api/sdk/v1/messages",r);return this.processSSEResponse(l)}async processSSEResponse(e){let n=!1;const i=await new I({onRawChunk:s=>{this.eventEmitter.emit("stream:message",s)},onStart:s=>{this.eventEmitter.emit("message:start",s)},onChunk:s=>{this.eventEmitter.emit("message:chunk",s)},onToolCall:s=>{this.eventEmitter.emit("toolCall:received",s),s.type==="form"&&this.eventEmitter.emit("toolCall:form",s)},onClientFunction:async s=>{if(n=!0,this.eventEmitter.emit("function:invoke",s),s.invocationId&&this.requiresApproval(s)){this.pendingApprovals.set(s.invocationId,s),this.eventEmitter.emit("function:awaiting-approval",s);return}await this.handleClientFunctionInvocation(s)},onAnswerComplete:s=>{this.eventEmitter.emit("message:answer-complete",s)},onWaitingClient:s=>{this.eventEmitter.emit("message:waiting-client",s),this.markResumeSignal(s.messageId,s.conversationId,"waitingConfirmed")},onComplete:s=>{this.eventEmitter.emit("message:complete",s)},onError:s=>{this.eventEmitter.emit("message:error",{error:s.message,code:s instanceof d?s.code:void 0})}}).processStream(e);return i.status!=="WAITING_CLIENT"&&this.maybeFlagEmptyReply(i,n),i}resolveClientFunctionsMode(e){return e?.clientFunctionsMode??this.config.clientFunctionsMode??"pause"}registerResumeGate(e){return this.resumeGate=e,()=>{this.resumeGate===e&&(this.resumeGate=null)}}async continueMessage(e){const n=this.functionRegistry.getSchemas(),i=this.contextRegistry.getSnapshot(),s={};n.length>0&&(s.client_functions=n),i&&(s.client_context=i);const o=await this.httpClient.postStream(`/api/sdk/v1/messages/${e}/continue`,s);return this.processSSEResponse(o)}markResumeSignal(e,n,i){if(!e)return;const s=this.pendingResumes.get(e)??{conversationId:n,waitingConfirmed:!1,resultsReady:!1,started:!1};s[i]=!0,n&&(s.conversationId=n),this.pendingResumes.set(e,s),!(!s.waitingConfirmed||!s.resultsReady||s.started)&&(s.started=!0,this.runAutoResume(e,s.conversationId))}async runAutoResume(e,n){try{this.resumeGate&&await this.resumeGate({conversationId:n,messageId:e})}catch(i){c.warn("Resume gate threw; resuming anyway:",i)}this.pendingResumes.delete(e);try{await this.continueMessage(e)}catch(i){c.warn("Auto-resume of paused turn failed:",i)}}maybeFlagEmptyReply(e,n){e.status==="DONE"&&(e.content??"").trim()===""&&!n&&!(e.toolCalls&&e.toolCalls.length>0)&&!(e.followUpReplies&&e.followUpReplies.length>0)&&setTimeout(()=>{if(this.eventEmitter.emit("message:empty",{conversationId:e.conversationId??"",messageId:e.id??""}),this.config.warnOnEmptyReply===!1)return;const i=e.conversationId||"(no conversation)";if(this.warnedEmptyConversations.has(i))return;this.warnedEmptyConversations.add(i);const s=this.config.agent||this.config.defaultAgentId;e.id?console.warn("[AGO] Reply completed with empty content. Possible causes: "+(s?`the agent "${s}" may not exist for this tenant, `:"an unknown `agent` slug, ")+"or the agent replied with no text. Listen for the `message:empty` event to handle this in your app; silence this warning with `warnOnEmptyReply: false`."):console.warn("[AGO] The stream completed without any message data. Possible causes: "+(s?`the agent "${s}" may not exist for this tenant, or `:"")+"`baseUrl` does not point at an AGO endpoint that returns server-sent events. Listen for the `message:empty` event to handle this in your app; silence this warning with `warnOnEmptyReply: false`.")},0)}async handleClientFunctionInvocation(e){let n,i;try{n=await this.functionRegistry.execute(e.functionName,e.arguments)}catch(s){i=s instanceof Error?s.message:"Unknown error",c.error("Client function execution failed:",s)}if(e.invocationId)try{const s=await this.submitToolCallForm(e.invocationId,{result:n,error:i,_type:"client_function_result"});s?.resume?.ready&&this.markResumeSignal(s.resume.message_id,e.conversationId,"resultsReady")}catch(s){c.error("Failed to submit function result:",s)}this.eventEmitter.emit("function:result",{invocationId:e.invocationId,result:n,error:i})}requiresApproval(e){const n=this.config.approvalPolicy;if(n)try{if(n(e))return!0}catch(i){c.warn("approvalPolicy threw; not gating this call:",i)}return this.functionRegistry.get(e.functionName)?.requiresApproval===!0}async approveFunction(e){const n=this.pendingApprovals.get(e);n&&(this.pendingApprovals.delete(e),await this.handleClientFunctionInvocation(n))}async rejectFunction(e){const n=this.pendingApprovals.get(e);if(!n)return;this.pendingApprovals.delete(e);const i={approved:!1,reason:"user_rejected"};try{const s=await this.submitToolCallForm(e,{result:i,_type:"client_function_result"});s?.resume?.ready&&this.markResumeSignal(s.resume.message_id,n.conversationId,"resultsReady")}catch(s){c.error("Failed to submit function rejection:",s)}this.eventEmitter.emit("function:result",{invocationId:e,result:i})}async getConversations(){return(await this.httpClient.get("/api/sdk/v1/conversations")).items.map(e=>({id:e.id,title:e.title,lastMessageDate:new Date(e.last_message_date)}))}async getConversation(e){const n=await this.httpClient.get(`/api/sdk/v1/conversations/${e}`),i={id:n.id,title:n.title,lastMessageDate:new Date(n.last_message_date),messages:n.messages.map(s=>({id:s.id,conversationId:n.id,content:s.content,role:s.role,status:s.status,createdAt:new Date(s.created_at),hidden:s.hidden,toolCalls:T.mapPersistedToolCalls(s.tool_call_data),attachments:s.attachments&&s.attachments.length>0?s.attachments.map(z):void 0}))};return this.eventEmitter.emit("conversation:loaded",i),i}static mapPersistedToolCalls(e){if(!(!e||e.length===0))return e.map(n=>({id:String(n.id??""),type:n.type??"status_message",status:String(n.status??"unknown"),toolName:n.tool_name??"",toolDisplayName:n.tool_display_name,message:n.message,formSchema:n.form_schema,data:n.data,functionName:n.function_name,arguments:n.arguments}))}async getMessages(e){return(await this.getConversation(e)).messages||[]}async resumePendingClientFunctions(e){const n=[...e.messages??[]].reverse().find(r=>r.role==="assistant"&&r.status==="WAITING_CLIENT");if(!n)return null;const i=(n.toolCalls??[]).filter(r=>r.type==="client_function"&&r.status==="waiting_input"&&r.functionName),s=new Set(this.functionRegistry.getSchemas().map(r=>r.name)),o=i.filter(r=>!s.has(r.functionName));if(o.length>0)return c.warn("Cannot resume paused turn: function(s) not registered on this page:",o.map(r=>r.functionName)),null;let a=i.length===0;for(const r of i){const l={invocationId:r.id,functionName:r.functionName,arguments:r.arguments??{},conversationId:e.id};if(this.requiresApproval(l)){this.pendingApprovals.set(r.id,l),this.markResumeSignal(n.id,e.id,"waitingConfirmed"),this.eventEmitter.emit("function:awaiting-approval",l),a=!1;continue}let u,h;try{u=await this.functionRegistry.execute(r.functionName,r.arguments??{})}catch(m){h=m instanceof Error?m.message:"Unknown error",c.error("Client function execution failed:",m)}a=(await this.submitToolCallForm(r.id,{result:u,error:h,_type:"client_function_result"}))?.resume?.ready??a}return a?this.continueMessage(n.id):null}async submitToolCallForm(e,n){return this.httpClient.post(`/api/sdk/v1/tool-calls/${e}/submit`,{formData:n})}async confirmToolCall(e){await this.httpClient.post(`/api/tool-calls/${e}/confirm/`)}async rejectToolCall(e){await this.httpClient.post(`/api/tool-calls/${e}/reject/`)}async getFormCollector(e){const n=await this.httpClient.get(`/api/sdk/v1/forms/${encodeURIComponent(e)}`);return{name:n.name,description:n.description,schema:n.schema,submit:n.submit??void 0}}async submitFormCollector(e,n){return this.httpClient.post("/api/sdk/v1/forms/submit",{name:e,values:n})}async submitFeedback(e,n){await this.httpClient.post(`/api/sdk/v1/messages/${e}/feedback`,{rating:n})}registerFunction(e,n,i){typeof e=="object"?this.functionRegistry.register(e):this.functionRegistry.register(e,n,i)}register(e){if(Array.isArray(e))for(const n of e)this.functionRegistry.register(n);else this.functionRegistry.register(e)}unregisterFunction(e){return this.functionRegistry.unregister(e)}getRegisteredFunctions(){return this.functionRegistry.getSchemas()}registerNavigationFunction(e,n){const i=n.map(r=>r.name),s=n.map(r=>{const l=b(r.path),u=l.length>0?` (requires ${l.map(h=>`"${h}"`).join(", ")})`:"";return`- "${r.name}"${u}: ${r.description}`}).join(`
5
- `),o=new Map;for(const r of n)for(const l of b(r.path))o.set(l,[...o.get(l)??[],r.name]);const a={page:{type:"string",description:"The page to navigate to",enum:i}};for(const[r,l]of o){if(r==="page"){c.error('registerNavigationFunction: a ":page" placeholder collides with the "page" argument and is ignored. Rename the placeholder.');continue}a[r]={type:"string",description:`Value for ":${r}" in the page path. Required when page is ${l.map(u=>`"${u}"`).join(" or ")}.`}}this.registerFunction("navigateToPage",async r=>{const l=r.page,u=n.find(f=>f.name===l);if(!u)return{success:!1,error:`Unknown page: ${l}`};let h={};if(typeof r.params=="string")try{h=JSON.parse(r.params)}catch{}else r.params&&typeof r.params=="object"&&(h=r.params);const m={};for(const f of b(u.path))m[f]=r[f]??h[f];const{path:p,missing:w}=X(u.path,m);if(w.length>0){const f=w.map(v=>`"${v}": "..."`).join(", ");return{success:!1,error:`Page "${l}" needs ${w.map(v=>`"${v}"`).join(", ")}. Retry with { "page": "${l}", ${f} }.`}}return e(p),{success:!0,navigatedTo:p}},{description:`Navigate the user to a page in the application. Available pages:
6
- ${s}`,parameters:{type:"object",properties:a,required:["page"]}}),this.addDynamicContext("current-page",()=>{if(typeof window>"u"||!window.location)return null;const r=window.location.href,l=typeof document<"u"?document.title:void 0,u=Y(window.location.pathname,n),h={url:r};return u&&(h.page=u.name),l&&(h.title=l),{name:"Current page",description:"The page the user is currently viewing.",data:h}})}unregisterNavigationFunction(){this.unregisterFunction("navigateToPage"),this.removeDynamicContext("current-page")}registerPageStateFunction(e,n){const i=n?.functionName??"setPageState",s=new Map(e.map(a=>[a.name,a])),o=e.map(a=>`- "${a.name}": ${a.description}`).join(`
7
- `);this.registerFunction(i,async a=>{const r={};for(const[l,u]of Object.entries(a)){if(u===void 0)continue;const h=s.get(l);h&&(await h.set(u),r[l]=u)}return{success:!0,applied:r}},{description:`Change the state of the current page. Set ONLY the controls the user explicitly asked for; leave the others unset. Available controls:
8
- ${o}`,parameters:{type:"object",properties:Object.fromEntries(e.map(a=>[a.name,{...a.schema,description:a.description}]))},requiresApproval:n?.requiresApproval}),this.addDynamicContext(`page-state:${i}`,()=>{const a=e.filter(l=>typeof l.get=="function");if(a.length===0)return null;const r={};for(const l of a)r[l.name]=l.get();return{name:"Page state",description:"Current, editable state of the page the user is viewing.",data:r}})}unregisterPageStateFunction(e){const n=e??"setPageState";this.unregisterFunction(n),this.removeDynamicContext(`page-state:${n}`)}setContext(e,n){this.contextRegistry.set(e,n),this.notifyContextChanged()}removeContext(e){const n=this.contextRegistry.remove(e);return n&&this.notifyContextChanged(),n}addDynamicContext(e,n){this.contextRegistry.addDynamicProvider(e,n),this.notifyContextChanged()}removeDynamicContext(e){const n=this.contextRegistry.removeDynamicProvider(e);return n&&this.notifyContextChanged(),n}notifyContextChanged(){this.eventEmitter.emit("context:changed",this.contextRegistry.getSnapshot())}emitFormEvent(e,n){this.eventEmitter.emit(e,n)}enableAutoPageContext(){this.addDynamicContext("browser-page",()=>{const e=typeof window<"u"?window.location.href:void 0,n=typeof document<"u"?document.title:void 0;return!e&&!n?null:{name:"Browser page",description:"Current page the user is viewing",data:{url:e,title:n}}})}getContextSnapshot(){return this.contextRegistry.getSnapshot()}on(e,n){this.eventEmitter.on(e,n)}off(e,n){this.eventEmitter.off(e,n)}once(e,n){this.eventEmitter.once(e,n)}waitFor(e,n){return this.eventEmitter.waitFor(e,n)}updateConfig(e){const n={};for(const[s,o]of Object.entries(e))o!==void 0&&(n[s]=o);const i={...this.config,...n};R(i,"AgoClient.updateConfig"),this.config=i,this.httpClient.updateConfig(n),n.debug!==void 0&&(n.debug?c.enable():c.disable()),n.maxFunctionResultBytes!==void 0&&this.functionRegistry.setDefaultMaxResultBytes(n.maxFunctionResultBytes)}destroy(){this.eventEmitter.removeAllListeners(),this.functionRegistry.clear(),this.contextRegistry.clear(),this.pendingResumes.clear(),this.pendingApprovals.clear(),this.resumeGate=null,c.log("AgoClient destroyed")}};Object.defineProperty(exports,"AgoApiError",{enumerable:!0,get:function(){return g}});Object.defineProperty(exports,"AgoClient",{enumerable:!0,get:function(){return V}});Object.defineProperty(exports,"AgoError",{enumerable:!0,get:function(){return d}});Object.defineProperty(exports,"AgoFunctionError",{enumerable:!0,get:function(){return E}});Object.defineProperty(exports,"AgoNetworkError",{enumerable:!0,get:function(){return y}});Object.defineProperty(exports,"AgoStreamError",{enumerable:!0,get:function(){return C}});Object.defineProperty(exports,"ClientContextRegistry",{enumerable:!0,get:function(){return k}});Object.defineProperty(exports,"EventEmitter",{enumerable:!0,get:function(){return N}});Object.defineProperty(exports,"FunctionRegistry",{enumerable:!0,get:function(){return F}});Object.defineProperty(exports,"SSEHandler",{enumerable:!0,get:function(){return I}});Object.defineProperty(exports,"attachmentsFromFiles",{enumerable:!0,get:function(){return J}});Object.defineProperty(exports,"canInlineImage",{enumerable:!0,get:function(){return W}});Object.defineProperty(exports,"formatFileSize",{enumerable:!0,get:function(){return q}});Object.defineProperty(exports,"isStreamNetworkError",{enumerable:!0,get:function(){return L}});Object.defineProperty(exports,"logger",{enumerable:!0,get:function(){return c}});Object.defineProperty(exports,"safeAttachmentUrl",{enumerable:!0,get:function(){return U}});Object.defineProperty(exports,"validateConfig",{enumerable:!0,get:function(){return R}});