@useago/sdk 1.7.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -0
- package/README.md +96 -1
- package/dist/angular.cjs +1 -1
- package/dist/angular.d.cts +402 -5
- package/dist/angular.d.ts +402 -5
- package/dist/angular.js +11 -7
- package/dist/{createConversationSession-DP90nY71.js → createConversationSession-B1Qn-y0I.js} +3 -3
- package/dist/createConversationSession-B3v_xOU7.cjs +1 -0
- package/dist/createFormCollector-FvwDOqnV.cjs +1 -0
- package/dist/{createFormCollector-BivHRIAe.js → createFormCollector-m79zk_NA.js} +1 -1
- package/dist/createMockClient-BEOXI8yW.cjs +1 -0
- package/dist/{createMockClient-BDLW6tVc.js → createMockClient-Beao82tu.js} +31 -18
- package/dist/devtools.cjs +61 -12
- package/dist/devtools.d.cts +403 -5
- package/dist/devtools.d.ts +403 -5
- package/dist/devtools.js +225 -73
- package/dist/errors-BLfjXnLB.js +32 -0
- package/dist/errors-o1IB5fEu.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +489 -7
- package/dist/index.d.ts +489 -7
- package/dist/index.js +124 -118
- package/dist/react.cjs +4 -4
- package/dist/react.d.cts +510 -3
- package/dist/react.d.ts +510 -3
- package/dist/react.js +1036 -752
- package/dist/sheetController-BThJRtOu.cjs +1 -0
- package/dist/sheetController-aEKXIvjk.js +343 -0
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +393 -3
- package/dist/testing.d.ts +393 -3
- package/dist/testing.js +1 -1
- package/dist/types-Ct1hlKwg.js +2408 -0
- package/dist/types-mcW8vB1U.cjs +10 -0
- package/dist/vue.cjs +1 -1
- package/dist/vue.d.cts +615 -3
- package/dist/vue.d.ts +615 -3
- package/dist/vue.js +202 -154
- package/dist/widget.cjs +6 -4
- package/dist/widget.d.cts +733 -5
- package/dist/widget.d.ts +733 -5
- package/dist/widget.js +3996 -786
- package/llms-full.txt +264 -31
- package/package.json +1 -1
- package/dist/AgoClient-7yXQ6R3O.cjs +0 -9
- package/dist/AgoClient-DTkBRGFE.js +0 -1965
- package/dist/createConversationSession-Bv6a15E0.cjs +0 -1
- package/dist/createFormCollector-CwfiYn0V.cjs +0 -1
- package/dist/createMockClient-BSNuN3ys.cjs +0 -1
- package/dist/sheetController-CBUVOroo.js +0 -205
- package/dist/sheetController-DY-sC13T.cjs +0 -1
package/llms-full.txt
CHANGED
|
@@ -314,7 +314,11 @@ ago.registerNavigationFunction((path) => (window.location.href = path), [
|
|
|
314
314
|
|
|
315
315
|
Page state is the mirror: let the agent change the current page's state
|
|
316
316
|
(filters, sort, view mode…) and read it back via each control's `get()`. One
|
|
317
|
-
synthesized `setPageState` function, one optional property per control.
|
|
317
|
+
synthesized `setPageState` function, one optional property per control. The SDK
|
|
318
|
+
validates type and enum, skips unchanged values, drops placeholders, and reports
|
|
319
|
+
each field in `applied`, `unchanged`, or `rejected`. See
|
|
320
|
+
[Page state shortcut](../general/functions-and-context.md#page-state-shortcut)
|
|
321
|
+
for `clearable`, tagged setter outcomes, and the full envelope.
|
|
318
322
|
|
|
319
323
|
```ts
|
|
320
324
|
ago.registerPageStateFunction([
|
|
@@ -382,8 +386,12 @@ widget.destroy(); // removes listeners, uninstalls forms, clears the DOM
|
|
|
382
386
|
While the agent answers, the send button becomes a Stop button that interrupts
|
|
383
387
|
the turn; pass `allowStop: false` to keep a disabled spinner instead.
|
|
384
388
|
Set `placement: "left"` or `"right"` for a fixed full-height side panel with a
|
|
385
|
-
launcher button
|
|
386
|
-
|
|
389
|
+
launcher button, or `placement: "bubble"` for the hosted widget's floating
|
|
390
|
+
bubble (home / chat / history screens); with `loadHomeConfig: true` the bubble's
|
|
391
|
+
home title, subtitle and starter cards come from the AGO dashboard.
|
|
392
|
+
Set `persistConversation: true` to resume the visitor's last
|
|
393
|
+
thread across reloads. Set `feedback: true` to put thumbs under each answer,
|
|
394
|
+
with a "what went wrong?" panel on a thumbs-down. Theme it with the `theme` option or `--ago-*` CSS variables
|
|
387
395
|
on `.ago-chat-widget`. Message text renders as GitHub-flavored markdown and is
|
|
388
396
|
HTML-escaped before it touches the DOM.
|
|
389
397
|
|
|
@@ -398,6 +406,14 @@ await ago.submitToolCallForm(toolCallId, { quantity: 3 });
|
|
|
398
406
|
await ago.confirmToolCall(toolCallId);
|
|
399
407
|
await ago.rejectToolCall(toolCallId);
|
|
400
408
|
await ago.submitFeedback(messageId, "positive");
|
|
409
|
+
// "this answer doesn't work": reasons/comment land in the feedback dashboard
|
|
410
|
+
await ago.submitFeedback(messageId, "negative", {
|
|
411
|
+
reasons: ["inaccurate"], // inaccurate | incomplete | information_not_found | technical_issue
|
|
412
|
+
comment: "The price is from last year.",
|
|
413
|
+
});
|
|
414
|
+
// "this conversation doesn't work": attached to its last answer
|
|
415
|
+
await ago.submitConversationFeedback(conversationId, "negative");
|
|
416
|
+
// or let the widget render the thumbs: mountChatWidget(el, { ..., feedback: true })
|
|
401
417
|
ago.updateConfig({ userJwt: token }); // e.g. after login
|
|
402
418
|
ago.destroy(); // clean up listeners, functions, context
|
|
403
419
|
```
|
|
@@ -571,6 +587,17 @@ each `{ id, title, url? }`):
|
|
|
571
587
|
))}
|
|
572
588
|
```
|
|
573
589
|
|
|
590
|
+
Let the user report an answer that did not work with `<ChatWidget feedback />`,
|
|
591
|
+
or place the row yourself with `<MessageFeedback messageId={m.id} />`. The hook
|
|
592
|
+
behind both is `useFeedback()`:
|
|
593
|
+
|
|
594
|
+
```tsx
|
|
595
|
+
const { submitFeedback, submitConversationFeedback } = useFeedback();
|
|
596
|
+
// reasons: inaccurate | incomplete | information_not_found | technical_issue
|
|
597
|
+
await submitFeedback(m.id, "negative", { reasons: ["inaccurate"], comment: "..." });
|
|
598
|
+
await submitConversationFeedback(conversationId, "negative");
|
|
599
|
+
```
|
|
600
|
+
|
|
574
601
|
Finer-grained hooks: `useMessages({ conversationId? })`,
|
|
575
602
|
`useConversation({ autoLoad? })`. All hooks read the client from context; pass
|
|
576
603
|
`{ client }` to override. Get the raw client with `useAgoClient()` (throws
|
|
@@ -681,9 +708,15 @@ useAgoPageState(controls, {
|
|
|
681
708
|
});
|
|
682
709
|
```
|
|
683
710
|
|
|
684
|
-
`setPageState` then returns `{ success, applied, data }`
|
|
685
|
-
`readPageData` function is registered too. The SDK
|
|
686
|
-
|
|
711
|
+
`setPageState` then returns `{ success, applied, unchanged, rejected?, data }`
|
|
712
|
+
and a read-only `readPageData` function is registered too. The SDK validates
|
|
713
|
+
each field before calling `set()`: type/enum mismatches and unknown controls are
|
|
714
|
+
rejected, values equal to the current `get()` are skipped (`unchanged`), and
|
|
715
|
+
placeholders (`null`, `undefined`, `""`) are silently dropped. A `set()` that
|
|
716
|
+
returns `{ result: "rejected", reason }` or `{ result: "unchanged" }` feeds the
|
|
717
|
+
same envelope. Mark a string control `clearable: true` to let `""` clear it
|
|
718
|
+
instead of being dropped. The SDK waits for `isLoading` to go false before
|
|
719
|
+
reading, and truncates a long list by whole rows. Needs
|
|
687
720
|
`clientFunctionsMode: "pause"` (the default).
|
|
688
721
|
|
|
689
722
|
## 6. Give the agent context: `useAgoContext`
|
|
@@ -739,9 +772,10 @@ Key events: `message:start`, `message:chunk` (`{ content }` per token),
|
|
|
739
772
|
## Exports cheat-sheet (`@useago/sdk/react`)
|
|
740
773
|
|
|
741
774
|
- Provider/context: `AgoProvider`, `useAgoClient`, `useOptionalAgoClient`
|
|
742
|
-
- Hooks: `useAgo`, `useChat`, `useMessages`, `useConversation`, `
|
|
743
|
-
`useAgoNavigation`, `useAgoPageState`, `useAgoContext`,
|
|
744
|
-
|
|
775
|
+
- Hooks: `useAgo`, `useChat`, `useMessages`, `useConversation`, `useFeedback`,
|
|
776
|
+
`useAgoFunction`, `useAgoNavigation`, `useAgoPageState`, `useAgoContext`,
|
|
777
|
+
`useAgoStore`, `useFormCollector`
|
|
778
|
+
- Components: `ChatWidget`, `Message`, `ChatInput`, `Markdown`, `MessageFeedback`
|
|
745
779
|
- Forms: `createFormCollector`
|
|
746
780
|
- Testing: `createMockClient`
|
|
747
781
|
- Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
|
|
@@ -861,6 +895,15 @@ Finer-grained composables: `useMessages({ conversationId? })`,
|
|
|
861
895
|
`useConversation({ autoLoad? })`. Each accepts `{ client }` to override the
|
|
862
896
|
injected client.
|
|
863
897
|
|
|
898
|
+
Let the user report an answer that did not work with `useFeedback()`:
|
|
899
|
+
|
|
900
|
+
```ts
|
|
901
|
+
const { ratings, submitFeedback, submitConversationFeedback } = useFeedback();
|
|
902
|
+
// reasons: inaccurate | incomplete | information_not_found | technical_issue
|
|
903
|
+
await submitFeedback(m.id, "negative", { reasons: ["inaccurate"], comment: "..." });
|
|
904
|
+
await submitConversationFeedback(conversationId, "negative");
|
|
905
|
+
```
|
|
906
|
+
|
|
864
907
|
## 3. Let the agent call your code: `useAgoFunction`
|
|
865
908
|
|
|
866
909
|
Registers on mount, unregisters on unmount.
|
|
@@ -912,7 +955,13 @@ explain the page to a colleague.
|
|
|
912
955
|
The mirror of navigation. Let the agent change the current page's state
|
|
913
956
|
(filters, sort, view mode…) and read it back. Each control becomes one optional
|
|
914
957
|
property of a single synthesized `setPageState` function, and every control's
|
|
915
|
-
`get()` value is sent as context.
|
|
958
|
+
`get()` value is sent as context. The SDK validates type and enum before calling
|
|
959
|
+
`set()`, skips values equal to the current `get()`, and drops placeholders
|
|
960
|
+
(`null`, `undefined`, `""`). The result reports each field in `applied`,
|
|
961
|
+
`unchanged`, or `rejected`. Mark a string control `clearable: true` to let `""`
|
|
962
|
+
clear it instead of being dropped. A `set()` that returns
|
|
963
|
+
`{ result: "rejected", reason }` or `{ result: "unchanged" }` feeds the same
|
|
964
|
+
envelope.
|
|
916
965
|
|
|
917
966
|
```vue
|
|
918
967
|
<script setup lang="ts">
|
|
@@ -990,8 +1039,9 @@ const state = useAgoStore(cart);
|
|
|
990
1039
|
## Exports cheat-sheet (`@useago/sdk/vue`)
|
|
991
1040
|
|
|
992
1041
|
- Setup: `AgoPlugin`, `useAgo`, `AGO_CLIENT_KEY`
|
|
993
|
-
- Composables: `useChat`, `useMessages`, `useConversation`, `
|
|
994
|
-
`useAgoNavigation`, `useAgoPageState`, `useAgoEvents`,
|
|
1042
|
+
- Composables: `useChat`, `useMessages`, `useConversation`, `useFeedback`,
|
|
1043
|
+
`useAgoFunction`, `useAgoNavigation`, `useAgoPageState`, `useAgoEvents`,
|
|
1044
|
+
`useAgoStore`
|
|
995
1045
|
- Types: `AgoConfig`, `AgoMessage`, `Conversation`, `AgoAgent`, `AgoSource`,
|
|
996
1046
|
`ToolCallData`, `AgoPluginOptions`
|
|
997
1047
|
|
|
@@ -1155,7 +1205,8 @@ ago.registerPageStateFunction(controls, opts?) / ago.unregisterPageStateFunction
|
|
|
1155
1205
|
ago.submitToolCallForm(toolCallId, formData)
|
|
1156
1206
|
ago.confirmToolCall(toolCallId)
|
|
1157
1207
|
ago.rejectToolCall(toolCallId)
|
|
1158
|
-
ago.submitFeedback(messageId, "positive" | "negative")
|
|
1208
|
+
ago.submitFeedback(messageId, "positive" | "negative", { reasons?, comment? })
|
|
1209
|
+
ago.submitConversationFeedback(conversationId, rating, details?) // → the reported message id
|
|
1159
1210
|
|
|
1160
1211
|
ago.on(event, handler) / ago.off(event, handler)
|
|
1161
1212
|
ago.updateConfig(partialConfig)
|
|
@@ -1215,7 +1266,11 @@ private stopAutoContinue = this.ago.enableAutoContinueAfterNavigation();
|
|
|
1215
1266
|
```
|
|
1216
1267
|
|
|
1217
1268
|
Register page state, the mirror of navigation (change the current page's state
|
|
1218
|
-
and read it back)
|
|
1269
|
+
and read it back). The SDK validates type and enum before calling `set()`, skips
|
|
1270
|
+
values equal to the current `get()`, drops placeholders, and reports each field
|
|
1271
|
+
in `applied`, `unchanged`, or `rejected`. See
|
|
1272
|
+
[Page state shortcut](../general/functions-and-context.md#page-state-shortcut)
|
|
1273
|
+
for the full contract:
|
|
1219
1274
|
|
|
1220
1275
|
```ts
|
|
1221
1276
|
this.ago.registerPageStateFunction([
|
|
@@ -1267,7 +1322,8 @@ reference, and the framework guides show the idiomatic wrappers.
|
|
|
1267
1322
|
|
|
1268
1323
|
- [Client-side functions](#client-side-functions): the agent runs code in the browser
|
|
1269
1324
|
- [Pre-built helpers](#pre-built-helpers): a catalogue of ready-made functions
|
|
1270
|
-
- [Client context](#client-context): tell the agent what the user is doing
|
|
1325
|
+
- [Client context](#client-context): tell the agent what the user is doing,
|
|
1326
|
+
including the [JavaScript errors](#javascript-errors) the page hit
|
|
1271
1327
|
|
|
1272
1328
|
---
|
|
1273
1329
|
|
|
@@ -1458,17 +1514,77 @@ This does two things:
|
|
|
1458
1514
|
current `get()` value, so the agent knows what to change. Controls without a
|
|
1459
1515
|
`get()` are write-only and don't appear in context.
|
|
1460
1516
|
|
|
1461
|
-
By default
|
|
1462
|
-
returns
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1517
|
+
By default, the handler validates each field, applies it via `set()`, and
|
|
1518
|
+
returns the result as an envelope:
|
|
1519
|
+
|
|
1520
|
+
```jsonc
|
|
1521
|
+
{
|
|
1522
|
+
"success": false,
|
|
1523
|
+
"applied": ["temperature"], // field names that were changed
|
|
1524
|
+
"unchanged": ["status"], // fields already holding that value
|
|
1525
|
+
"rejected": { "model": "\"gpt-5\" is not an allowed value. Allowed values: ..." }
|
|
1526
|
+
}
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
`applied` and `unchanged` are always present (as arrays of field names).
|
|
1530
|
+
`rejected` is omitted when empty. `success` is `false` when any field is
|
|
1531
|
+
rejected, `true` otherwise. Placeholders (`null`, `undefined`, and `""` on
|
|
1532
|
+
non-clearable controls) are silently dropped and appear in no bucket.
|
|
1533
|
+
|
|
1534
|
+
Before calling `set()`, the SDK validates the declared type and enum. Unknown
|
|
1535
|
+
control names are rejected with the list of available controls. A value that
|
|
1536
|
+
matches the current `get()` value is skipped without calling `set()` and
|
|
1537
|
+
reported as `unchanged`.
|
|
1538
|
+
|
|
1539
|
+
React/Vue offer `useAgoPageState(controls, opts?)` with lifecycle cleanup;
|
|
1540
|
+
Angular exposes `agoService.registerPageStateFunction(...)`.
|
|
1541
|
+
|
|
1542
|
+
#### Clearing a string field
|
|
1543
|
+
|
|
1544
|
+
By default, `""` is treated as filler and dropped. To let the agent clear a
|
|
1545
|
+
string field, mark it `clearable`:
|
|
1546
|
+
|
|
1547
|
+
```ts
|
|
1548
|
+
{
|
|
1549
|
+
name: "searchQuery",
|
|
1550
|
+
description: "Free-text search filter",
|
|
1551
|
+
schema: { type: "string" },
|
|
1552
|
+
clearable: true,
|
|
1553
|
+
get: () => query,
|
|
1554
|
+
set: (v) => setQuery(v as string),
|
|
1555
|
+
}
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
The SDK adds `Pass "" to clear it.` to the control's description for the agent,
|
|
1559
|
+
and on enum controls adds `""` to the allowed values. `clearable` only affects
|
|
1560
|
+
string controls; on other types it has no effect.
|
|
1561
|
+
|
|
1562
|
+
#### Tagged setter outcomes
|
|
1563
|
+
|
|
1564
|
+
A `set()` function can return a tagged result to reject a value or signal that
|
|
1565
|
+
the field is already in the requested state:
|
|
1566
|
+
|
|
1567
|
+
```ts
|
|
1568
|
+
set: (v) => {
|
|
1569
|
+
const match = availableModels.find((m) => m.name === v);
|
|
1570
|
+
if (!match) return { result: "rejected", reason: `No model named "${v}".` };
|
|
1571
|
+
if (match.id === currentModelId) return { result: "unchanged" };
|
|
1572
|
+
setModelId(match.id);
|
|
1573
|
+
},
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
`{ result: "rejected", reason }` puts the field in `rejected` with the given
|
|
1577
|
+
reason. `{ result: "unchanged" }` puts it in `unchanged` without calling the
|
|
1578
|
+
React state setter. Returning nothing (void) means the value was applied. A
|
|
1579
|
+
thrown or rejected promise remains a function execution error; it is not
|
|
1580
|
+
converted into a per-field rejection.
|
|
1466
1581
|
|
|
1467
1582
|
### Return what the page displays
|
|
1468
1583
|
|
|
1469
|
-
|
|
1470
|
-
it to "find the user dupont" and it applies the filter
|
|
1471
|
-
found: the rows only reach it on the next message, as
|
|
1584
|
+
The envelope on its own tells the agent what happened to its arguments, not what
|
|
1585
|
+
appeared on screen. Ask it to "find the user dupont" and it applies the filter
|
|
1586
|
+
but cannot say who it found: the rows only reach it on the next message, as
|
|
1587
|
+
context.
|
|
1472
1588
|
|
|
1473
1589
|
Add a `data` source and the rows come back as the result of the agent's own
|
|
1474
1590
|
call, in the same turn:
|
|
@@ -1488,7 +1604,7 @@ useAgoPageState(controls, {
|
|
|
1488
1604
|
});
|
|
1489
1605
|
```
|
|
1490
1606
|
|
|
1491
|
-
`setPageState` now returns `{ success, applied, data }`, and a second function
|
|
1607
|
+
`setPageState` now returns `{ success, applied, unchanged, rejected?, data }`, and a second function
|
|
1492
1608
|
`readPageData` is registered: no parameters, changes nothing, returns `{ data }`.
|
|
1493
1609
|
That one answers "what's on screen?" without touching the page. Both disappear
|
|
1494
1610
|
when you unregister.
|
|
@@ -1538,9 +1654,9 @@ Two things to keep in mind:
|
|
|
1538
1654
|
the SDK keeps the first whole items that fit and adds a `truncation` field
|
|
1539
1655
|
(`{ truncated, returnedItems, totalItems, hint }`) next to them. The counts
|
|
1540
1656
|
are nested rather than merged into your snapshot so they can never overwrite
|
|
1541
|
-
a field of your own called `totalItems`.
|
|
1542
|
-
survive intact. Return the columns
|
|
1543
|
-
objects.
|
|
1657
|
+
a field of your own called `totalItems`. The verdict fields (`success`,
|
|
1658
|
+
`applied`, `unchanged`, `rejected`) always survive intact. Return the columns
|
|
1659
|
+
the agent needs, not your whole row objects.
|
|
1544
1660
|
- **Pause mode only.** The result travels back inside the turn only when
|
|
1545
1661
|
`clientFunctionsMode` is `"pause"` (the default). In `"placeholder"` mode the
|
|
1546
1662
|
turn is already over by then, so the agent will not see the data during the
|
|
@@ -1806,11 +1922,94 @@ event can't bloat the context. Read or clear the log with
|
|
|
1806
1922
|
`client.getRecentActivity()` / `client.clearActivity()`, observe it via
|
|
1807
1923
|
`client.on("activity:recorded", …)`, or use the React `useAgoActivityLog()` hook.
|
|
1808
1924
|
|
|
1925
|
+
### JavaScript errors
|
|
1926
|
+
|
|
1927
|
+
"The button does nothing" is much easier to answer when the agent can see the
|
|
1928
|
+
exception behind it. Turn the error watcher on and the page's recent JavaScript
|
|
1929
|
+
errors ride along with every message:
|
|
1930
|
+
|
|
1931
|
+
```ts
|
|
1932
|
+
const client = new AgoClient({
|
|
1933
|
+
baseUrl: "https://playground.api.useago.com",
|
|
1934
|
+
agent: "generic-guide",
|
|
1935
|
+
errorWatcher: true,
|
|
1936
|
+
});
|
|
1937
|
+
```
|
|
1938
|
+
|
|
1939
|
+
(`<AgoProvider errorWatcher>`, the Vue plugin and `provideAgo` take the same
|
|
1940
|
+
option, and so does `mountChatWidget({ config: { errorWatcher: true } })`.)
|
|
1941
|
+
|
|
1942
|
+
With it on, the SDK captures:
|
|
1943
|
+
|
|
1944
|
+
- uncaught exceptions (the `window` `error` event), with file, line and stack;
|
|
1945
|
+
- unhandled promise rejections;
|
|
1946
|
+
- every `console.error` call (see the note below);
|
|
1947
|
+
- errors your app caught itself and hands over with `client.reportError()`.
|
|
1948
|
+
|
|
1949
|
+
They go out as the `errors:recent` context entry: at most 10 distinct errors,
|
|
1950
|
+
deduplicated with a `count`, each with how long ago it last fired, pruned after
|
|
1951
|
+
10 minutes. Messages are cut at 500 characters and stacks at 2000, so one crash
|
|
1952
|
+
cannot flood the context. The entry is absent when nothing happened.
|
|
1953
|
+
|
|
1954
|
+
Report the errors you already catch, so the agent sees them too:
|
|
1955
|
+
|
|
1956
|
+
```tsx
|
|
1957
|
+
// React error boundary
|
|
1958
|
+
componentDidCatch(error, info) {
|
|
1959
|
+
this.props.client.reportError(error, { componentStack: info.componentStack });
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// Vue
|
|
1963
|
+
app.config.errorHandler = (err, _instance, info) => client.reportError(err, { info });
|
|
1964
|
+
|
|
1965
|
+
// Angular
|
|
1966
|
+
@Injectable() class AgoErrorHandler implements ErrorHandler {
|
|
1967
|
+
constructor(private ago: AgoService) {}
|
|
1968
|
+
handleError(error: unknown) { this.ago.getClient().reportError(error); }
|
|
1969
|
+
}
|
|
1970
|
+
```
|
|
1971
|
+
|
|
1972
|
+
Tune the caps or redact what leaves the page with options instead of `true`:
|
|
1973
|
+
|
|
1974
|
+
```ts
|
|
1975
|
+
new AgoClient({
|
|
1976
|
+
baseUrl: "https://playground.api.useago.com",
|
|
1977
|
+
errorWatcher: {
|
|
1978
|
+
maxErrors: 5, // distinct errors kept (default 10)
|
|
1979
|
+
maxAgeMs: 120_000, // forget errors older than 2 min (default 10 min)
|
|
1980
|
+
maxStackChars: 1000, // default 2000
|
|
1981
|
+
// Return null to drop an error, or an edited copy to redact it.
|
|
1982
|
+
filter: (e) => (e.message.includes("token") ? null : e),
|
|
1983
|
+
},
|
|
1984
|
+
});
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
Things to know:
|
|
1988
|
+
|
|
1989
|
+
- **It is off by default.** Error messages can contain emails, ids or internal
|
|
1990
|
+
URLs. Enabling it is your call; use `filter` if some of them must stay home.
|
|
1991
|
+
- **`console.error` is wrapped.** The SDK never touches globals otherwise, but
|
|
1992
|
+
most caught errors (React render errors included) only surface there. The
|
|
1993
|
+
wrapper calls the original every time and is restored on `destroy()`.
|
|
1994
|
+
- **Cross-origin scripts** without a `crossorigin` attribute only yield
|
|
1995
|
+
`Script error.` with no details. That is the browser, not the SDK.
|
|
1996
|
+
- Errors also count as friction for the proactive mode: a trigger can use
|
|
1997
|
+
`when: { jsErrors: 1 }` to nudge right after a crash.
|
|
1998
|
+
|
|
1999
|
+
Observe or inspect it with `client.on("error:captured", …)`,
|
|
2000
|
+
`client.getRecentErrors()`, `client.clearErrors()`, or toggle it at runtime
|
|
2001
|
+
with `client.updateConfig({ errorWatcher: false })`.
|
|
2002
|
+
|
|
2003
|
+
To see it work, `examples/simple-html/error-watcher.html` is an order page
|
|
2004
|
+
with three planted bugs (a `TypeError`, an unhandled rejection, a swallowed
|
|
2005
|
+
error logged with `console.error`) and the bubble widget wired to the same
|
|
2006
|
+
client. Build the SDK, serve the repo, click a button, ask the agent what broke.
|
|
2007
|
+
|
|
1809
2008
|
### Per framework
|
|
1810
2009
|
|
|
1811
2010
|
| Framework | Idiomatic API |
|
|
1812
2011
|
| --- | --- |
|
|
1813
|
-
| Core | `setContext` / `addDynamicContext` / `enableAutoPageContext` |
|
|
2012
|
+
| Core | `setContext` / `addDynamicContext` / `enableAutoPageContext` / `errorWatcher` |
|
|
1814
2013
|
| React | `useAgoContext(entryOrFn, key?)` + `pageContext="auto"` |
|
|
1815
2014
|
| Vue | core API inside `onMounted` / `onUnmounted` |
|
|
1816
2015
|
| Angular | `agoService.getClient().setContext(…)` |
|
|
@@ -1872,6 +2071,7 @@ const final = await client.waitFor("message:complete", { timeout: 10_000 });
|
|
|
1872
2071
|
| `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) |
|
|
1873
2072
|
| `function:result` | `{ invocationId, result, error? }` |
|
|
1874
2073
|
| `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) |
|
|
2074
|
+
| `error:captured` | `CapturedError`: the error watcher captured a JavaScript error (uncaught exception, unhandled rejection, `console.error`, or `reportError`). Fires on repeats too, with the updated `count`. Needs `errorWatcher` in the config. See [JavaScript errors](functions-and-context.md#javascript-errors) |
|
|
1875
2075
|
| `form:submitted` | `{ name, values, result }`: a form collector submitted successfully; `result` is the submit response (the third-party API's answer) |
|
|
1876
2076
|
| `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 |
|
|
1877
2077
|
| `connection:status` | `{ connected }` |
|
|
@@ -1991,6 +2191,14 @@ client.on("toolCall:received", async (toolCall) => {
|
|
|
1991
2191
|
`ToolCallType` is one of: `form`, `confirmation_input`, `status_message`,
|
|
1992
2192
|
`progress_indicator`, `client_function`, `reasoning`, `mcp_ui_resource`.
|
|
1993
2193
|
|
|
2194
|
+
A `form` tool call from the agent's `ago_ticketing` tool is the ticket form:
|
|
2195
|
+
`toolName` is `"ago_ticketing"`, `formSchema` is empty, and the fields come from
|
|
2196
|
+
`getConfig().permissions[0].ticketForm` instead. The call carries the agent's
|
|
2197
|
+
pre-filled `ticket`, `allowedToCreateTicket`, and for embed-mode forms
|
|
2198
|
+
`embedHtml`. The vanilla widget renders and submits it for you (see
|
|
2199
|
+
[Ticket form](widget.md#ticket-form-the-form-tool-call)); a custom UI creates
|
|
2200
|
+
the ticket with `createTicket()` and then calls `submitToolCallForm(id, { success: true, ticket_id, ticket_url })`.
|
|
2201
|
+
|
|
1994
2202
|
---
|
|
1995
2203
|
|
|
1996
2204
|
## Framework equivalents
|
|
@@ -2029,10 +2237,12 @@ interface AgoConfig {
|
|
|
2029
2237
|
permission?: string; // X-Widget-Permission header
|
|
2030
2238
|
userEmail?: string; // X-User-Email header, identify the end user
|
|
2031
2239
|
userJwt?: string; // Authorization: Bearer <jwt>, authenticated users
|
|
2240
|
+
metadata?: Record<string, unknown> | null; // Message body, for user/ticket enrichment
|
|
2032
2241
|
debug?: boolean; // enable verbose console logging
|
|
2033
2242
|
warnOnEmptyReply?: boolean; // default true; see "Empty replies" below
|
|
2034
2243
|
clientFunctionsMode?: "placeholder" | "pause"; // default "pause"
|
|
2035
2244
|
maxFunctionResultBytes?: number; // default 50000; cap on client function results
|
|
2245
|
+
errorWatcher?: boolean | ErrorWatcherOptions; // default off; send recent JS errors as context
|
|
2036
2246
|
}
|
|
2037
2247
|
```
|
|
2038
2248
|
|
|
@@ -2044,10 +2254,12 @@ interface AgoConfig {
|
|
|
2044
2254
|
| `permission` | — | `X-Widget-Permission` | Mirrors the widget's `window.AGO.permission`. |
|
|
2045
2255
|
| `userEmail` | — | `X-User-Email` | Identifies the user to AGO. |
|
|
2046
2256
|
| `userJwt` | — | `Authorization: Bearer …` | For authenticated sessions. |
|
|
2257
|
+
| `metadata` | — | `metadata` in the body | JSON-serializable user/ticket context. Per-message overrides replace defaults; `null` omits the field. |
|
|
2047
2258
|
| `debug` | — | — | Turns on the SDK's logger. |
|
|
2048
2259
|
| `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. |
|
|
2049
2260
|
| `clientFunctionsMode` | — | `client_functions_mode` in the body | Default `"pause"`: the agent stops on client function call(s) and resumes the SAME turn once the results are submitted (the SDK submits and resumes automatically; see the [`message:waiting-client` event](events-and-streaming.md#events)). Needs a backend with pause/resume support; older backends ignore the flag and fall back to placeholder behavior. Legacy `"placeholder"`: the turn continues on a placeholder and results are only visible on later turns. Per-message override via `sendMessage(..., { clientFunctionsMode })`. |
|
|
2050
2261
|
| `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). |
|
|
2262
|
+
| `errorWatcher` | — | `errors:recent` in `client_context` | Off by default. `true` (or options) captures the page's JavaScript errors (uncaught exceptions, unhandled rejections, `console.error`, `reportError`) and sends the recent ones with every message. See [JavaScript errors](functions-and-context.md#javascript-errors). |
|
|
2051
2263
|
|
|
2052
2264
|
---
|
|
2053
2265
|
|
|
@@ -2109,7 +2321,7 @@ provideAgo({ baseUrl: "https://acme.useago.com", agent: "support" });
|
|
|
2109
2321
|
order:
|
|
2110
2322
|
|
|
2111
2323
|
1. **`window.AGO`** (the widget config object): `basepath`, `widgetId`,
|
|
2112
|
-
`agent`/`defaultAgent`, `permission`, `email`, `jwt`.
|
|
2324
|
+
`agent`/`defaultAgent`, `permission`, `email`, `jwt`, `metadata`.
|
|
2113
2325
|
2. **`<meta>` tags**: `ago-base-url`, `ago-widget-id`, `ago-agent`
|
|
2114
2326
|
(or `ago-agent-id`), `ago-permission`, `ago-user-email`.
|
|
2115
2327
|
3. **`data-ago-*` attributes** on `<body>` or a `<script>` tag:
|
|
@@ -2143,13 +2355,32 @@ client.updateConfig({ userJwt: token, userEmail: "jane@acme.com" });
|
|
|
2143
2355
|
|
|
2144
2356
|
Changing `permission` to a falsy value removes the header. In React, the
|
|
2145
2357
|
`useAgo` hook re-applies `agent`, `permission`, `userEmail`, `userJwt`,
|
|
2146
|
-
`debug` and `warnOnEmptyReply` automatically when those props
|
|
2147
|
-
`baseUrl`/`widgetId` recreates the client).
|
|
2358
|
+
`metadata`, `debug` and `warnOnEmptyReply` automatically when those props
|
|
2359
|
+
change (changing `baseUrl`/`widgetId` recreates the client).
|
|
2148
2360
|
|
|
2149
2361
|
`updateConfig` validates the merged result: omitting a key (or passing it as
|
|
2150
2362
|
`undefined`) keeps the current value; an explicit empty or non-string `baseUrl`
|
|
2151
2363
|
throws `config_missing_base_url` and leaves the client unchanged.
|
|
2152
2364
|
|
|
2365
|
+
### User and ticket metadata
|
|
2366
|
+
|
|
2367
|
+
Pass JSON-serializable data in `config.metadata` to include it in every new
|
|
2368
|
+
message request (JSON and multipart uploads). `sendMessage(text, { metadata })`
|
|
2369
|
+
replaces those defaults for that message; it does not merge objects. `null`
|
|
2370
|
+
omits metadata. `updateConfig({ metadata: null })` stops sending defaults; it
|
|
2371
|
+
does not erase data already stored by the server.
|
|
2372
|
+
|
|
2373
|
+
```ts
|
|
2374
|
+
client.updateConfig({ metadata: { brevo: { clientid: 11970100, planCategory: "Free" } } });
|
|
2375
|
+
await client.sendMessage("I need help");
|
|
2376
|
+
```
|
|
2377
|
+
|
|
2378
|
+
The AGO API stores this data as widget metadata on the user. Configure metadata
|
|
2379
|
+
field mappings in AGO to include selected fields in tickets. This does not
|
|
2380
|
+
populate a separately hosted Zendesk form. Use `setContext` as well when the
|
|
2381
|
+
agent needs these values in its prompt. Metadata supplied by the browser is
|
|
2382
|
+
untrusted context, not an authentication credential or permission grant.
|
|
2383
|
+
|
|
2153
2384
|
---
|
|
2154
2385
|
|
|
2155
2386
|
## Errors
|
|
@@ -2196,6 +2427,8 @@ may be reworded in any release.
|
|
|
2196
2427
|
| `stream_error` | `AgoStreamError` | the stream failed mid-flight | Usually a dropped connection; retry the message |
|
|
2197
2428
|
| `function_invalid_registration` | `AgoError` | `registerFunction` called without a handler or schema | Use `registerFunction({ name, parameters, handler })` |
|
|
2198
2429
|
| `function_error` | `AgoFunctionError` | a registered client function threw | Inspect `functionName` and `originalError` |
|
|
2430
|
+
| `feedback_no_message` | `AgoError` | `submitConversationFeedback` on a conversation with no answer to attach the report to | Send a message first, or pass `details.lastMessageId` |
|
|
2431
|
+
| `missing_client` | `AgoError` | a React hook was used without a client | Pass `options.client` or wrap the app in `<AgoProvider>` |
|
|
2199
2432
|
|
|
2200
2433
|
Server-supplied errors (`AgoApiError` with a structured body) keep the
|
|
2201
2434
|
backend's own `code` and, when provided, link their docs in the message
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
var ie=10,$=1e3;function ne(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function E(e,t){if(typeof e=="string"){if(e.length<=1e3)return e;const s=e.length-$;return e.slice(0,$)+`…[truncated ${s} chars]`}if(Array.isArray(e)){if(t.has(e))return"…[circular]";t.add(e);const s=e.slice(0,50).map(i=>E(i,t));return e.length>50&&s.push(`…[+${e.length-50} more]`),t.delete(e),s}if(ne(e)){if(t.has(e))return"…[circular]";t.add(e);const s={};for(const[i,n]of Object.entries(e))s[i]=E(n,t);return t.delete(e),s}return e}function re(e){return E(e,new WeakSet)}var K=class{constructor(e=10){this.entries=[];const t=Math.floor(e);this.maxEntries=Number.isFinite(t)?Math.max(1,t):10}add(e){const t={ts:e.ts??Date.now(),actor:e.actor??"user",name:e.name,summary:e.summary,...e.data!==void 0?{data:re(e.data)}:{}};this.entries.push(t);const s=this.entries.length-this.maxEntries;return s>0&&this.entries.splice(0,s),t}getRecent(){return[...this.entries]}get size(){return this.entries.length}clear(){this.entries=[]}},g=class extends Error{constructor(e,t,s){super(e),this.code=t,this.statusCode=s,this.name="AgoError"}},y=class H extends g{constructor(t,s,i,n,r,c){super(t,s,i),this.type=n,this.param=r,this.docUrl=c,this.name="AgoApiError"}static fromResponse(t,s){const i=t.error;return new H(i.doc_url?`${i.message} See ${i.doc_url}`:i.message,i.code,s,i.type,i.param,i.doc_url)}},w=class extends g{constructor(e,t){super(e,"network_error"),this.originalError=t,this.name="AgoNetworkError"}},_=class extends g{constructor(e,t="stream_error"){super(e,t),this.name="AgoStreamError"}},I=class extends g{constructor(e,t,s){super(e,"function_error"),this.functionName=t,this.originalError=s,this.name="AgoFunctionError"}},oe=class{constructor(e=!1){this.enabled=e}enable(){this.enabled=!0}disable(){this.enabled=!1}log(...e){this.enabled&&console.log("[AGO SDK]",...e)}warn(...e){this.enabled&&console.warn("[AGO SDK]",...e)}error(...e){console.error("[AGO SDK]",...e)}debug(...e){this.enabled&&console.debug("[AGO SDK]",...e)}},l=new oe,N="ago_widget_id";function R(e){return typeof DOMException<"u"&&e instanceof DOMException&&e.name==="AbortError"||e instanceof Error&&e.name==="AbortError"}function ae(){if(typeof localStorage<"u"){const t=localStorage.getItem(N);if(t)return t}const e=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(N,e)}catch{}return e}var ce=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.headers={"X-User-Anon-Id":e.widgetId||ae()},e.userEmail&&(this.headers["X-User-Email"]=e.userEmail),e.userJwt&&(this.headers.Authorization=`Bearer ${e.userJwt}`),e.permission&&(this.headers["X-Widget-Permission"]=e.permission)}updateConfig(e){e.baseUrl&&(this.baseUrl=e.baseUrl.replace(/\/$/,"")),e.widgetId&&(this.headers["X-User-Anon-Id"]=e.widgetId),e.userEmail&&(this.headers["X-User-Email"]=e.userEmail),e.userJwt&&(this.headers.Authorization=`Bearer ${e.userJwt}`),e.permission!==void 0&&(e.permission?this.headers["X-Widget-Permission"]=e.permission:delete this.headers["X-Widget-Permission"])}async get(e){return this.request("GET",e)}async post(e,t,s){return this.request("POST",e,t,s)}async postStream(e,t,s){const i=`${this.baseUrl}${e}`;l.debug("POST (stream)",i,t);try{const n=await fetch(i,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0,signal:s?.signal});return n.ok||await this.handleErrorResponse(n),n}catch(n){throw n instanceof y||R(n)?n:new w(`Network error: ${n instanceof Error?n.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,n instanceof Error?n:void 0)}}async postFormData(e,t,s){const i=`${this.baseUrl}${e}`;l.debug("POST (formData)",i);const n={...this.headers};try{const r=await fetch(i,{method:"POST",headers:n,body:t,signal:s?.signal});return r.ok||await this.handleErrorResponse(r),r}catch(r){throw r instanceof y||R(r)?r:new w(`Network error: ${r instanceof Error?r.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,r instanceof Error?r:void 0)}}async request(e,t,s,i){const n=`${this.baseUrl}${t}`;l.debug(e,n,s);try{const r=await fetch(n,{method:e,headers:{...this.headers,"Content-Type":"application/json"},body:s?JSON.stringify(s):void 0,...i?.keepalive?{keepalive:!0}:{}});return r.ok||await this.handleErrorResponse(r),r.status===204?void 0:r.json()}catch(r){throw r instanceof y?r:new w(`Network error: ${r instanceof Error?r.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,r instanceof Error?r:void 0)}}async handleErrorResponse(e){let t;try{t=await e.json()}catch{}if(t?.error)throw y.fromResponse(t,e.status);let s="";throw e.status===401||e.status===403?s=" Check `userJwt` (and that it has not expired).":e.status===404&&(s=" Check that `baseUrl` points at your AGO API root."),new y(`HTTP ${e.status}: ${e.statusText}.${s}`,"http_error",e.status,"api_error")}},le=5e4,ue=2e4,he=2e3,z=class{constructor(e){this.functions=new Map,this.defaultMaxResultBytes=e?.maxResultBytes??le}setDefaultMaxResultBytes(e){this.defaultMaxResultBytes=e}getDefaultMaxResultBytes(){return this.defaultMaxResultBytes}register(e,t,s){if(typeof e=="object"){const{name:o,handler:a,description:h,parameters:u,maxResultBytes:d,requiresApproval:f}=e;return this.register(o,a,{description:h,parameters:u,maxResultBytes:d,requiresApproval:f})}const i=e;if(!t||!s)throw new g(`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)&&l.warn(`Function "${i}" is being overwritten`);const{maxResultBytes:n,requiresApproval:r,...c}=s;this.functions.set(i,{schema:{...c,name:i},handler:t,maxResultBytes:n,requiresApproval:r}),l.log(`Registered function: ${i}`)}unregister(e){const t=this.functions.delete(e);return t&&l.log(`Unregistered function: ${e}`),t}get(e){return this.functions.get(e)}has(e){return this.functions.has(e)}getSchemas(){return Array.from(this.functions.values()).map(e=>e.schema)}async execute(e,t){const s=this.functions.get(e);if(!s)throw new I(`Function "${e}" is not registered`,e);l.log(`Executing function: ${e}`,t);try{const i=await s.handler(t);return l.log(`Function ${e} completed:`,i),this.guardResultSize(e,i,s.maxResultBytes)}catch(i){throw l.error(`Function ${e} failed:`,i),new I(`Function "${e}" execution failed: ${i instanceof Error?i.message:"Unknown error"}`,e,i instanceof Error?i:void 0)}}guardResultSize(e,t,s){let i;try{i=JSON.stringify(t)}catch{return t}if(i===void 0)return t;const n=new TextEncoder().encode(i).length,r=s??this.defaultMaxResultBytes;return n>r?(l.warn(`Function "${e}" result is ${n} bytes, over the ${r}-byte limit; a truncated preview was sent to the agent instead. Return fewer fields, paginate, or raise maxResultBytes for this function.`),{truncated:!0,originalBytes:n,maxBytes:r,preview:i.slice(0,he),hint:`The result of "${e}" exceeded the ${r}-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.`}):(n>ue&&l.warn(`Function "${e}" returned a large result (${Math.round(n/1024)} KB). Large results consume LLM context: return fewer fields, paginate, or push the data to the UI and return a summary.`),t)}clear(){this.functions.clear(),l.log("Cleared all registered functions")}get size(){return this.functions.size}},de=50,ge=2;var fe=2e3,pe=new TextEncoder;function m(e){return pe.encode(e).length}function v(e){try{return JSON.stringify(e)}catch{return}}var k=Symbol("ago.pageData.timeout");function me(e,t){return t<=0?Promise.resolve(k):new Promise((s,i)=>{const n=setTimeout(()=>s(k),t);e.then(r=>{clearTimeout(n),s(r)},r=>{clearTimeout(n),i(r)})})}async function ye(e,t){const s=Date.now()+t;let i=0;for(;Date.now()<s&&i<ge;)i=e().isLoading?.()?0:i+1,await new Promise(o=>setTimeout(o,de));const n=e().get();if(!M(n))return n;const r=await me(Promise.resolve(n),s-Date.now());if(r!==k)return r;l.warn("Page data did not resolve within the settle timeout; returning what the page has now. Raise settleTimeoutMs if the work legitimately takes longer.");const c=e().get();if(!M(c))return c;Promise.resolve(c).catch(()=>{})}function M(e){return typeof e=="object"&&e!==null&&typeof e.then=="function"}function ve(e){if(typeof e!="object"||e===null||Array.isArray(e))return;const t=e;let s;for(const i of Object.keys(t)){const n=t[i];Array.isArray(n)&&(s===void 0||n.length>t[s].length)&&(s=i)}return s}function we(e,t){let s=2,i=0;for(const n of e){const r=m(v(n)??"null")+(i>0?1:0);if(s+r>t)break;s+=r,i++}return i}function Ce(e,t){if(t<=0)return"";const s=r=>m(JSON.stringify(r))-2;let i=e.slice(0,t),n=s(i);for(;n>t&&i.length>0;)i=i.slice(0,Math.max(0,Math.floor(i.length*t/n)-1)),n=s(i);return i}function T(e){return`The page data returned by "${e}" was too large to send in full. Only the first items are included: ask for fewer rows or narrow the filters to see the rest.`}function Se(e,t,s,i){const n={truncated:!0,originalBytes:s,maxBytes:i,preview:"",hint:T(e)},r=m(v(n)??"{}");return{...n,preview:Ce(t,Math.min(fe,i-r))}}function be(e,t,s,i,n){const r=s===void 0?{}:{...t},c=s??"items",o=m(v({...r,[c]:[],truncation:{truncated:!0,returnedItems:i.length,totalItems:i.length,hint:T(e)}})??"{}")-2;if(o>=n)return;const a=i.slice(0,we(i,n-o));return{...r,[c]:a,truncation:{truncated:!0,returnedItems:a.length,totalItems:i.length,hint:T(e)}}}function U(e,t,s){const i=v(t);if(i===void 0)return t;const n=m(i);if(n<=s)return t;l.warn(`Page data for "${e}" is ${n} bytes, over the ${s}-byte limit; it was truncated to the first items that fit. Return fewer fields per item, paginate, or raise maxResultBytes on the data source.`);const r=ve(t),c=Array.isArray(t)?t:r!==void 0?t[r]:void 0;if(c){const o=be(e,t,r,c,s),a=m(v(o)??"");if(o!==void 0&&a<=s)return o}return Se(e,i,n,s)}var Ae=250,Ee=3,_e=1e3,Ie=24,Re=10,ke=50,Te=["mousemove","mousedown","keydown","scroll","touchstart"],J=class{constructor(e={}){this.started=!1,this.teardowns=[],this.route="",this.routeEnteredAt=0,this.lastActivityAt=0,this.lastHeavyActivityAt=0,this.recentRoutes=[],this.routeBounces=0,this.rageClicks=0,this.fieldErrors={},this.recentClicks=[],this.signals=[],this.listeners=new Set,this.getPageState=e.getPageState,this.activityThrottleMs=e.activityThrottleMs??Ae,this.rageClickCount=e.rageClickCount??Ee,this.rageClickWindowMs=e.rageClickWindowMs??_e,this.rageClickRadiusPx=e.rageClickRadiusPx??Ie,this.maxRecentRoutes=e.maxRecentRoutes??Re,this.maxSignals=e.maxSignals??ke}start(){if(this.started||typeof window>"u"||typeof document>"u")return;this.started=!0;const e=Date.now();this.route=this.currentPathname(),this.routeEnteredAt=e,this.lastActivityAt=e,this.recentRoutes=[this.route];const t=(i,n,r)=>{i.addEventListener(n,r,{passive:!0}),this.teardowns.push(()=>i.removeEventListener(n,r))},s=()=>this.recordActivity();for(const i of Te)t(window,i,s);t(document,"visibilitychange",()=>{document.visibilityState==="visible"&&this.recordActivity()}),t(window,"popstate",()=>this.checkRoute()),t(window,"hashchange",()=>this.checkRoute()),t(window,"click",i=>this.onClick(i)),l.log("SignalCollector started")}destroy(){for(const e of this.teardowns)e();this.teardowns=[],this.listeners.clear(),this.recentClicks=[],this.started=!1,l.log("SignalCollector destroyed")}signalFieldError(e){this.checkRoute();const t=(this.fieldErrors[e]??0)+1;this.fieldErrors[e]=t,this.pushSignal({type:"field_error",route:this.route,at:Date.now(),count:t,meta:{field:e}})}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}getSignals(){return[...this.signals]}getSnapshot(e){this.checkRoute();const t=Date.now();return{route:this.route,dwellMs:this.started?t-this.routeEnteredAt:0,idleMs:this.started?t-this.lastActivityAt:0,rageClicks:this.rageClicks,routeBounces:this.routeBounces,fieldErrors:{...this.fieldErrors},recentRoutes:[...this.recentRoutes],pageState:e?.pageState===!1?null:this.readPageState()}}currentPathname(){return typeof window<"u"&&window.location?window.location.pathname:""}readPageState(){if(!this.getPageState)return null;try{return this.getPageState()??null}catch(e){return l.warn("SignalCollector getPageState threw:",e),null}}recordActivity(){const e=Date.now();this.lastActivityAt=e,e-this.lastHeavyActivityAt>=this.activityThrottleMs&&(this.lastHeavyActivityAt=e,this.checkRoute())}checkRoute(){if(!this.started)return;const e=this.currentPathname();if(e===this.route)return;const t=Date.now(),s=this.route;this.route=e,this.routeEnteredAt=t,this.rageClicks=0,this.fieldErrors={},this.recentClicks=[],this.recentRoutes.push(e),this.recentRoutes.length>this.maxRecentRoutes&&this.recentRoutes.splice(0,this.recentRoutes.length-this.maxRecentRoutes),this.pushSignal({type:"route_change",route:e,at:t,meta:{from:s}});const i=this.recentRoutes,n=i.length;n>=4&&i[n-1]===i[n-3]&&i[n-2]===i[n-4]&&i[n-1]!==i[n-2]&&(this.routeBounces+=1,this.pushSignal({type:"route_bounce",route:e,at:t,count:this.routeBounces,meta:{between:`${i[n-2]}↔${i[n-1]}`}}))}onClick(e){this.recordActivity();const t=Date.now(),s=this.pageStateKey(),i={at:t,x:e.clientX,y:e.clientY,stateKey:s};this.recentClicks=this.recentClicks.filter(o=>t-o.at<=this.rageClickWindowMs);const n=this.recentClicks[0],r=!n||Math.abs(i.x-n.x)<=this.rageClickRadiusPx&&Math.abs(i.y-n.y)<=this.rageClickRadiusPx,c=!n||n.stateKey===s;if(!r||!c){this.recentClicks=[i];return}this.recentClicks.push(i),this.recentClicks.length>=this.rageClickCount&&(this.rageClicks+=1,this.recentClicks=[],this.pushSignal({type:"rage_click",route:this.route,at:t,count:this.rageClicks}))}pageStateKey(){const e=this.readPageState();if(!e)return"";try{return JSON.stringify(e)}catch{return""}}pushSignal(e){this.signals.push(e),this.signals.length>this.maxSignals&&this.signals.splice(0,this.signals.length-this.maxSignals);for(const t of this.listeners)try{t(e)}catch(s){l.error("Signal listener threw:",s)}}};function X(e,t){const s=t?t.storage??(typeof localStorage<"u"?localStorage:void 0):void 0;let i=e;if(t&&s)try{const o=s.getItem(t.key);o!=null&&(i=JSON.parse(o))}catch{}const n=new Set,r=()=>n.forEach(o=>o(i));function c(){if(t&&s)try{s.setItem(t.key,JSON.stringify(i))}catch{}}return{get:()=>i,set:o=>{i=o,c(),r()},subscribe:o=>(n.add(o),()=>{n.delete(o)})}}var xe=6e4,Pe=1440*60*1e3,Fe="ago_proactive";function b(e){const t=new Date(e),s=String(t.getMonth()+1).padStart(2,"0"),i=String(t.getDate()).padStart(2,"0");return`${t.getFullYear()}-${s}-${i}`}var Oe=class{constructor(e={}){this.killSwitchEnabled=!1,this.engaged=!1,this.turnInFlight=!1,this.slotOccupied=!1,this.sessionCount=0,this.perSessionCap=e.frequencyCap?.perSession,this.perDayCap=e.frequencyCap?.perDay,this.store=X({optOut:!1,lastFiredAt:{},dismissedAt:{},day:{date:b(Date.now()),count:0}},{key:e.storageKey??Fe,storage:e.storage})}setKillSwitch(e){this.killSwitchEnabled=e}optOut(){this.store.set({...this.store.get(),optOut:!0})}optIn(){this.store.set({...this.store.get(),optOut:!1})}isOptedOut(){return this.store.get().optOut}setEngaged(e){this.engaged=e}setTurnInFlight(e){this.turnInFlight=e}isSlotOccupied(){return this.slotOccupied}occupySlot(){this.slotOccupied=!0}releaseSlot(){this.slotOccupied=!1}canFire(e,t=Date.now()){if(!this.killSwitchEnabled)return this.deny(e,"kill-switch (proactive disabled server-side)");const s=this.store.get();if(s.optOut)return this.deny(e,"user opted out");if(this.engaged)return this.deny(e,"user engaged (widget open)");if(this.turnInFlight)return this.deny(e,"message turn in flight");if(this.slotOccupied)return this.deny(e,"nudge slot occupied");const i=s.lastFiredAt[e.id],n=e.cooldownMs??xe;if(i!==void 0&&t-i<n)return this.deny(e,"cooldown");const r=s.dismissedAt[e.id],c=e.resurfaceAfterMs??Pe;return r!==void 0&&t-r<c?this.deny(e,"dismissed recently"):this.perSessionCap!==void 0&&this.sessionCount>=this.perSessionCap?this.deny(e,"perSession cap"):this.perDayCap!==void 0&&this.dayCount(t)>=this.perDayCap?this.deny(e,"perDay cap"):!0}recordFired(e,t=Date.now()){const s=this.store.get();this.store.set({...s,lastFiredAt:{...s.lastFiredAt,[e]:t}})}recordNudgeShown(e=Date.now()){this.sessionCount+=1;const t=this.store.get(),s=b(e),i=t.day.date===s?{date:s,count:t.day.count+1}:{date:s,count:1};this.store.set({...t,day:i})}recordDismissed(e,t=Date.now()){const s=this.store.get();this.store.set({...s,dismissedAt:{...s.dismissedAt,[e]:t}})}dayCount(e){const t=this.store.get();return t.day.date===b(e)?t.day.count:0}deny(e,t){return l.debug(`NudgeGovernor: trigger "${e.id}" blocked — ${t}`),!1}};function j(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.floor(Math.random()*16);return(e==="x"?t:t&3|8).toString(16)})}function De(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Y(e,t){if(e==="*"||e===t)return!0;const s=e.split("/"),i=s.map((n,r)=>{const c=r===s.length-1;return n==="*"?c?"[^/]+(?:/[^/]+)*":"[^/]+":n.startsWith(":")?"[^/]+":De(n)});return new RegExp("^"+i.join("/")+"/?$").test(t)}function D(e,t){return e===t?!0:e===null||t===null?!1:Array.isArray(e)?!Array.isArray(t)||t.length!==e.length?!1:e.every((s,i)=>x(s,t[i])):typeof e=="object"?typeof t!="object"||Array.isArray(t)?!1:Object.entries(e).every(([s,i])=>D(i,t[s])):!1}function x(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!=typeof t)return!1;if(Array.isArray(e))return!Array.isArray(t)||e.length!==t.length?!1:e.every((s,i)=>x(s,t[i]));if(typeof e=="object"){const s=Object.keys(e),i=Object.keys(t);return s.length!==i.length?!1:s.every(n=>x(e[n],t[n]))}return!1}function V(e,t){if(e.route!==void 0&&!Y(e.route,t.route)||e.idleMs!==void 0&&t.idleMs<e.idleMs||e.dwellMs!==void 0&&t.dwellMs<e.dwellMs||e.rageClicks!==void 0&&t.rageClicks<e.rageClicks||e.routeBounces!==void 0&&t.routeBounces<e.routeBounces)return!1;if(e.fieldErrors!==void 0){for(const[s,i]of Object.entries(e.fieldErrors))if((t.fieldErrors[s]??0)<i)return!1}return!(e.pageState!==void 0&&!D(e.pageState,t.pageState??{}))}var $e=1e3,Ne="/api/sdk/v1/config",Me="/api/sdk/v1/proactive/evaluate",Ue=class{constructor(e){this.tickTimer=null,this.teardowns=[],this.started=!1,this.activeNudge=null,this.lastConversationId=null,this.shownIds=new Set,this.resolvedIds=new Set,this.lastEvaluationAt=0,this.client=e.client,this.collector=e.collector,this.governor=e.governor,this.tracker=e.tracker,this.options=e.options,this.tickMs=e.options.tickMs??$e}start(){if(this.started)return;this.started=!0,this.options.enabledOverride!==void 0?this.governor.setKillSwitch(this.options.enabledOverride):this.loadKillSwitch();const e=n=>{n.conversationId&&(this.lastConversationId=n.conversationId),this.governor.setTurnInFlight(!0)},t=()=>this.governor.setTurnInFlight(!1),s=()=>this.governor.setTurnInFlight(!1),i=n=>{n?.id&&(this.lastConversationId=n.id)};this.client.on("message:start",e),this.client.on("message:complete",t),this.client.on("message:error",s),this.client.on("conversation:loaded",i),this.teardowns.push(()=>{this.client.off("message:start",e),this.client.off("message:complete",t),this.client.off("message:error",s),this.client.off("conversation:loaded",i)}),this.tickTimer=setInterval(()=>this.evaluate(),this.tickMs),this.teardowns.push(this.collector.subscribe(()=>{Date.now()-this.lastEvaluationAt>=this.tickMs&&this.evaluate()}))}stop(){this.tickTimer!==null&&(clearInterval(this.tickTimer),this.tickTimer=null);for(const e of this.teardowns)e();this.teardowns=[],this.started=!1}getActiveNudge(){return this.activeNudge}async loadKillSwitch(){try{const e=await this.client.getHttp().get(Ne);this.governor.setKillSwitch(e?.proactive?.enabled===!0)}catch(e){l.debug("Proactive config fetch failed; staying disabled:",e),this.governor.setKillSwitch(!1)}}evaluate(){if(this.lastEvaluationAt=Date.now(),typeof document<"u"&&document.visibilityState==="hidden"||this.governor.isSlotOccupied())return;const e=this.collector.getSnapshot();for(const t of this.options.triggers)if(V(t.when,e)&&this.governor.canFire(t)){this.fire(t);return}}fire(e){if(this.governor.recordFired(e.id),this.governor.occupySlot(),e.intervene==="agent"){this.evaluateWithAgent(e);return}this.setReady({id:j(),triggerId:e.id,source:"static",message:e.intervene.message,action:e.intervene.action,goal:e.goal,kickoff:e.kickoff,createdAt:new Date})}async evaluateWithAgent(e){const t=j(),s={client_nudge_id:t,trigger_id:e.id,goal:e.goal??"",agent:this.client.getConfiguredAgent(),signals:this.collector.getSnapshot(),client_context:this.client.getContextSnapshot()??{entries:{}},client_functions:this.client.getRegisteredFunctions()};let i;try{i=await this.client.getHttp().post(Me,s)}catch(n){l.debug("Proactive evaluate failed (treated as no-intervene):",n),this.governor.releaseSlot();return}if(!i?.intervene||!i.message){this.governor.releaseSlot();return}this.setReady({id:t,triggerId:e.id,source:"agent",message:i.message,action:je(i.action),goal:e.goal,kickoff:e.kickoff,createdAt:new Date,serverNudgeId:i.nudge_id})}setReady(e){this.activeNudge=e,this.client.emitProactiveEvent("nudge:ready",e)}shown(e){this.shownIds.has(e.id)||(this.shownIds.add(e.id),this.governor.recordNudgeShown(),this.tracker.track({clientNudgeId:e.id,triggerId:e.triggerId,source:e.source,type:"shown",message:e.message,occurredAt:Date.now()}),this.client.emitProactiveEvent("nudge:shown",{nudge:e}))}dismiss(e){this.resolvedIds.has(e.id)||(this.resolvedIds.add(e.id),this.governor.recordDismissed(e.triggerId),this.tracker.track({clientNudgeId:e.id,triggerId:e.triggerId,source:e.source,type:"dismissed",occurredAt:Date.now()}),this.clearActive(e),this.client.emitProactiveEvent("nudge:dismissed",{nudge:e}))}async accept(e){this.resolvedIds.has(e.id)||(this.resolvedIds.add(e.id),this.tracker.track({clientNudgeId:e.id,triggerId:e.triggerId,source:e.source,type:"accepted",occurredAt:Date.now()}),this.clearActive(e),this.client.emitProactiveEvent("nudge:accepted",{nudge:e}),(e.source==="agent"||e.goal)&&(this.lastConversationId?this.client.sendMessage(Le(e),{hidden:!0,conversationId:this.lastConversationId}).catch(t=>l.debug("Nudge kickoff message failed:",t)):e.kickoff?this.client.sendMessage(e.kickoff).catch(t=>l.debug("Nudge kickoff message failed:",t)):l.debug("Nudge accepted with no open conversation and no trigger.kickoff; skipping the chat pre-seed (a hidden message cannot open a conversation).")),e.action&&await this.runAction(e.action))}markConverted(e){this.tracker.track({clientNudgeId:e.id,triggerId:e.triggerId,source:e.source,type:"converted",occurredAt:Date.now()})}clearActive(e){this.activeNudge?.id===e.id&&(this.activeNudge=null),this.governor.releaseSlot()}async runAction(e){try{"functionName"in e?await this.client.executeClientFunction(e.functionName,e.arguments??{}):await this.client.executeClientFunction("setPageState",e.setPageState)}catch(t){l.warn("Nudge action failed:",t)}}};function je(e){if(e)return{label:e.label,functionName:e.function_name,arguments:e.arguments}}function Le(e){const t=e.goal?` Goal: ${e.goal}.`:"";return`The user accepted a proactive nudge that said: "${e.message}".${t} Recent friction signals are in the client context (friction_signals). Start helping with this now: briefly explain what you can do and take the first concrete step.`}var Be=5e3,qe="/api/sdk/v1/proactive/events",Ge=class{constructor(e){if(this.queue=[],this.flushTimer=null,this.teardowns=[],this.destroyed=!1,this.post=e.post,this.agent=e.agent,this.flushIntervalMs=e.flushIntervalMs??Be,typeof document<"u"){const t=()=>{document.visibilityState==="hidden"&&this.flush({keepalive:!0})};document.addEventListener("visibilitychange",t),this.teardowns.push(()=>document.removeEventListener("visibilitychange",t))}if(typeof window<"u"){const t=()=>this.flush({keepalive:!0});window.addEventListener("pagehide",t),this.teardowns.push(()=>window.removeEventListener("pagehide",t))}}track(e){this.destroyed||(this.queue.push(e),this.flushTimer===null&&(this.flushTimer=setTimeout(()=>this.flush(),this.flushIntervalMs)))}flush(e){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.queue.length===0)return;const t=this.queue;this.queue=[];const s={events:t.map(i=>({client_nudge_id:i.clientNudgeId,trigger_id:i.triggerId,source:i.source,type:i.type,...i.message!==void 0?{message:i.message}:{},...this.agent?{agent:this.agent}:{},occurred_at:new Date(i.occurredAt).toISOString()}))};this.post(qe,s,e).catch(i=>{l.debug("Proactive event batch failed:",i)})}destroy(){if(!this.destroyed){this.flush({keepalive:!0}),this.destroyed=!0;for(const e of this.teardowns)e();this.teardowns=[]}}},L="friction_signals";function P(e,t){e.proactive&&(l.warn("createAgoProactive: a proactive controller is already attached; replacing it."),e.proactive.destroy());const s=new J({...t.signals,getPageState:()=>{const o=e.getContextSnapshot();if(!o)return null;const a={};for(const[h,u]of Object.entries(o.entries))h.startsWith("page-state:")&&u.data&&Object.assign(a,u.data);return Object.keys(a).length>0?a:null}});s.start(),e.addDynamicContext(L,()=>{const{pageState:o,...a}=s.getSnapshot({pageState:!1});return{name:"Friction signals",description:"Behavioral friction observed on the current page: dwell/idle times, rage clicks, route bounces, repeated field errors (names and counters only).",data:a}});const i=new Oe({frequencyCap:t.frequencyCap,storage:t.storage,storageKey:t.storageKey}),n=new Ge({post:(o,a,h)=>e.getHttp().post(o,a,h),agent:e.getConfiguredAgent()}),r=new Ue({client:e,collector:s,governor:i,tracker:n,options:t});r.start();const c={optOut:()=>i.optOut(),optIn:()=>i.optIn(),isOptedOut:()=>i.isOptedOut(),setEngaged:o=>i.setEngaged(o),signalFieldError:o=>s.signalFieldError(o),getSignalsSnapshot:()=>s.getSnapshot(),getActiveNudge:()=>r.getActiveNudge(),shown:o=>r.shown(o),accept:o=>r.accept(o),dismiss:o=>r.dismiss(o),markConverted:o=>r.markConverted(o),destroy:()=>{r.stop(),n.destroy(),s.destroy(),e.removeDynamicContext(L),e.proactive===c&&(e.proactive=null)}};return e.proactive=c,c}var Q=class{constructor(){this.entries=new Map,this.dynamicProviders=new Map}set(e,t){this.entries.set(e,t),l.log(`ClientContext set: ${e}`)}remove(e){const t=this.entries.delete(e);return t&&l.log(`ClientContext removed: ${e}`),t}addDynamicProvider(e,t){this.dynamicProviders.set(e,t),l.log(`DynamicContext provider added: ${e}`)}removeDynamicProvider(e){const t=this.dynamicProviders.delete(e);return t&&l.log(`DynamicContext provider removed: ${e}`),t}getSnapshot(){if(this.entries.size===0&&this.dynamicProviders.size===0)return null;const e={};for(const[t,s]of this.entries)e[t]=s;for(const[t,s]of this.dynamicProviders)try{const i=s();i&&(e[t]=i)}catch(i){l.error(`DynamicContext provider "${t}" threw:`,i)}return Object.keys(e).length===0?null:{entries:e}}clear(){this.entries.clear(),this.dynamicProviders.clear(),l.log("ClientContext cleared")}},We="page-state:";function Ke(e){const t={};for(const[s,i]of Object.entries(e))s.startsWith(We)&&i.data&&Object.assign(t,i.data);return t}function He(e){const t=e["current-page"]?.data;return t?.page?t.page:t?.url?t.url:e["browser-page"]?.data?.url}function ze(e,t){return e===t?!0:JSON.stringify(e)===JSON.stringify(t)}function Je(e,t){const s=[];for(const i of Object.keys(t))i in e&&(ze(e[i],t[i])||s.push({field:i,from:e[i],to:t[i]}));return s}function Xe(e,t){const s=Ke(t),i=He(t);return{changes:e&&e.page===i?Je(e.state,s):[],baseline:{page:i,state:s}}}function F(e){return e===null||typeof e!="object"?JSON.stringify(e)??"null":Array.isArray(e)?`[${e.map(F).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${F(e[t])}`).join(",")}}`}var B="",Z=class{constructor(e,t={}){this.callbacks=e,this.options=t,this.buffer="",this.message={},this.toolCalls=[],this.sources=[],this.followUpReplies=[],this.isFirstChunk=!0,this.answerCompleteEmitted=!1,this.waitingClientEmitted=!1,this.firedClientFunctions=new Map}async processStream(e){if(!e.body)throw new _("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 t=e.body.getReader(),s=new TextDecoder;try{for(;;){const{done:i,value:n}=await t.read();if(i)break;const r=s.decode(n,{stream:!0});this.buffer+=r,this.processBuffer()}return this.buffer.trim()&&this.processBuffer(),this.buildFinalMessage()}catch(i){if(this.options.signal?.aborted)return this.message.status="CANCELED",this.buildFinalMessage();const n=i instanceof Error?i:new _("Stream processing failed mid-flight. The connection may have dropped; retry the message.");throw this.callbacks.onError?.(n),n}finally{t.releaseLock()}}processBuffer(){const e=this.buffer.split(`
|
|
2
|
-
|
|
3
|
-
`);this.buffer=e.pop()||"";for(const t of e){if(!t.trim())continue;const s=t.split(`
|
|
4
|
-
`);for(const i of s)if(!i.startsWith(": ")&&i.startsWith("data: ")){const n=i.slice(6);try{const r=JSON.parse(n);this.handleChunk(r)}catch(r){l.warn("Failed to parse SSE data:",n,r)}}}}handleChunk(e){if(this.callbacks.onRawChunk?.(e),e.message_id&&!this.message.id&&(this.message.id=e.message_id),e.thread?.id&&(this.message.conversationId=e.thread.id,this.isFirstChunk&&this.message.id&&(this.isFirstChunk=!1,this.callbacks.onStart?.({conversationId:this.message.conversationId,messageId:this.message.id}))),e.title&&this.message.conversationId&&this.callbacks.onTitle?.({conversationId:this.message.conversationId,title:e.title}),e.content!==void 0&&(this.message.content=(this.message.content||"")+e.content,this.message.conversationId&&this.message.id&&this.callbacks.onChunk?.({content:e.content,conversationId:this.message.conversationId,messageId:this.message.id})),e.full_content!==void 0&&(this.message.content=e.full_content),e.status&&(this.message.status=e.status),e.agent&&(this.message.agent={id:e.agent.id,name:e.agent.name,displayName:e.agent.display_name}),e.knowledge_sources&&(this.sources=e.knowledge_sources.map(t=>{const s=t.knowledge_document;return{id:s.id,title:s.title,url:s.use_external_link?s.external_link_url:s.internal_link_url}})),e.type==="client_function"&&e.function_name){const t=`${e.function_name}::${F(e.arguments??{})}`,s=e.id||"",i=this.firedClientFunctions.get(t);let n=!1;i?s&&!i.has(s)&&(i.has(B)?(i.delete(B),i.add(s)):(i.add(s),n=!0)):(this.firedClientFunctions.set(t,new Set([s])),n=!0),n&&this.callbacks.onClientFunction?.({invocationId:s,functionName:e.function_name,arguments:e.arguments||{},conversationId:this.message.conversationId||""})}if(e.tool_call_data&&e.type&&e.type!=="client_function"){const t=this.parseToolCall(e);this.callbacks.onToolCall?.(t);const s=this.toolCalls.findIndex(i=>i.id===t.id);s>=0?this.toolCalls[s]=t:this.toolCalls.push(t)}e.follow_up_replies&&(this.followUpReplies=e.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:e.waiting_tool_call_ids??[]}))}parseToolCall(e){return{id:e.id||"",type:e.type||"status_message",status:e.status||"unknown",toolName:e.tool_name||"",toolDisplayName:e.tool_display_name,message:e.message,formSchema:e.form_schema,data:e.data,functionName:e.function_name,arguments:e.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 e=this.buildMessage();return e.status!=="WAITING_CLIENT"&&this.callbacks.onComplete?.(e),e}};function Ye(e){const t=e.message.toLowerCase();return t.includes("load failed")||t.includes("failed to fetch")||t.includes("network")||t.includes("abort")}var Ve=new Set(["image/jpeg","image/png","image/gif","image/webp","image/bmp","image/tiff"]);function ee(e){if(!e)return;const t=typeof window<"u"&&window.location?window.location.href:"https://ago.local/";try{const s=new URL(e,t);if(s.protocol==="http:"||s.protocol==="https:"||s.protocol==="blob:")return e}catch{}}function Qe(e){return e.isSafeImage===!0&&typeof e.contentType=="string"&&e.contentType.startsWith("image/")&&ee(e.url)!==void 0}function Ze(e){if(e==null||e<0)return"";if(e<1024)return`${e} B`;const t=e/1024;return t<1024?`${t.toFixed(1)} KB`:`${(t/1024).toFixed(1)} MB`}function et(e){return{id:String(e.id??""),name:typeof e.name=="string"&&e.name?e.name:"file",contentType:typeof e.content_type=="string"?e.content_type:void 0,fileSize:typeof e.file_size=="number"?e.file_size:void 0,url:typeof e.file_url=="string"?e.file_url:void 0,isSafeImage:e.is_safe_image===!0}}function tt(e){return e.map((t,s)=>({id:`local-${s}-${t.name}`,name:t.name,contentType:t.type||void 0,fileSize:t.size,url:typeof URL<"u"&&URL.createObjectURL?URL.createObjectURL(t):void 0,isSafeImage:Ve.has(t.type)}))}var te=class{constructor(){this.handlers=new Map}on(e,t){this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(t)}off(e,t){const s=this.handlers.get(e);s&&s.delete(t)}emit(e,t){const s=this.handlers.get(e);s&&s.forEach(i=>{try{i(t)}catch(n){console.error(`Error in event handler for ${String(e)}:`,n)}})}once(e,t){const s=(i=>{this.off(e,s),t(i)});this.on(e,s)}waitFor(e,t){return new Promise((s,i)=>{let n;const r=(c=>{n&&clearTimeout(n),s(c)});this.once(e,r),t?.timeout&&(n=setTimeout(()=>{this.off(e,r),i(new Error(`waitFor("${String(e)}") timed out after ${t.timeout}ms`))},t.timeout))})}removeAllListeners(e){e?this.handlers.delete(e):this.handlers.clear()}},q='`baseUrl` is required, e.g. new AgoClient({ baseUrl: "https://YOUR-DOMAIN.api.useago.com" }). Try the public demo account https://playground.api.useago.com (agent generic-guide). See docs/general/configuration.md#error-codes (config_missing_base_url).',st=/^[a-z][a-z0-9+.-]*:\/\//i,G=!1;function O(e,t="AgoClient"){if(typeof e!="object"||e===null)throw new g(`${t}: ${q}`,"config_missing_base_url");const s=e.baseUrl;if(typeof s!="string"||s.trim()==="")throw new g(`${t}: ${q}`,"config_missing_base_url");!s.startsWith("/")&&!st.test(s)&&(G||(G=!0,console.warn(`[AGO] ${t}: \`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 it(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function A(e){return e.split("/").filter(t=>t.startsWith(":")).map(t=>t.slice(1))}function nt(e,t){const s=[];return{path:e.split("/").map(i=>{if(!i.startsWith(":"))return i;const n=i.slice(1),r=t[n];return r==null||r===""?(s.push(n),i):encodeURIComponent(String(r))}).join("/"),missing:s}}function rt(e,t){const s=t.find(n=>n.path===e);if(s)return s;const i=t.find(n=>{if(!n.path.includes(":"))return!1;const r="^"+n.path.split("/").map(c=>c.startsWith(":")?"[^/]+":it(c)).join("/")+"/?$";return new RegExp(r).test(e)});return i||t.filter(n=>n.path!=="/"&&e.startsWith(n.path)).sort((n,r)=>r.path.length-n.path.length)[0]}var ot="activity:recent",at="state:delta";function W(e){return e==="setPageState"?"readPageData":`read${e.charAt(0).toUpperCase()}${e.slice(1)}Data`}var ct=class se{constructor(t){this.lastSentPageState=null,this.warnedEmptyConversations=new Set,this.warnedPageDataPlaceholder=!1,this.pageDataCompanions=new Set,this.pendingResumes=new Map,this.pendingApprovals=new Map,this.resumeGate=null,this.activeTurn=null,this.pausedTurn=null,this.stoppedTurnIds=new Set,this.proactive=null,O(t,"AgoClient"),this.config=t,this.activityLedger=new K(t.maxActivityEntries),this.httpClient=new ce(t),this.functionRegistry=new z({maxResultBytes:t.maxFunctionResultBytes}),this.contextRegistry=new Q,this.eventEmitter=new te,this.registerActivityContext(),t.debug&&l.enable(),t.proactive&&P(this,t.proactive),l.log("AgoClient initialized")}async sendMessage(t,s){const i=this.functionRegistry.getSchemas(),n=this.buildSendContext(),r=this.config.agent||this.config.defaultAgentId,c=this.resolveClientFunctionsMode(s),o={content:t,conversation_id:s?.conversationId,agent_id:s?.agentId||r};i.length>0&&(o.client_functions=i,c==="pause"&&(o.client_functions_mode=c)),n&&(o.client_context=n),s?.hidden&&(o.hidden=!0);const a=this.beginTurn();let h;if(s?.files&&s.files.length>0){const u=new FormData;u.append("content",t),s.conversationId&&u.append("conversation_id",s.conversationId),(s.agentId||r)&&u.append("agent_id",s.agentId||r||""),i.length>0&&(u.append("client_functions",JSON.stringify(i)),c==="pause"&&u.append("client_functions_mode",c)),n&&u.append("client_context",JSON.stringify(n)),s.hidden&&u.append("hidden","true");for(const d of s.files)u.append("files",d);h=await this.sendRequest(a,()=>this.httpClient.postFormData("/api/sdk/v1/messages",u,{signal:a.controller.signal}))}else h=await this.sendRequest(a,()=>this.httpClient.postStream("/api/sdk/v1/messages",o,{signal:a.controller.signal}));return h?this.processSSEResponse(h,a):this.buildStoppedMessage(a)}async sendRequest(t,s){try{return await s()}catch(i){if(this.endTurn(t),R(i))return null;throw i}}async processSSEResponse(t,s){let i=!1;const n=new Z({onRawChunk:r=>{s&&(r.message_id&&!s.messageId&&(s.messageId=r.message_id),r.thread?.id&&(s.conversationId=r.thread.id)),this.eventEmitter.emit("stream:message",r)},onStart:r=>{this.eventEmitter.emit("message:start",r)},onChunk:r=>{this.eventEmitter.emit("message:chunk",r)},onToolCall:r=>{this.eventEmitter.emit("toolCall:received",r),r.type==="form"&&this.eventEmitter.emit("toolCall:form",r)},onClientFunction:async r=>{if(i=!0,this.eventEmitter.emit("function:invoke",r),this.recordAgentAction(r),r.invocationId&&this.requiresApproval(r)){this.pendingApprovals.set(r.invocationId,r),this.eventEmitter.emit("function:awaiting-approval",r);return}await this.handleClientFunctionInvocation(r)},onTitle:r=>{this.eventEmitter.emit("conversation:title",r)},onAnswerComplete:r=>{this.eventEmitter.emit("message:answer-complete",r)},onWaitingClient:r=>{this.pausedTurn={messageId:r.messageId,conversationId:r.conversationId},this.eventEmitter.emit("message:waiting-client",r),this.markResumeSignal(r.messageId,r.conversationId,"waitingConfirmed")},onComplete:r=>{s?.stopRequested&&!r.id||this.eventEmitter.emit("message:complete",r)},onError:r=>{this.eventEmitter.emit("message:error",{error:r.message,code:r instanceof g?r.code:void 0})}},{signal:s?.controller.signal});try{const r=await n.processStream(t);return s?.stopRequested&&(this.postStopForTurn(s,s.messageId??r.id),this.eventEmitter.emit("message:stopped",{conversationId:s.conversationId??r.conversationId??"",messageId:s.messageId??r.id??""})),r.status!=="WAITING_CLIENT"&&this.maybeFlagEmptyReply(r,i),r}finally{s&&this.endTurn(s)}}async stop(){const t=this.activeTurn;if(t){t.stopRequested=!0;const n=t.messageId;return t.controller.abort(),n?this.postStopForTurn(t,n):null}const s=this.pausedTurn;if(!s)return null;this.pausedTurn=null,this.pendingResumes.delete(s.messageId),this.stoppedTurnIds.add(s.messageId);let i=null;try{i=await this.stopMessage(s.messageId)}catch(n){l.warn("Failed to stop the paused turn:",n)}return this.eventEmitter.emit("message:stopped",{conversationId:s.conversationId,messageId:s.messageId}),i}async stopMessage(t){const s=await this.httpClient.post(`/api/sdk/v1/messages/${t}/stop`);return{status:s.status,messageStatus:s.message_status}}isGenerating(){return this.activeTurn!==null||this.pausedTurn!==null}beginTurn(){this.pausedTurn=null;const t={controller:new AbortController,messageId:null,conversationId:null,stopRequested:!1,stopPosted:!1};return this.activeTurn=t,t}endTurn(t){this.activeTurn===t&&(this.activeTurn=null)}async postStopForTurn(t,s){if(!s||t.stopPosted)return null;t.stopPosted=!0;try{return await this.stopMessage(s)}catch(i){return l.warn("Failed to stop the turn server-side:",i),null}}buildStoppedMessage(t){const s={id:t.messageId??"",conversationId:t.conversationId??"",content:"",role:"assistant",status:"CANCELED",createdAt:new Date};return s.id&&this.eventEmitter.emit("message:complete",s),this.eventEmitter.emit("message:stopped",{conversationId:s.conversationId,messageId:s.id}),s}resolveClientFunctionsMode(t){return t?.clientFunctionsMode??this.config.clientFunctionsMode??"pause"}registerResumeGate(t){return this.resumeGate=t,()=>{this.resumeGate===t&&(this.resumeGate=null)}}async continueMessage(t){const s=this.functionRegistry.getSchemas(),i=this.buildSendContext(),n={};s.length>0&&(n.client_functions=s),i&&(n.client_context=i);const r=this.beginTurn();r.messageId=t;const c=await this.sendRequest(r,()=>this.httpClient.postStream(`/api/sdk/v1/messages/${t}/continue`,n,{signal:r.controller.signal}));return c?this.processSSEResponse(c,r):this.buildStoppedMessage(r)}markResumeSignal(t,s,i){if(!t)return;const n=this.pendingResumes.get(t)??{conversationId:s,waitingConfirmed:!1,resultsReady:!1,started:!1};n[i]=!0,s&&(n.conversationId=s),this.pendingResumes.set(t,n),!(!n.waitingConfirmed||!n.resultsReady||n.started)&&(n.started=!0,this.runAutoResume(t,n.conversationId))}async runAutoResume(t,s){try{this.resumeGate&&await this.resumeGate({conversationId:s,messageId:t})}catch(i){l.warn("Resume gate threw; resuming anyway:",i)}if(this.pendingResumes.delete(t),!this.stoppedTurnIds.delete(t))try{await this.continueMessage(t)}catch(i){l.warn("Auto-resume of paused turn failed:",i)}}maybeFlagEmptyReply(t,s){t.status==="DONE"&&(t.content??"").trim()===""&&!s&&!(t.toolCalls&&t.toolCalls.length>0)&&!(t.followUpReplies&&t.followUpReplies.length>0)&&setTimeout(()=>{if(this.eventEmitter.emit("message:empty",{conversationId:t.conversationId??"",messageId:t.id??""}),this.config.warnOnEmptyReply===!1)return;const i=t.conversationId||"(no conversation)";if(this.warnedEmptyConversations.has(i))return;this.warnedEmptyConversations.add(i);const n=this.config.agent||this.config.defaultAgentId;t.id?console.warn("[AGO] Reply completed with empty content. Possible causes: "+(n?`the agent "${n}" 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: "+(n?`the agent "${n}" 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(t){let s,i;try{s=await this.functionRegistry.execute(t.functionName,t.arguments)}catch(n){i=n instanceof Error?n.message:"Unknown error",l.error("Client function execution failed:",n)}if(t.invocationId)try{const n=await this.submitToolCallForm(t.invocationId,{result:s,error:i,_type:"client_function_result"});n?.resume?.ready&&this.markResumeSignal(n.resume.message_id,t.conversationId,"resultsReady")}catch(n){l.error("Failed to submit function result:",n)}this.eventEmitter.emit("function:result",{invocationId:t.invocationId,result:s,error:i})}requiresApproval(t){const s=this.config.approvalPolicy;if(s)try{if(s(t))return!0}catch(i){l.warn("approvalPolicy threw; not gating this call:",i)}return this.functionRegistry.get(t.functionName)?.requiresApproval===!0}async approveFunction(t){const s=this.pendingApprovals.get(t);s&&(this.pendingApprovals.delete(t),await this.handleClientFunctionInvocation(s))}async rejectFunction(t){const s=this.pendingApprovals.get(t);if(!s)return;this.pendingApprovals.delete(t);const i={approved:!1,reason:"user_rejected"};try{const n=await this.submitToolCallForm(t,{result:i,_type:"client_function_result"});n?.resume?.ready&&this.markResumeSignal(n.resume.message_id,s.conversationId,"resultsReady")}catch(n){l.error("Failed to submit function rejection:",n)}this.eventEmitter.emit("function:result",{invocationId:t,result:i})}async getConversations(t){const s=new URLSearchParams;t?.page!==void 0&&s.set("page",String(t.page)),t?.pageSize!==void 0&&s.set("page_size",String(t.pageSize));const i=s.toString(),n=await this.httpClient.get(`/api/sdk/v1/conversations${i?`?${i}`:""}`),r=(Array.isArray(n.data)?n.data:[]).map(c=>({id:String(c.id),title:c.title??"",lastMessageDate:new Date(c.last_message_at??c.created_at??0)}));return{data:r,hasMore:n.has_more??!1,total:n.total??r.length}}async getConversation(t){const s=await this.httpClient.get(`/api/sdk/v1/conversations/${t}`),i={id:s.id,title:s.title,lastMessageDate:new Date(s.last_message_at??s.created_at??0),messages:s.messages.map(n=>({id:n.id,conversationId:s.id,content:n.content,role:n.role,status:n.status,createdAt:new Date(n.created_at),hidden:n.hidden,toolCalls:se.mapPersistedToolCalls(n.tool_call_data),attachments:n.attachments&&n.attachments.length>0?n.attachments.map(et):void 0,followUpReplies:n.follow_up_replies&&n.follow_up_replies.length>0?n.follow_up_replies:void 0}))};return this.eventEmitter.emit("conversation:loaded",i),i}static mapPersistedToolCalls(t){if(!(!t||t.length===0))return t.map(s=>({id:String(s.id??""),type:s.type??"status_message",status:String(s.status??"unknown"),toolName:s.tool_name??"",toolDisplayName:s.tool_display_name,message:s.message,formSchema:s.form_schema,data:s.data,functionName:s.function_name,arguments:s.arguments}))}async getMessages(t){return(await this.getConversation(t)).messages||[]}async resumePendingClientFunctions(t){const s=[...t.messages??[]].reverse().find(o=>o.role==="assistant"&&o.status==="WAITING_CLIENT");if(!s)return null;const i=(s.toolCalls??[]).filter(o=>o.type==="client_function"&&o.status==="waiting_input"&&o.functionName),n=new Set(this.functionRegistry.getSchemas().map(o=>o.name)),r=i.filter(o=>!n.has(o.functionName));if(r.length>0)return l.warn("Cannot resume paused turn: function(s) not registered on this page:",r.map(o=>o.functionName)),null;let c=i.length===0;for(const o of i){const a={invocationId:o.id,functionName:o.functionName,arguments:o.arguments??{},conversationId:t.id};if(this.requiresApproval(a)){this.pendingApprovals.set(o.id,a),this.markResumeSignal(s.id,t.id,"waitingConfirmed"),this.eventEmitter.emit("function:awaiting-approval",a),c=!1;continue}this.eventEmitter.emit("function:invoke",a);let h,u;try{h=await this.functionRegistry.execute(o.functionName,o.arguments??{})}catch(d){u=d instanceof Error?d.message:"Unknown error",l.error("Client function execution failed:",d)}this.eventEmitter.emit("function:result",{invocationId:o.id,result:h,error:u}),c=(await this.submitToolCallForm(o.id,{result:h,error:u,_type:"client_function_result"}))?.resume?.ready??c}return c?this.continueMessage(s.id):null}async submitToolCallForm(t,s){return this.httpClient.post(`/api/sdk/v1/tool-calls/${t}/submit`,{formData:s})}async confirmToolCall(t){await this.httpClient.post(`/api/tool-calls/${t}/confirm/`)}async rejectToolCall(t){await this.httpClient.post(`/api/tool-calls/${t}/reject/`)}async getFormCollector(t){const s=await this.httpClient.get(`/api/sdk/v1/forms/${encodeURIComponent(t)}`);return{name:s.name,description:s.description,schema:s.schema,submit:s.submit??void 0}}async submitFormCollector(t,s){return this.httpClient.post("/api/sdk/v1/forms/submit",{name:t,values:s})}async submitFeedback(t,s){await this.httpClient.post(`/api/sdk/v1/messages/${t}/feedback`,{rating:s})}registerFunction(t,s,i){typeof t=="object"?this.functionRegistry.register(t):this.functionRegistry.register(t,s,i)}register(t){if(Array.isArray(t))for(const s of t)this.functionRegistry.register(s);else this.functionRegistry.register(t)}unregisterFunction(t){return this.functionRegistry.unregister(t)}getRegisteredFunctions(){return this.functionRegistry.getSchemas()}async executeClientFunction(t,s){return this.functionRegistry.execute(t,s)}registerNavigationFunction(t,s){const i=s.map(o=>o.name),n=s.map(o=>{const a=A(o.path),h=a.length>0?` (requires ${a.map(u=>`"${u}"`).join(", ")})`:"";return`- "${o.name}"${h}: ${o.description}`}).join(`
|
|
5
|
-
`),r=new Map;for(const o of s)for(const a of A(o.path))r.set(a,[...r.get(a)??[],o.name]);const c={page:{type:"string",description:"The page to navigate to",enum:i}};for(const[o,a]of r){if(o==="page"){l.error('registerNavigationFunction: a ":page" placeholder collides with the "page" argument and is ignored. Rename the placeholder.');continue}c[o]={type:"string",description:`Value for ":${o}" in the page path. Required when page is ${a.map(h=>`"${h}"`).join(" or ")}.`}}this.registerFunction("navigateToPage",async o=>{const a=o.page,h=s.find(p=>p.name===a);if(!h)return{success:!1,error:`Unknown page: ${a}`};let u={};if(typeof o.params=="string")try{u=JSON.parse(o.params)}catch{}else o.params&&typeof o.params=="object"&&(u=o.params);const d={};for(const p of A(h.path))d[p]=o[p]??u[p];const{path:f,missing:C}=nt(h.path,d);if(C.length>0){const p=C.map(S=>`"${S}": "..."`).join(", ");return{success:!1,error:`Page "${a}" needs ${C.map(S=>`"${S}"`).join(", ")}. Retry with { "page": "${a}", ${p} }.`}}return t(f),{success:!0,navigatedTo:f}},{description:`Navigate the user to a page in the application. Available pages:
|
|
6
|
-
${n}`,parameters:{type:"object",properties:c,required:["page"]}}),this.addDynamicContext("current-page",()=>{if(typeof window>"u"||!window.location)return null;const o=window.location.href,a=typeof document<"u"?document.title:void 0,h=rt(window.location.pathname,s),u={url:o};return h&&(u.page=h.name),a&&(u.title=a),{name:"Current page",description:"The page the user is currently viewing.",data:u}})}unregisterNavigationFunction(){this.unregisterFunction("navigateToPage"),this.removeDynamicContext("current-page")}registerPageStateFunction(t,s){const i=s?.functionName??"setPageState",n=new Map(t.map(a=>[a.name,a])),r=s?.data,c=t.map(a=>`- "${a.name}": ${a.description}`).join(`
|
|
7
|
-
`);this.registerFunction(i,async a=>{const h={};for(const[u,d]of Object.entries(a)){if(d===void 0)continue;const f=n.get(u);f&&(await f.set(d),h[u]=d)}return r?{success:!0,applied:h,data:U(i,await this.readPageData(i,r,s),this.pageDataBudget(r)-this.envelopeBytes({success:!0,applied:h}))}:{success:!0,applied:h}},{description:`Change the state of the current page. Set ONLY the controls the user explicitly asked for; leave the others unset. Available controls:
|
|
8
|
-
${c}`+(r?`
|
|
9
|
-
Returns the resulting page data once it has loaded: ${r.description}`:""),parameters:{type:"object",properties:Object.fromEntries(t.map(a=>[a.name,{...a.schema,description:a.description}])),required:[]},requiresApproval:s?.requiresApproval,maxResultBytes:r?.maxResultBytes});const o=W(i);r?(this.warnIfPlaceholderMode(i),this.registerFunction(o,async()=>({data:U(i,await this.readPageData(i,r,s),this.pageDataBudget(r)-this.envelopeBytes({}))}),{description:`Read what the current page is displaying, without changing anything: ${r.description}`,parameters:{type:"object",properties:{}},maxResultBytes:r.maxResultBytes}),this.pageDataCompanions.add(o)):this.pageDataCompanions.delete(o)&&this.unregisterFunction(o),this.addDynamicContext(`page-state:${i}`,()=>{const a=t.filter(u=>typeof u.get=="function");if(a.length===0)return null;const h={};for(const u of a)h[u.name]=u.get();return{name:"Page state",description:"Current, editable state of the page the user is viewing.",data:h}})}unregisterPageStateFunction(t){const s=t??"setPageState";this.unregisterFunction(s);const i=W(s);this.pageDataCompanions.delete(i)&&this.unregisterFunction(i),this.removeDynamicContext(`page-state:${s}`)}pageDataBudget(t){return t.maxResultBytes??this.functionRegistry.getDefaultMaxResultBytes()}envelopeBytes(t){return m(JSON.stringify({...t,data:null}))-4}readPageData(t,s,i){return ye(()=>s,i?.settleTimeoutMs??1e4).catch(n=>(l.warn(`Reading page data for "${t}" failed:`,n),{error:"page_data_unavailable",message:n instanceof Error?n.message:String(n)}))}warnIfPlaceholderMode(t){this.warnedPageDataPlaceholder||this.config.clientFunctionsMode==="placeholder"&&(this.warnedPageDataPlaceholder=!0,l.warn(`"${t}" declares a data source, but clientFunctionsMode is "placeholder": the agent will not see that data during the turn it calls the function. Switch to "pause" (the default) for the data to come back as the result of its own call.`))}setContext(t,s){this.contextRegistry.set(t,s),this.notifyContextChanged()}removeContext(t){const s=this.contextRegistry.remove(t);return s&&this.notifyContextChanged(),s}addDynamicContext(t,s){this.contextRegistry.addDynamicProvider(t,s),this.notifyContextChanged()}removeDynamicContext(t){const s=this.contextRegistry.removeDynamicProvider(t);return s&&this.notifyContextChanged(),s}notifyContextChanged(){this.eventEmitter.emit("context:changed",this.contextRegistry.getSnapshot())}emitFormEvent(t,s){this.eventEmitter.emit(t,s)}emitProactiveEvent(t,s){this.eventEmitter.emit(t,s)}getHttp(){return this.httpClient}getConfiguredAgent(){return this.config.agent||this.config.defaultAgentId}enableAutoPageContext(){this.addDynamicContext("browser-page",()=>{const t=typeof window<"u"?window.location.href:void 0,s=typeof document<"u"?document.title:void 0;return!t&&!s?null:{name:"Browser page",description:"Current page the user is viewing",data:{url:t,title:s}}})}getContextSnapshot(){return this.contextRegistry.getSnapshot()}buildSendContext(){const t=this.contextRegistry.getSnapshot(),s=t?.entries??{},{changes:i,baseline:n}=Xe(this.lastSentPageState,s);return this.lastSentPageState=n,i.length===0?t:{entries:{...s,[at]:{name:"State changes",description:"Fields in the page state that changed since your last message, as field: <old> -> <new>.",data:{changes:i}}}}}recordActivity(t){this.addActivity(t)}getRecentActivity(){return this.activityLedger.getRecent()}clearActivity(){this.activityLedger.clear()}addActivity(t){const s=this.activityLedger.add(t);this.eventEmitter.emit("activity:recorded",s)}recordAgentAction(t){const s=t.functionName,i=t.arguments;let n,r;if(s==="navigateToPage"){const c=i?.page;n="agent.navigate",r=c?`Agent navigated to "${String(c)}"`:"Agent navigated"}else s==="setPageState"?(n="agent.page_state",r="Agent changed the page state"):(n=`agent.${s}`,r=`Agent ran "${s}"`);this.addActivity({actor:"agent",name:n,summary:r,...i&&Object.keys(i).length>0?{data:{arguments:i}}:{}})}registerActivityContext(){this.contextRegistry.addDynamicProvider(ot,()=>{const t=this.activityLedger.getRecent();return t.length===0?null:{name:"Recent activity",description:"Recent actions by the user and the agent in the app, oldest first. Use this to understand what just happened before this message.",data:{events:t}}})}on(t,s){this.eventEmitter.on(t,s)}off(t,s){this.eventEmitter.off(t,s)}once(t,s){this.eventEmitter.once(t,s)}waitFor(t,s){return this.eventEmitter.waitFor(t,s)}updateConfig(t){const s={};for(const[n,r]of Object.entries(t))r!==void 0&&(s[n]=r);const i={...this.config,...s};O(i,"AgoClient.updateConfig"),this.config=i,this.httpClient.updateConfig(s),s.debug!==void 0&&(s.debug?l.enable():l.disable()),s.maxFunctionResultBytes!==void 0&&this.functionRegistry.setDefaultMaxResultBytes(s.maxFunctionResultBytes)}destroy(){this.proactive?.destroy(),this.proactive=null,this.activeTurn?.controller.abort(),this.activeTurn=null,this.pausedTurn=null,this.stoppedTurnIds.clear(),this.eventEmitter.removeAllListeners(),this.functionRegistry.clear(),this.contextRegistry.clear(),this.pendingResumes.clear(),this.pendingApprovals.clear(),this.resumeGate=null,l.log("AgoClient destroyed")}reviveAfterDestroy(){this.registerActivityContext(),this.config.proactive&&!this.proactive&&P(this,this.config.proactive)}};Object.defineProperty(exports,"ActivityLedger",{enumerable:!0,get:function(){return K}});Object.defineProperty(exports,"AgoApiError",{enumerable:!0,get:function(){return y}});Object.defineProperty(exports,"AgoClient",{enumerable:!0,get:function(){return ct}});Object.defineProperty(exports,"AgoError",{enumerable:!0,get:function(){return g}});Object.defineProperty(exports,"AgoFunctionError",{enumerable:!0,get:function(){return I}});Object.defineProperty(exports,"AgoNetworkError",{enumerable:!0,get:function(){return w}});Object.defineProperty(exports,"AgoStreamError",{enumerable:!0,get:function(){return _}});Object.defineProperty(exports,"ClientContextRegistry",{enumerable:!0,get:function(){return Q}});Object.defineProperty(exports,"DEFAULT_MAX_ACTIVITY_ENTRIES",{enumerable:!0,get:function(){return ie}});Object.defineProperty(exports,"EventEmitter",{enumerable:!0,get:function(){return te}});Object.defineProperty(exports,"FunctionRegistry",{enumerable:!0,get:function(){return z}});Object.defineProperty(exports,"SSEHandler",{enumerable:!0,get:function(){return Z}});Object.defineProperty(exports,"SignalCollector",{enumerable:!0,get:function(){return J}});Object.defineProperty(exports,"attachmentsFromFiles",{enumerable:!0,get:function(){return tt}});Object.defineProperty(exports,"canInlineImage",{enumerable:!0,get:function(){return Qe}});Object.defineProperty(exports,"createAgoProactive",{enumerable:!0,get:function(){return P}});Object.defineProperty(exports,"createStore",{enumerable:!0,get:function(){return X}});Object.defineProperty(exports,"formatFileSize",{enumerable:!0,get:function(){return Ze}});Object.defineProperty(exports,"isStreamNetworkError",{enumerable:!0,get:function(){return Ye}});Object.defineProperty(exports,"logger",{enumerable:!0,get:function(){return l}});Object.defineProperty(exports,"matchesRoutePattern",{enumerable:!0,get:function(){return Y}});Object.defineProperty(exports,"matchesWhen",{enumerable:!0,get:function(){return V}});Object.defineProperty(exports,"partialDeepMatch",{enumerable:!0,get:function(){return D}});Object.defineProperty(exports,"safeAttachmentUrl",{enumerable:!0,get:function(){return ee}});Object.defineProperty(exports,"validateConfig",{enumerable:!0,get:function(){return O}});
|