@useago/sdk 1.5.5 → 1.7.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 +230 -0
- package/README.md +59 -0
- package/dist/AgoClient-BHegN73y.cjs +9 -0
- package/dist/{AgoClient-BI5esqbd.js → AgoClient-PWPaekcg.js} +657 -387
- package/dist/angular.cjs +1 -1
- package/dist/angular.d.cts +230 -5
- package/dist/angular.d.ts +230 -5
- package/dist/angular.js +10 -1
- package/dist/{createConversationSession-0zVkMdYj.cjs → createConversationSession-BbUUukV0.cjs} +1 -1
- package/dist/{createConversationSession-BlYTpxwz.js → createConversationSession-BkruoOVm.js} +1 -1
- package/dist/{createFormCollector-DiCe_8ik.cjs → createFormCollector-C1ixsjV0.cjs} +1 -1
- package/dist/{createFormCollector-Bgs94NyR.js → createFormCollector-CvVFNkp6.js} +1 -1
- package/dist/{createMockClient-DcuNlufr.js → createMockClient-BDLW6tVc.js} +5 -2
- package/dist/createMockClient-BSNuN3ys.cjs +1 -0
- package/dist/devtools.d.cts +220 -5
- package/dist/devtools.d.ts +220 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +238 -6
- package/dist/index.d.ts +238 -6
- package/dist/index.js +111 -109
- package/dist/react.cjs +40 -3
- package/dist/react.d.cts +419 -7
- package/dist/react.d.ts +419 -7
- package/dist/react.js +1195 -743
- package/dist/sheetController-CBUVOroo.js +205 -0
- package/dist/sheetController-DY-sC13T.cjs +1 -0
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +220 -5
- package/dist/testing.d.ts +220 -5
- package/dist/testing.js +1 -1
- package/dist/vue.cjs +1 -1
- package/dist/vue.d.cts +235 -5
- package/dist/vue.d.ts +235 -5
- package/dist/vue.js +121 -100
- package/dist/widget.cjs +4 -4
- package/dist/widget.d.cts +328 -5
- package/dist/widget.d.ts +328 -5
- package/dist/widget.js +721 -629
- package/llms-full.txt +202 -14
- package/package.json +1 -1
- package/dist/AgoClient-CQh8YovA.cjs +0 -8
- package/dist/createMockClient-Dw-cyISN.cjs +0 -1
package/llms-full.txt
CHANGED
|
@@ -256,6 +256,24 @@ await ago.sendMessage("Summarise this", { files: [fileInput.files[0]], conversat
|
|
|
256
256
|
await ago.sendMessage("Escalate this", { agentId: "human-handoff" });
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
+
Stop the answer being generated. `stop()` closes the stream AND calls the backend
|
|
260
|
+
to stop generating; closing the stream alone would leave the agent running:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
document.getElementById("stop").onclick = () => void ago.stop();
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The partial text is kept, the message is finalized as `CANCELED`, and the pending
|
|
267
|
+
`sendMessage` resolves with it (it does not reject). `message:complete` fires with
|
|
268
|
+
the partial message, then `message:stopped`. Finalize your UI on
|
|
269
|
+
`message:stopped`: it always fires, while `message:complete` is skipped when
|
|
270
|
+
there is nothing to complete (a stop that beat the backend naming the message, or
|
|
271
|
+
a turn stopped while paused on client functions). `stop()` resolves to `null` when
|
|
272
|
+
nothing was generating, `isGenerating()` reports whether there is anything to
|
|
273
|
+
stop, and `stopMessage(messageId)` stops a turn by id (e.g. one still running
|
|
274
|
+
after a page reload). A turn paused on client functions is stoppable too, and
|
|
275
|
+
stopping it cancels the automatic resume.
|
|
276
|
+
|
|
259
277
|
## 3. Conversations
|
|
260
278
|
|
|
261
279
|
```ts
|
|
@@ -357,9 +375,12 @@ const widget = mountChatWidget("#chat", {
|
|
|
357
375
|
});
|
|
358
376
|
|
|
359
377
|
widget.sendMessage("Hello");
|
|
378
|
+
widget.stop(); // interrupt the answer being generated (no-op when idle)
|
|
360
379
|
widget.destroy(); // removes listeners, uninstalls forms, clears the DOM
|
|
361
380
|
```
|
|
362
381
|
|
|
382
|
+
While the agent answers, the send button becomes a Stop button that interrupts
|
|
383
|
+
the turn; pass `allowStop: false` to keep a disabled spinner instead.
|
|
363
384
|
Set `placement: "left"` or `"right"` for a fixed full-height side panel with a
|
|
364
385
|
launcher button. Set `persistConversation: true` to resume the visitor's last
|
|
365
386
|
thread across reloads. Theme it with the `theme` option or `--ago-*` CSS variables
|
|
@@ -384,7 +405,8 @@ ago.destroy(); // clean up listeners, functions, conte
|
|
|
384
405
|
## Key events
|
|
385
406
|
|
|
386
407
|
`message:start`, `message:chunk` (`{ content }` per token), `message:complete`
|
|
387
|
-
(`AgoMessage`), `message:
|
|
408
|
+
(`AgoMessage`), `message:stopped` (`{ conversationId, messageId }`, after a
|
|
409
|
+
`stop()`), `message:error`, `conversation:loaded`, `context:changed`,
|
|
388
410
|
`toolCall:received`, `function:invoke`, `function:result`, `connection:status`.
|
|
389
411
|
Use `on` / `off` / `once`, or `await ago.waitFor("message:complete", { timeout })`.
|
|
390
412
|
|
|
@@ -496,10 +518,12 @@ function Support() {
|
|
|
496
518
|
}
|
|
497
519
|
```
|
|
498
520
|
|
|
499
|
-
Common props: `title`, `welcomeMessage`, `placeholder`, `allowFiles`, `
|
|
500
|
-
`logoUrl`, `showAgentName`, `forms`, `onMessageSent`, `onMessageReceived`,
|
|
521
|
+
Common props: `title`, `welcomeMessage`, `placeholder`, `allowFiles`, `allowStop`,
|
|
522
|
+
`height`, `logoUrl`, `showAgentName`, `forms`, `onMessageSent`, `onMessageReceived`,
|
|
501
523
|
`onFollowUpClick`, `className`. When the agent returns follow-up suggestions the
|
|
502
524
|
widget renders them as clickable buttons (clicking sends the reply by default).
|
|
525
|
+
While the agent answers, the send button becomes a **Stop** button that
|
|
526
|
+
interrupts the turn (`allowStop`, default `true`; pass `false` to disable it).
|
|
503
527
|
|
|
504
528
|
`ChatWidget` is a high-level component. For a custom interface, use `useChat`.
|
|
505
529
|
|
|
@@ -512,14 +536,16 @@ All-in-one state for a custom chat interface (composes `useMessages` +
|
|
|
512
536
|
import { useChat } from "@useago/sdk/react";
|
|
513
537
|
|
|
514
538
|
function Chat() {
|
|
515
|
-
const { messages, sendMessage, isLoading, error } = useChat();
|
|
539
|
+
const { messages, sendMessage, stop, isLoading, error } = useChat();
|
|
516
540
|
|
|
517
541
|
return (
|
|
518
542
|
<div>
|
|
519
543
|
{messages.map((m) => (
|
|
520
544
|
<p key={m.id}><b>{m.role}:</b> {m.content}</p>
|
|
521
545
|
))}
|
|
522
|
-
|
|
546
|
+
{isLoading
|
|
547
|
+
? <button onClick={() => void stop()}>Stop</button>
|
|
548
|
+
: <button onClick={() => sendMessage("Hello!")}>Send</button>}
|
|
523
549
|
{error && <p role="alert">{error.message}</p>}
|
|
524
550
|
</div>
|
|
525
551
|
);
|
|
@@ -530,6 +556,12 @@ function Chat() {
|
|
|
530
556
|
message is included). `sendMessage(content, files?)` resolves with the final
|
|
531
557
|
message or `null` on error.
|
|
532
558
|
|
|
559
|
+
`stop()` interrupts the answer being generated: the stream closes and the backend
|
|
560
|
+
is told to stop generating, the partial text stays in `messages` with status
|
|
561
|
+
`CANCELED`, and `isLoading` goes back to `false`. It is a no-op when nothing is
|
|
562
|
+
generating. Building your own input? `<ChatInput onStop={...} disabled={isLoading} />`
|
|
563
|
+
renders the same Stop button.
|
|
564
|
+
|
|
533
565
|
Show the knowledge sources the agent retrieved via `m.sources` (an `AgoSource[]`,
|
|
534
566
|
each `{ id, title, url? }`):
|
|
535
567
|
|
|
@@ -634,6 +666,26 @@ useAgoPageState([
|
|
|
634
666
|
]);
|
|
635
667
|
```
|
|
636
668
|
|
|
669
|
+
Add a `data` source to hand the agent the resulting rows as the result of its own
|
|
670
|
+
call (same turn), instead of only as context on the next message:
|
|
671
|
+
|
|
672
|
+
```tsx
|
|
673
|
+
const { data: invoices, isFetching } = useQuery({ queryKey: ["invoices", status], queryFn: fetchInvoices });
|
|
674
|
+
|
|
675
|
+
useAgoPageState(controls, {
|
|
676
|
+
data: {
|
|
677
|
+
description: "The invoices matching the current filters.",
|
|
678
|
+
get: () => invoices ?? [],
|
|
679
|
+
isLoading: () => isFetching, // isFetching, not isLoading: a background refetch must count
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
`setPageState` then returns `{ success, applied, data }` and a read-only
|
|
685
|
+
`readPageData` function is registered too. The SDK waits for `isLoading` to go
|
|
686
|
+
false before reading, and truncates a long list by whole rows. Needs
|
|
687
|
+
`clientFunctionsMode: "pause"` (the default).
|
|
688
|
+
|
|
637
689
|
## 6. Give the agent context: `useAgoContext`
|
|
638
690
|
|
|
639
691
|
Expose what the user is looking at, sent with every message.
|
|
@@ -680,8 +732,9 @@ useEffect(() => {
|
|
|
680
732
|
```
|
|
681
733
|
|
|
682
734
|
Key events: `message:start`, `message:chunk` (`{ content }` per token),
|
|
683
|
-
`message:complete` (`AgoMessage`), `message:
|
|
684
|
-
`
|
|
735
|
+
`message:complete` (`AgoMessage`), `message:stopped`
|
|
736
|
+
(`{ conversationId, messageId }`, after the turn was stopped), `message:error`,
|
|
737
|
+
`toolCall:received`, `function:invoke`, `function:result`.
|
|
685
738
|
|
|
686
739
|
## Exports cheat-sheet (`@useago/sdk/react`)
|
|
687
740
|
|
|
@@ -772,7 +825,7 @@ All-in-one reactive state (messages + conversations). Everything returned is a
|
|
|
772
825
|
import { ref } from "vue";
|
|
773
826
|
import { useChat } from "@useago/sdk/vue";
|
|
774
827
|
|
|
775
|
-
const { messages, sendMessage, isLoading, error } = useChat();
|
|
828
|
+
const { messages, sendMessage, stop, isLoading, error } = useChat();
|
|
776
829
|
const draft = ref("");
|
|
777
830
|
|
|
778
831
|
async function onSend() {
|
|
@@ -789,12 +842,18 @@ async function onSend() {
|
|
|
789
842
|
</div>
|
|
790
843
|
<form @submit.prevent="onSend">
|
|
791
844
|
<input v-model="draft" :disabled="isLoading" placeholder="Ask anything..." />
|
|
792
|
-
<button
|
|
845
|
+
<button v-if="isLoading" type="button" @click="stop()">Stop</button>
|
|
846
|
+
<button v-else>Send</button>
|
|
793
847
|
</form>
|
|
794
848
|
<p v-if="error" role="alert">{{ error.message }}</p>
|
|
795
849
|
</template>
|
|
796
850
|
```
|
|
797
851
|
|
|
852
|
+
`stop()` interrupts the answer being generated: the stream closes and the backend
|
|
853
|
+
is told to stop generating, the partial text stays in `messages` with status
|
|
854
|
+
`CANCELED`, and `isLoading` goes back to `false`. It is a no-op when nothing is
|
|
855
|
+
generating.
|
|
856
|
+
|
|
798
857
|
Each assistant message carries the knowledge sources it used in `m.sources` (an
|
|
799
858
|
`AgoSource[]`, each `{ id, title, url? }`). Render them as links.
|
|
800
859
|
|
|
@@ -886,7 +945,8 @@ useAgoEvents("message:complete", (msg) => console.log("Got reply:", msg.content)
|
|
|
886
945
|
```
|
|
887
946
|
|
|
888
947
|
Key events: `message:start`, `message:chunk` (`{ content }` per token),
|
|
889
|
-
`message:complete` (`AgoMessage`), `message:
|
|
948
|
+
`message:complete` (`AgoMessage`), `message:stopped`
|
|
949
|
+
(`{ conversationId, messageId }`, after the turn was stopped), `message:error`.
|
|
890
950
|
|
|
891
951
|
## 6. Client context
|
|
892
952
|
|
|
@@ -1079,6 +1139,9 @@ use `ago.on(event, handler)` / `ago.off(event, handler)`.
|
|
|
1079
1139
|
|
|
1080
1140
|
```ts
|
|
1081
1141
|
ago.sendMessage(content, options?) // Promise<AgoMessage>
|
|
1142
|
+
ago.stop() // stop the turn being generated
|
|
1143
|
+
ago.stopMessage(messageId) // stop a turn by id
|
|
1144
|
+
ago.isGenerating() // boolean
|
|
1082
1145
|
ago.getConversations() // Promise<Conversation[]>
|
|
1083
1146
|
ago.getConversation(id)
|
|
1084
1147
|
ago.getMessages(conversationId)
|
|
@@ -1100,6 +1163,17 @@ ago.getClient() // underlying AgoClient
|
|
|
1100
1163
|
ago.destroy()
|
|
1101
1164
|
```
|
|
1102
1165
|
|
|
1166
|
+
`stop()` closes the stream and tells the backend to stop generating. The partial
|
|
1167
|
+
text is kept, the message is finalized as `CANCELED`, and the pending
|
|
1168
|
+
`sendMessage` resolves with it (it does not reject). It resolves to `null` when
|
|
1169
|
+
nothing was generating, so a Stop button can call it unconditionally:
|
|
1170
|
+
|
|
1171
|
+
```ts
|
|
1172
|
+
async onStop() {
|
|
1173
|
+
await this.ago.stop();
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1103
1177
|
## 5. Functions, navigation and context
|
|
1104
1178
|
|
|
1105
1179
|
Register a client-side function the agent can call:
|
|
@@ -1384,10 +1458,97 @@ This does two things:
|
|
|
1384
1458
|
current `get()` value, so the agent knows what to change. Controls without a
|
|
1385
1459
|
`get()` are write-only and don't appear in context.
|
|
1386
1460
|
|
|
1387
|
-
|
|
1388
|
-
`{ success, applied }`. The new state reaches the agent through the
|
|
1389
|
-
the next message. React/Vue offer `useAgoPageState(controls, opts?)`
|
|
1390
|
-
lifecycle cleanup; Angular exposes
|
|
1461
|
+
By default it's fire-and-forget: the handler applies each `set()` locally and
|
|
1462
|
+
returns `{ success, applied }`. The new state reaches the agent through the
|
|
1463
|
+
context on the next message. React/Vue offer `useAgoPageState(controls, opts?)`
|
|
1464
|
+
with lifecycle cleanup; Angular exposes
|
|
1465
|
+
`agoService.registerPageStateFunction(...)`.
|
|
1466
|
+
|
|
1467
|
+
### Return what the page displays
|
|
1468
|
+
|
|
1469
|
+
`{ success, applied }` only tells the agent its own arguments were accepted. Ask
|
|
1470
|
+
it to "find the user dupont" and it applies the filter but cannot say who it
|
|
1471
|
+
found: the rows only reach it on the next message, as context.
|
|
1472
|
+
|
|
1473
|
+
Add a `data` source and the rows come back as the result of the agent's own
|
|
1474
|
+
call, in the same turn:
|
|
1475
|
+
|
|
1476
|
+
```ts
|
|
1477
|
+
const { data: users, isFetching } = useQuery({
|
|
1478
|
+
queryKey: ["users", filters],
|
|
1479
|
+
queryFn: fetchUsers,
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
useAgoPageState(controls, {
|
|
1483
|
+
data: {
|
|
1484
|
+
description: "The users matching the current filters.",
|
|
1485
|
+
get: () => users ?? [],
|
|
1486
|
+
isLoading: () => isFetching,
|
|
1487
|
+
},
|
|
1488
|
+
});
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
`setPageState` now returns `{ success, applied, data }`, and a second function
|
|
1492
|
+
`readPageData` is registered: no parameters, changes nothing, returns `{ data }`.
|
|
1493
|
+
That one answers "what's on screen?" without touching the page. Both disappear
|
|
1494
|
+
when you unregister.
|
|
1495
|
+
|
|
1496
|
+
#### Telling the SDK when the work is done
|
|
1497
|
+
|
|
1498
|
+
A control change kicks off work, and the SDK has to wait for it. There are two
|
|
1499
|
+
ways to say when it finished. They are not equivalent.
|
|
1500
|
+
|
|
1501
|
+
**Return a promise from `get()`.** You own the await, so the SDK is exact: it
|
|
1502
|
+
resolves when your work resolves. No polling, no timing assumption. With
|
|
1503
|
+
TanStack Query, `ensureQueryData` hands you that promise and shares the single
|
|
1504
|
+
in-flight request with the UI, so the data is not fetched twice:
|
|
1505
|
+
|
|
1506
|
+
```ts
|
|
1507
|
+
useAgoPageState(controls, {
|
|
1508
|
+
data: {
|
|
1509
|
+
description: "The users matching the current filters.",
|
|
1510
|
+
get: () => queryClient.ensureQueryData({
|
|
1511
|
+
queryKey: ["users", filters],
|
|
1512
|
+
queryFn: fetchUsers,
|
|
1513
|
+
}),
|
|
1514
|
+
},
|
|
1515
|
+
});
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
**Or declare `isLoading`**, for data you cannot get a promise for. The SDK polls
|
|
1519
|
+
it every 50 ms and treats two consecutive `false` readings as settled. That is a
|
|
1520
|
+
heuristic: the flag has to be up within ~100 ms of the change, or the SDK
|
|
1521
|
+
concludes the page settled and reads the previous rows. A query library that
|
|
1522
|
+
flips `isFetching` during render makes it in time; a **debounced** fetch does
|
|
1523
|
+
not, and the agent then answers with the previous search's results, silently.
|
|
1524
|
+
|
|
1525
|
+
If you do use it, pass `isFetching`, not `isLoading`: TanStack Query's
|
|
1526
|
+
`isLoading` is only true on the very first load, so a background refetch would
|
|
1527
|
+
look idle.
|
|
1528
|
+
|
|
1529
|
+
Both forms are bounded by `settleTimeoutMs` (10 s by default). On timeout the
|
|
1530
|
+
SDK returns what the page has rather than holding the turn open. If `get()`
|
|
1531
|
+
rejects, the agent is told the read failed instead of being handed an empty
|
|
1532
|
+
page.
|
|
1533
|
+
|
|
1534
|
+
Two things to keep in mind:
|
|
1535
|
+
|
|
1536
|
+
- **Size.** The snapshot lands in the model's context. Over
|
|
1537
|
+
`maxResultBytes` (the client's `maxFunctionResultBytes`, 50 000 by default)
|
|
1538
|
+
the SDK keeps the first whole items that fit and adds a `truncation` field
|
|
1539
|
+
(`{ truncated, returnedItems, totalItems, hint }`) next to them. The counts
|
|
1540
|
+
are nested rather than merged into your snapshot so they can never overwrite
|
|
1541
|
+
a field of your own called `totalItems`. `success` and `applied` always
|
|
1542
|
+
survive intact. Return the columns the agent needs, not your whole row
|
|
1543
|
+
objects.
|
|
1544
|
+
- **Pause mode only.** The result travels back inside the turn only when
|
|
1545
|
+
`clientFunctionsMode` is `"pause"` (the default). In `"placeholder"` mode the
|
|
1546
|
+
turn is already over by then, so the agent will not see the data during the
|
|
1547
|
+
call that produced it; the SDK warns once if you declare `data` anyway.
|
|
1548
|
+
|
|
1549
|
+
The snapshot deliberately stays out of the `page-state:` context entry, which
|
|
1550
|
+
keeps carrying control values only. Putting 200 rows in every message would cost
|
|
1551
|
+
a lot for nothing: the data travels only when the agent asks for it.
|
|
1391
1552
|
|
|
1392
1553
|
The SDK also tells the agent **what changed**: on each message it diffs the page
|
|
1393
1554
|
state against the previous message and adds a `state:delta` entry listing the
|
|
@@ -1699,6 +1860,7 @@ const final = await client.waitFor("message:complete", { timeout: 10_000 });
|
|
|
1699
1860
|
| `message:chunk` | `{ content, conversationId, messageId }` |
|
|
1700
1861
|
| `message:answer-complete` | `AgoMessage`: the main answer text is done, but follow-up replies may still be pending; fires once, before `message:complete` |
|
|
1701
1862
|
| `message:complete` | `AgoMessage` |
|
|
1863
|
+
| `message:stopped` | `{ conversationId, messageId }`: the user stopped the turn (`client.stop()`). Fires once the stream has unwound, after the `message:complete` that carries the partial answer with status `CANCELED` (or on its own when there was nothing to complete). `messageId` is `""` when the stop landed before the backend had named the message. See [Stop the answer](core.md#stop-the-answer) |
|
|
1702
1864
|
| `message:waiting-client` | `{ conversationId, messageId, waitingToolCallIds }`: the turn paused on client function call(s) (`clientFunctionsMode: "pause"`). `message:complete` does not fire for the paused stream — it fires when the resumed turn concludes. The SDK submits the results and resumes on its own; listen to this to keep a "working…" indicator up |
|
|
1703
1865
|
| `message:empty` | `{ conversationId, messageId }`: the reply completed as `DONE` with no content, tool calls, or follow-ups (usually an unknown `agent` slug). Fires after `message:complete` AND after `sendMessage` resolves, so subscribing right after the `await` still catches it. Both ids are `""` when the stream carried no message data at all. See [Empty replies](configuration.md#empty-replies). |
|
|
1704
1866
|
| `message:error` | `{ error, code?, conversationId?, messageId? }`: `code` is the stable [error code](configuration.md#error-codes) when the failure was an `AgoError` |
|
|
@@ -1779,6 +1941,32 @@ listeners automatically when the loop ends.
|
|
|
1779
1941
|
|
|
1780
1942
|
---
|
|
1781
1943
|
|
|
1944
|
+
## Stopping a stream
|
|
1945
|
+
|
|
1946
|
+
`client.stop()` interrupts the turn being generated: it closes the stream and
|
|
1947
|
+
calls the backend to stop generating. Closing the stream alone would leave the
|
|
1948
|
+
agent running server-side.
|
|
1949
|
+
|
|
1950
|
+
```ts
|
|
1951
|
+
document.getElementById("stop").onclick = () => void client.stop();
|
|
1952
|
+
|
|
1953
|
+
client.on("message:stopped", ({ messageId }) => {
|
|
1954
|
+
console.log("stopped", messageId);
|
|
1955
|
+
});
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
The text that already arrived stays: the message is finalized as `CANCELED` and
|
|
1959
|
+
the pending `sendMessage` resolves with it instead of rejecting.
|
|
1960
|
+
`message:complete` fires with the partial message, then `message:stopped`.
|
|
1961
|
+
|
|
1962
|
+
Finalize your UI on `message:stopped`. It is the one event that always fires,
|
|
1963
|
+
while `message:complete` is skipped when there is nothing to complete: a stop
|
|
1964
|
+
that landed before the backend had named the message, and a turn stopped while
|
|
1965
|
+
it was paused on client functions (no stream is open then). Full details in
|
|
1966
|
+
[Stop the answer](core.md#stop-the-answer).
|
|
1967
|
+
|
|
1968
|
+
---
|
|
1969
|
+
|
|
1782
1970
|
## Tool calls
|
|
1783
1971
|
|
|
1784
1972
|
When the agent needs structured input (a form), a confirmation, or shows a
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
var X=10,T=1e3;function Y(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function b(e,t){if(typeof e=="string"){if(e.length<=1e3)return e;const i=e.length-T;return e.slice(0,T)+`…[truncated ${i} chars]`}if(Array.isArray(e)){if(t.has(e))return"…[circular]";t.add(e);const i=e.slice(0,50).map(s=>b(s,t));return e.length>50&&i.push(`…[+${e.length-50} more]`),t.delete(e),i}if(Y(e)){if(t.has(e))return"…[circular]";t.add(e);const i={};for(const[s,n]of Object.entries(e))i[s]=b(n,t);return t.delete(e),i}return e}function J(e){return b(e,new WeakSet)}var D=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:J(e.data)}:{}};this.entries.push(t);const i=this.entries.length-this.maxEntries;return i>0&&this.entries.splice(0,i),t}getRecent(){return[...this.entries]}get size(){return this.entries.length}clear(){this.entries=[]}},d=class extends Error{constructor(e,t,i){super(e),this.code=t,this.statusCode=i,this.name="AgoError"}},p=class U extends d{constructor(t,i,s,n,o,a){super(t,i,s),this.type=n,this.param=o,this.docUrl=a,this.name="AgoApiError"}static fromResponse(t,i){const s=t.error;return new U(s.doc_url?`${s.message} See ${s.doc_url}`:s.message,s.code,i,s.type,s.param,s.doc_url)}},y=class extends d{constructor(e,t){super(e,"network_error"),this.originalError=t,this.name="AgoNetworkError"}},A=class extends d{constructor(e,t="stream_error"){super(e,t),this.name="AgoStreamError"}},E=class extends d{constructor(e,t,i){super(e,"function_error"),this.functionName=t,this.originalError=i,this.name="AgoFunctionError"}},V=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)}},c=new V,F="ago_widget_id";function Q(){if(typeof localStorage<"u"){const t=localStorage.getItem(F);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(F,e)}catch{}return e}var Z=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.headers={"X-User-Anon-Id":e.widgetId||Q()},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,i){return this.request("POST",e,t,i)}async postStream(e,t){const i=`${this.baseUrl}${e}`;c.debug("POST (stream)",i,t);try{const s=await fetch(i,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0});return s.ok||await this.handleErrorResponse(s),s}catch(s){throw s instanceof p?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 postFormData(e,t){const i=`${this.baseUrl}${e}`;c.debug("POST (formData)",i);const s={...this.headers};try{const n=await fetch(i,{method:"POST",headers:s,body:t});return n.ok||await this.handleErrorResponse(n),n}catch(n){throw n instanceof p?n:new y(`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 request(e,t,i,s){const n=`${this.baseUrl}${t}`;c.debug(e,n,i);try{const o=await fetch(n,{method:e,headers:{...this.headers,"Content-Type":"application/json"},body:i?JSON.stringify(i):void 0,...s?.keepalive?{keepalive:!0}:{}});return o.ok||await this.handleErrorResponse(o),o.status===204?void 0:o.json()}catch(o){throw o instanceof p?o:new y(`Network error: ${o instanceof Error?o.message:"Unknown error"}. Check that \`baseUrl\` is reachable and includes the protocol (https://).`,o instanceof Error?o:void 0)}}async handleErrorResponse(e){let t;try{t=await e.json()}catch{}if(t?.error)throw p.fromResponse(t,e.status);let i="";throw e.status===401||e.status===403?i=" Check `userJwt` (and that it has not expired).":e.status===404&&(i=" Check that `baseUrl` points at your AGO API root."),new p(`HTTP ${e.status}: ${e.statusText}.${i}`,"http_error",e.status,"api_error")}},ee=5e4,te=2e4,ie=2e3,M=class{constructor(e){this.functions=new Map,this.defaultMaxResultBytes=e?.maxResultBytes??ee}setDefaultMaxResultBytes(e){this.defaultMaxResultBytes=e}register(e,t,i){if(typeof e=="object"){const{name:r,handler:l,description:u,parameters:h,maxResultBytes:g,requiresApproval:m}=e;return this.register(r,l,{description:u,parameters:h,maxResultBytes:g,requiresApproval:m})}const s=e;if(!t||!i)throw new d(`registerFunction("${s}"): 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(s)&&c.warn(`Function "${s}" is being overwritten`);const{maxResultBytes:n,requiresApproval:o,...a}=i;this.functions.set(s,{schema:{...a,name:s},handler:t,maxResultBytes:n,requiresApproval:o}),c.log(`Registered function: ${s}`)}unregister(e){const t=this.functions.delete(e);return t&&c.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 i=this.functions.get(e);if(!i)throw new E(`Function "${e}" is not registered`,e);c.log(`Executing function: ${e}`,t);try{const s=await i.handler(t);return c.log(`Function ${e} completed:`,s),this.guardResultSize(e,s,i.maxResultBytes)}catch(s){throw c.error(`Function ${e} failed:`,s),new E(`Function "${e}" execution failed: ${s instanceof Error?s.message:"Unknown error"}`,e,s instanceof Error?s:void 0)}}guardResultSize(e,t,i){let s;try{s=JSON.stringify(t)}catch{return t}if(s===void 0)return t;const n=new TextEncoder().encode(s).length,o=i??this.defaultMaxResultBytes;return n>o?(c.warn(`Function "${e}" result is ${n} 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:n,maxBytes:o,preview:s.slice(0,ie),hint:`The result of "${e}" 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.`}):(n>te&&c.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(),c.log("Cleared all registered functions")}get size(){return this.functions.size}},se=250,ne=3,re=1e3,oe=24,ae=10,ce=50,le=["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??se,this.rageClickCount=e.rageClickCount??ne,this.rageClickWindowMs=e.rageClickWindowMs??re,this.rageClickRadiusPx=e.rageClickRadiusPx??oe,this.maxRecentRoutes=e.maxRecentRoutes??ae,this.maxSignals=e.maxSignals??ce}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=(s,n,o)=>{s.addEventListener(n,o,{passive:!0}),this.teardowns.push(()=>s.removeEventListener(n,o))},i=()=>this.recordActivity();for(const s of le)t(window,s,i);t(document,"visibilitychange",()=>{document.visibilityState==="visible"&&this.recordActivity()}),t(window,"popstate",()=>this.checkRoute()),t(window,"hashchange",()=>this.checkRoute()),t(window,"click",s=>this.onClick(s)),c.log("SignalCollector started")}destroy(){for(const e of this.teardowns)e();this.teardowns=[],this.listeners.clear(),this.recentClicks=[],this.started=!1,c.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 c.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(),i=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:i}});const s=this.recentRoutes,n=s.length;n>=4&&s[n-1]===s[n-3]&&s[n-2]===s[n-4]&&s[n-1]!==s[n-2]&&(this.routeBounces+=1,this.pushSignal({type:"route_bounce",route:e,at:t,count:this.routeBounces,meta:{between:`${s[n-2]}↔${s[n-1]}`}}))}onClick(e){this.recordActivity();const t=Date.now(),i=this.pageStateKey(),s={at:t,x:e.clientX,y:e.clientY,stateKey:i};this.recentClicks=this.recentClicks.filter(r=>t-r.at<=this.rageClickWindowMs);const n=this.recentClicks[0],o=!n||Math.abs(s.x-n.x)<=this.rageClickRadiusPx&&Math.abs(s.y-n.y)<=this.rageClickRadiusPx,a=!n||n.stateKey===i;if(!o||!a){this.recentClicks=[s];return}this.recentClicks.push(s),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(i){c.error("Signal listener threw:",i)}}};function L(e,t){const i=t?t.storage??(typeof localStorage<"u"?localStorage:void 0):void 0;let s=e;if(t&&i)try{const r=i.getItem(t.key);r!=null&&(s=JSON.parse(r))}catch{}const n=new Set,o=()=>n.forEach(r=>r(s));function a(){if(t&&i)try{i.setItem(t.key,JSON.stringify(s))}catch{}}return{get:()=>s,set:r=>{s=r,a(),o()},subscribe:r=>(n.add(r),()=>{n.delete(r)})}}var ue=6e4,he=1440*60*1e3,de="ago_proactive";function C(e){const t=new Date(e),i=String(t.getMonth()+1).padStart(2,"0"),s=String(t.getDate()).padStart(2,"0");return`${t.getFullYear()}-${i}-${s}`}var ge=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=L({optOut:!1,lastFiredAt:{},dismissedAt:{},day:{date:C(Date.now()),count:0}},{key:e.storageKey??de,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 i=this.store.get();if(i.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 s=i.lastFiredAt[e.id],n=e.cooldownMs??ue;if(s!==void 0&&t-s<n)return this.deny(e,"cooldown");const o=i.dismissedAt[e.id],a=e.resurfaceAfterMs??he;return o!==void 0&&t-o<a?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 i=this.store.get();this.store.set({...i,lastFiredAt:{...i.lastFiredAt,[e]:t}})}recordNudgeShown(e=Date.now()){this.sessionCount+=1;const t=this.store.get(),i=C(e),s=t.day.date===i?{date:i,count:t.day.count+1}:{date:i,count:1};this.store.set({...t,day:s})}recordDismissed(e,t=Date.now()){const i=this.store.get();this.store.set({...i,dismissedAt:{...i.dismissedAt,[e]:t}})}dayCount(e){const t=this.store.get();return t.day.date===C(e)?t.day.count:0}deny(e,t){return c.debug(`NudgeGovernor: trigger "${e.id}" blocked — ${t}`),!1}};function P(){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 fe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function B(e,t){if(e==="*"||e===t)return!0;const i=e.split("/"),s=i.map((n,o)=>{const a=o===i.length-1;return n==="*"?a?"[^/]+(?:/[^/]+)*":"[^/]+":n.startsWith(":")?"[^/]+":fe(n)});return new RegExp("^"+s.join("/")+"/?$").test(t)}function x(e,t){return e===t?!0:e===null||t===null?!1:Array.isArray(e)?!Array.isArray(t)||t.length!==e.length?!1:e.every((i,s)=>_(i,t[s])):typeof e=="object"?typeof t!="object"||Array.isArray(t)?!1:Object.entries(e).every(([i,s])=>x(s,t[i])):!1}function _(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((i,s)=>_(i,t[s]));if(typeof e=="object"){const i=Object.keys(e),s=Object.keys(t);return i.length!==s.length?!1:i.every(n=>_(e[n],t[n]))}return!1}function G(e,t){if(e.route!==void 0&&!B(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[i,s]of Object.entries(e.fieldErrors))if((t.fieldErrors[i]??0)<s)return!1}return!(e.pageState!==void 0&&!x(e.pageState,t.pageState??{}))}var pe=1e3,me="/api/sdk/v1/config",ye="/api/sdk/v1/proactive/evaluate",ve=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??pe}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),i=()=>this.governor.setTurnInFlight(!1),s=n=>{n?.id&&(this.lastConversationId=n.id)};this.client.on("message:start",e),this.client.on("message:complete",t),this.client.on("message:error",i),this.client.on("conversation:loaded",s),this.teardowns.push(()=>{this.client.off("message:start",e),this.client.off("message:complete",t),this.client.off("message:error",i),this.client.off("conversation:loaded",s)}),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(me);this.governor.setKillSwitch(e?.proactive?.enabled===!0)}catch(e){c.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(G(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:P(),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=P(),i={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 s;try{s=await this.client.getHttp().post(ye,i)}catch(n){c.debug("Proactive evaluate failed (treated as no-intervene):",n),this.governor.releaseSlot();return}if(!s?.intervene||!s.message){this.governor.releaseSlot();return}this.setReady({id:t,triggerId:e.id,source:"agent",message:s.message,action:we(s.action),goal:e.goal,kickoff:e.kickoff,createdAt:new Date,serverNudgeId:s.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(Ce(e),{hidden:!0,conversationId:this.lastConversationId}).catch(t=>c.debug("Nudge kickoff message failed:",t)):e.kickoff?this.client.sendMessage(e.kickoff).catch(t=>c.debug("Nudge kickoff message failed:",t)):c.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){c.warn("Nudge action failed:",t)}}};function we(e){if(e)return{label:e.label,functionName:e.function_name,arguments:e.arguments}}function Ce(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 Se=5e3,be="/api/sdk/v1/proactive/events",Ae=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??Se,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 i={events:t.map(s=>({client_nudge_id:s.clientNudgeId,trigger_id:s.triggerId,source:s.source,type:s.type,...s.message!==void 0?{message:s.message}:{},...this.agent?{agent:this.agent}:{},occurred_at:new Date(s.occurredAt).toISOString()}))};this.post(be,i,e).catch(s=>{c.debug("Proactive event batch failed:",s)})}destroy(){if(!this.destroyed){this.flush({keepalive:!0}),this.destroyed=!0;for(const e of this.teardowns)e();this.teardowns=[]}}},O="friction_signals";function k(e,t){e.proactive&&(c.warn("createAgoProactive: a proactive controller is already attached; replacing it."),e.proactive.destroy());const i=new j({...t.signals,getPageState:()=>{const r=e.getContextSnapshot();if(!r)return null;const l={};for(const[u,h]of Object.entries(r.entries))u.startsWith("page-state:")&&h.data&&Object.assign(l,h.data);return Object.keys(l).length>0?l:null}});i.start(),e.addDynamicContext(O,()=>{const{pageState:r,...l}=i.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:l}});const s=new ge({frequencyCap:t.frequencyCap,storage:t.storage,storageKey:t.storageKey}),n=new Ae({post:(r,l,u)=>e.getHttp().post(r,l,u),agent:e.getConfiguredAgent()}),o=new ve({client:e,collector:i,governor:s,tracker:n,options:t});o.start();const a={optOut:()=>s.optOut(),optIn:()=>s.optIn(),isOptedOut:()=>s.isOptedOut(),setEngaged:r=>s.setEngaged(r),signalFieldError:r=>i.signalFieldError(r),getSignalsSnapshot:()=>i.getSnapshot(),getActiveNudge:()=>o.getActiveNudge(),shown:r=>o.shown(r),accept:r=>o.accept(r),dismiss:r=>o.dismiss(r),markConverted:r=>o.markConverted(r),destroy:()=>{o.stop(),n.destroy(),i.destroy(),e.removeDynamicContext(O),e.proactive===a&&(e.proactive=null)}};return e.proactive=a,a}var W=class{constructor(){this.entries=new Map,this.dynamicProviders=new Map}set(e,t){this.entries.set(e,t),c.log(`ClientContext set: ${e}`)}remove(e){const t=this.entries.delete(e);return t&&c.log(`ClientContext removed: ${e}`),t}addDynamicProvider(e,t){this.dynamicProviders.set(e,t),c.log(`DynamicContext provider added: ${e}`)}removeDynamicProvider(e){const t=this.dynamicProviders.delete(e);return t&&c.log(`DynamicContext provider removed: ${e}`),t}getSnapshot(){if(this.entries.size===0&&this.dynamicProviders.size===0)return null;const e={};for(const[t,i]of this.entries)e[t]=i;for(const[t,i]of this.dynamicProviders)try{const s=i();s&&(e[t]=s)}catch(s){c.error(`DynamicContext provider "${t}" threw:`,s)}return Object.keys(e).length===0?null:{entries:e}}clear(){this.entries.clear(),this.dynamicProviders.clear(),c.log("ClientContext cleared")}},Ee="page-state:";function _e(e){const t={};for(const[i,s]of Object.entries(e))i.startsWith(Ee)&&s.data&&Object.assign(t,s.data);return t}function ke(e){const t=e["current-page"]?.data;return t?.page?t.page:t?.url?t.url:e["browser-page"]?.data?.url}function Re(e,t){return e===t?!0:JSON.stringify(e)===JSON.stringify(t)}function Ie(e,t){const i=[];for(const s of Object.keys(t))s in e&&(Re(e[s],t[s])||i.push({field:s,from:e[s],to:t[s]}));return i}function xe(e,t){const i=_e(t),s=ke(t);return{changes:e&&e.page===s?Ie(e.state,i):[],baseline:{page:s,state:i}}}function R(e){return e===null||typeof e!="object"?JSON.stringify(e)??"null":Array.isArray(e)?`[${e.map(R).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${R(e[t])}`).join(",")}}`}var K=class{constructor(e){this.callbacks=e,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(e){if(!e.body)throw new A("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(),i=new TextDecoder;try{for(;;){const{done:s,value:n}=await t.read();if(s)break;const o=i.decode(n,{stream:!0});this.buffer+=o,this.processBuffer()}return this.buffer.trim()&&this.processBuffer(),this.buildFinalMessage()}catch(s){const n=s instanceof Error?s:new A("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 i=t.split(`
|
|
4
|
-
`);for(const s of i)if(!s.startsWith(": ")&&s.startsWith("data: ")){const n=s.slice(6);try{const o=JSON.parse(n);this.handleChunk(o)}catch(o){c.warn("Failed to parse SSE data:",n,o)}}}}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 i=t.knowledge_document;return{id:i.id,title:i.title,url:i.use_external_link?i.external_link_url:i.internal_link_url}})),e.type==="client_function"&&e.function_name){const t=`${e.function_name}::${R(e.arguments??{})}`;this.firedClientFunctions.has(t)||(this.firedClientFunctions.add(t),this.callbacks.onClientFunction?.({invocationId:e.id||"",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 i=this.toolCalls.findIndex(s=>s.id===t.id);i>=0?this.toolCalls[i]=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 Te(e){const t=e.message.toLowerCase();return t.includes("load failed")||t.includes("failed to fetch")||t.includes("network")||t.includes("abort")}var Fe=new Set(["image/jpeg","image/png","image/gif","image/webp","image/bmp","image/tiff"]);function q(e){if(!e)return;const t=typeof window<"u"&&window.location?window.location.href:"https://ago.local/";try{const i=new URL(e,t);if(i.protocol==="http:"||i.protocol==="https:"||i.protocol==="blob:")return e}catch{}}function Pe(e){return e.isSafeImage===!0&&typeof e.contentType=="string"&&e.contentType.startsWith("image/")&&q(e.url)!==void 0}function Oe(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 $e(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 Ne(e){return e.map((t,i)=>({id:`local-${i}-${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:Fe.has(t.type)}))}var H=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 i=this.handlers.get(e);i&&i.delete(t)}emit(e,t){const i=this.handlers.get(e);i&&i.forEach(s=>{try{s(t)}catch(n){console.error(`Error in event handler for ${String(e)}:`,n)}})}once(e,t){const i=(s=>{this.off(e,i),t(s)});this.on(e,i)}waitFor(e,t){return new Promise((i,s)=>{let n;const o=(a=>{n&&clearTimeout(n),i(a)});this.once(e,o),t?.timeout&&(n=setTimeout(()=>{this.off(e,o),s(new Error(`waitFor("${String(e)}") timed out after ${t.timeout}ms`))},t.timeout))})}removeAllListeners(e){e?this.handlers.delete(e):this.handlers.clear()}},$='`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).',De=/^[a-z][a-z0-9+.-]*:\/\//i,N=!1;function I(e,t="AgoClient"){if(typeof e!="object"||e===null)throw new d(`${t}: ${$}`,"config_missing_base_url");const i=e.baseUrl;if(typeof i!="string"||i.trim()==="")throw new d(`${t}: ${$}`,"config_missing_base_url");!i.startsWith("/")&&!De.test(i)&&(N||(N=!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 Ue(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function S(e){return e.split("/").filter(t=>t.startsWith(":")).map(t=>t.slice(1))}function Me(e,t){const i=[];return{path:e.split("/").map(s=>{if(!s.startsWith(":"))return s;const n=s.slice(1),o=t[n];return o==null||o===""?(i.push(n),s):encodeURIComponent(String(o))}).join("/"),missing:i}}function je(e,t){const i=t.find(n=>n.path===e);if(i)return i;const s=t.find(n=>{if(!n.path.includes(":"))return!1;const o="^"+n.path.split("/").map(a=>a.startsWith(":")?"[^/]+":Ue(a)).join("/")+"/?$";return new RegExp(o).test(e)});return s||t.filter(n=>n.path!=="/"&&e.startsWith(n.path)).sort((n,o)=>o.path.length-n.path.length)[0]}var Le="activity:recent",Be="state:delta",Ge=class z{constructor(t){this.lastSentPageState=null,this.warnedEmptyConversations=new Set,this.pendingResumes=new Map,this.pendingApprovals=new Map,this.resumeGate=null,this.proactive=null,I(t,"AgoClient"),this.config=t,this.activityLedger=new D(t.maxActivityEntries),this.httpClient=new Z(t),this.functionRegistry=new M({maxResultBytes:t.maxFunctionResultBytes}),this.contextRegistry=new W,this.eventEmitter=new H,this.registerActivityContext(),t.debug&&c.enable(),t.proactive&&k(this,t.proactive),c.log("AgoClient initialized")}async sendMessage(t,i){const s=this.functionRegistry.getSchemas(),n=this.buildSendContext(),o=this.config.agent||this.config.defaultAgentId,a=this.resolveClientFunctionsMode(i),r={content:t,conversation_id:i?.conversationId,agent_id:i?.agentId||o};s.length>0&&(r.client_functions=s,a==="pause"&&(r.client_functions_mode=a)),n&&(r.client_context=n),i?.hidden&&(r.hidden=!0);let l;if(i?.files&&i.files.length>0){const u=new FormData;u.append("content",t),i.conversationId&&u.append("conversation_id",i.conversationId),(i.agentId||o)&&u.append("agent_id",i.agentId||o||""),s.length>0&&(u.append("client_functions",JSON.stringify(s)),a==="pause"&&u.append("client_functions_mode",a)),n&&u.append("client_context",JSON.stringify(n)),i.hidden&&u.append("hidden","true");for(const h of i.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(t){let i=!1;const s=await new K({onRawChunk:n=>{this.eventEmitter.emit("stream:message",n)},onStart:n=>{this.eventEmitter.emit("message:start",n)},onChunk:n=>{this.eventEmitter.emit("message:chunk",n)},onToolCall:n=>{this.eventEmitter.emit("toolCall:received",n),n.type==="form"&&this.eventEmitter.emit("toolCall:form",n)},onClientFunction:async n=>{if(i=!0,this.eventEmitter.emit("function:invoke",n),this.recordAgentAction(n),n.invocationId&&this.requiresApproval(n)){this.pendingApprovals.set(n.invocationId,n),this.eventEmitter.emit("function:awaiting-approval",n);return}await this.handleClientFunctionInvocation(n)},onTitle:n=>{this.eventEmitter.emit("conversation:title",n)},onAnswerComplete:n=>{this.eventEmitter.emit("message:answer-complete",n)},onWaitingClient:n=>{this.eventEmitter.emit("message:waiting-client",n),this.markResumeSignal(n.messageId,n.conversationId,"waitingConfirmed")},onComplete:n=>{this.eventEmitter.emit("message:complete",n)},onError:n=>{this.eventEmitter.emit("message:error",{error:n.message,code:n instanceof d?n.code:void 0})}}).processStream(t);return s.status!=="WAITING_CLIENT"&&this.maybeFlagEmptyReply(s,i),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 i=this.functionRegistry.getSchemas(),s=this.buildSendContext(),n={};i.length>0&&(n.client_functions=i),s&&(n.client_context=s);const o=await this.httpClient.postStream(`/api/sdk/v1/messages/${t}/continue`,n);return this.processSSEResponse(o)}markResumeSignal(t,i,s){if(!t)return;const n=this.pendingResumes.get(t)??{conversationId:i,waitingConfirmed:!1,resultsReady:!1,started:!1};n[s]=!0,i&&(n.conversationId=i),this.pendingResumes.set(t,n),!(!n.waitingConfirmed||!n.resultsReady||n.started)&&(n.started=!0,this.runAutoResume(t,n.conversationId))}async runAutoResume(t,i){try{this.resumeGate&&await this.resumeGate({conversationId:i,messageId:t})}catch(s){c.warn("Resume gate threw; resuming anyway:",s)}this.pendingResumes.delete(t);try{await this.continueMessage(t)}catch(s){c.warn("Auto-resume of paused turn failed:",s)}}maybeFlagEmptyReply(t,i){t.status==="DONE"&&(t.content??"").trim()===""&&!i&&!(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 s=t.conversationId||"(no conversation)";if(this.warnedEmptyConversations.has(s))return;this.warnedEmptyConversations.add(s);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 i,s;try{i=await this.functionRegistry.execute(t.functionName,t.arguments)}catch(n){s=n instanceof Error?n.message:"Unknown error",c.error("Client function execution failed:",n)}if(t.invocationId)try{const n=await this.submitToolCallForm(t.invocationId,{result:i,error:s,_type:"client_function_result"});n?.resume?.ready&&this.markResumeSignal(n.resume.message_id,t.conversationId,"resultsReady")}catch(n){c.error("Failed to submit function result:",n)}this.eventEmitter.emit("function:result",{invocationId:t.invocationId,result:i,error:s})}requiresApproval(t){const i=this.config.approvalPolicy;if(i)try{if(i(t))return!0}catch(s){c.warn("approvalPolicy threw; not gating this call:",s)}return this.functionRegistry.get(t.functionName)?.requiresApproval===!0}async approveFunction(t){const i=this.pendingApprovals.get(t);i&&(this.pendingApprovals.delete(t),await this.handleClientFunctionInvocation(i))}async rejectFunction(t){const i=this.pendingApprovals.get(t);if(!i)return;this.pendingApprovals.delete(t);const s={approved:!1,reason:"user_rejected"};try{const n=await this.submitToolCallForm(t,{result:s,_type:"client_function_result"});n?.resume?.ready&&this.markResumeSignal(n.resume.message_id,i.conversationId,"resultsReady")}catch(n){c.error("Failed to submit function rejection:",n)}this.eventEmitter.emit("function:result",{invocationId:t,result:s})}async getConversations(t){const i=new URLSearchParams;t?.page!==void 0&&i.set("page",String(t.page)),t?.pageSize!==void 0&&i.set("page_size",String(t.pageSize));const s=i.toString(),n=await this.httpClient.get(`/api/sdk/v1/conversations${s?`?${s}`:""}`),o=(Array.isArray(n.data)?n.data:[]).map(a=>({id:String(a.id),title:a.title??"",lastMessageDate:new Date(a.last_message_at??a.created_at??0)}));return{data:o,hasMore:n.has_more??!1,total:n.total??o.length}}async getConversation(t){const i=await this.httpClient.get(`/api/sdk/v1/conversations/${t}`),s={id:i.id,title:i.title,lastMessageDate:new Date(i.last_message_at??i.created_at??0),messages:i.messages.map(n=>({id:n.id,conversationId:i.id,content:n.content,role:n.role,status:n.status,createdAt:new Date(n.created_at),hidden:n.hidden,toolCalls:z.mapPersistedToolCalls(n.tool_call_data),attachments:n.attachments&&n.attachments.length>0?n.attachments.map($e):void 0}))};return this.eventEmitter.emit("conversation:loaded",s),s}static mapPersistedToolCalls(t){if(!(!t||t.length===0))return t.map(i=>({id:String(i.id??""),type:i.type??"status_message",status:String(i.status??"unknown"),toolName:i.tool_name??"",toolDisplayName:i.tool_display_name,message:i.message,formSchema:i.form_schema,data:i.data,functionName:i.function_name,arguments:i.arguments}))}async getMessages(t){return(await this.getConversation(t)).messages||[]}async resumePendingClientFunctions(t){const i=[...t.messages??[]].reverse().find(r=>r.role==="assistant"&&r.status==="WAITING_CLIENT");if(!i)return null;const s=(i.toolCalls??[]).filter(r=>r.type==="client_function"&&r.status==="waiting_input"&&r.functionName),n=new Set(this.functionRegistry.getSchemas().map(r=>r.name)),o=s.filter(r=>!n.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=s.length===0;for(const r of s){const l={invocationId:r.id,functionName:r.functionName,arguments:r.arguments??{},conversationId:t.id};if(this.requiresApproval(l)){this.pendingApprovals.set(r.id,l),this.markResumeSignal(i.id,t.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(g){h=g instanceof Error?g.message:"Unknown error",c.error("Client function execution failed:",g)}a=(await this.submitToolCallForm(r.id,{result:u,error:h,_type:"client_function_result"}))?.resume?.ready??a}return a?this.continueMessage(i.id):null}async submitToolCallForm(t,i){return this.httpClient.post(`/api/sdk/v1/tool-calls/${t}/submit`,{formData:i})}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 i=await this.httpClient.get(`/api/sdk/v1/forms/${encodeURIComponent(t)}`);return{name:i.name,description:i.description,schema:i.schema,submit:i.submit??void 0}}async submitFormCollector(t,i){return this.httpClient.post("/api/sdk/v1/forms/submit",{name:t,values:i})}async submitFeedback(t,i){await this.httpClient.post(`/api/sdk/v1/messages/${t}/feedback`,{rating:i})}registerFunction(t,i,s){typeof t=="object"?this.functionRegistry.register(t):this.functionRegistry.register(t,i,s)}register(t){if(Array.isArray(t))for(const i of t)this.functionRegistry.register(i);else this.functionRegistry.register(t)}unregisterFunction(t){return this.functionRegistry.unregister(t)}getRegisteredFunctions(){return this.functionRegistry.getSchemas()}async executeClientFunction(t,i){return this.functionRegistry.execute(t,i)}registerNavigationFunction(t,i){const s=i.map(r=>r.name),n=i.map(r=>{const l=S(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 i)for(const l of S(r.path))o.set(l,[...o.get(l)??[],r.name]);const a={page:{type:"string",description:"The page to navigate to",enum:s}};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=i.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 g={};for(const f of S(u.path))g[f]=r[f]??h[f];const{path:m,missing:v}=Me(u.path,g);if(v.length>0){const f=v.map(w=>`"${w}": "..."`).join(", ");return{success:!1,error:`Page "${l}" needs ${v.map(w=>`"${w}"`).join(", ")}. Retry with { "page": "${l}", ${f} }.`}}return t(m),{success:!0,navigatedTo:m}},{description:`Navigate the user to a page in the application. Available pages:
|
|
6
|
-
${n}`,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=je(window.location.pathname,i),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(t,i){const s=i?.functionName??"setPageState",n=new Map(t.map(a=>[a.name,a])),o=t.map(a=>`- "${a.name}": ${a.description}`).join(`
|
|
7
|
-
`);this.registerFunction(s,async a=>{const r={};for(const[l,u]of Object.entries(a)){if(u===void 0)continue;const h=n.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(t.map(a=>[a.name,{...a.schema,description:a.description}]))},requiresApproval:i?.requiresApproval}),this.addDynamicContext(`page-state:${s}`,()=>{const a=t.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(t){const i=t??"setPageState";this.unregisterFunction(i),this.removeDynamicContext(`page-state:${i}`)}setContext(t,i){this.contextRegistry.set(t,i),this.notifyContextChanged()}removeContext(t){const i=this.contextRegistry.remove(t);return i&&this.notifyContextChanged(),i}addDynamicContext(t,i){this.contextRegistry.addDynamicProvider(t,i),this.notifyContextChanged()}removeDynamicContext(t){const i=this.contextRegistry.removeDynamicProvider(t);return i&&this.notifyContextChanged(),i}notifyContextChanged(){this.eventEmitter.emit("context:changed",this.contextRegistry.getSnapshot())}emitFormEvent(t,i){this.eventEmitter.emit(t,i)}emitProactiveEvent(t,i){this.eventEmitter.emit(t,i)}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,i=typeof document<"u"?document.title:void 0;return!t&&!i?null:{name:"Browser page",description:"Current page the user is viewing",data:{url:t,title:i}}})}getContextSnapshot(){return this.contextRegistry.getSnapshot()}buildSendContext(){const t=this.contextRegistry.getSnapshot(),i=t?.entries??{},{changes:s,baseline:n}=xe(this.lastSentPageState,i);return this.lastSentPageState=n,s.length===0?t:{entries:{...i,[Be]:{name:"State changes",description:"Fields in the page state that changed since your last message, as field: <old> -> <new>.",data:{changes:s}}}}}recordActivity(t){this.addActivity(t)}getRecentActivity(){return this.activityLedger.getRecent()}clearActivity(){this.activityLedger.clear()}addActivity(t){const i=this.activityLedger.add(t);this.eventEmitter.emit("activity:recorded",i)}recordAgentAction(t){const i=t.functionName,s=t.arguments;let n,o;if(i==="navigateToPage"){const a=s?.page;n="agent.navigate",o=a?`Agent navigated to "${String(a)}"`:"Agent navigated"}else i==="setPageState"?(n="agent.page_state",o="Agent changed the page state"):(n=`agent.${i}`,o=`Agent ran "${i}"`);this.addActivity({actor:"agent",name:n,summary:o,...s&&Object.keys(s).length>0?{data:{arguments:s}}:{}})}registerActivityContext(){this.contextRegistry.addDynamicProvider(Le,()=>{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,i){this.eventEmitter.on(t,i)}off(t,i){this.eventEmitter.off(t,i)}once(t,i){this.eventEmitter.once(t,i)}waitFor(t,i){return this.eventEmitter.waitFor(t,i)}updateConfig(t){const i={};for(const[n,o]of Object.entries(t))o!==void 0&&(i[n]=o);const s={...this.config,...i};I(s,"AgoClient.updateConfig"),this.config=s,this.httpClient.updateConfig(i),i.debug!==void 0&&(i.debug?c.enable():c.disable()),i.maxFunctionResultBytes!==void 0&&this.functionRegistry.setDefaultMaxResultBytes(i.maxFunctionResultBytes)}destroy(){this.proactive?.destroy(),this.proactive=null,this.eventEmitter.removeAllListeners(),this.functionRegistry.clear(),this.contextRegistry.clear(),this.pendingResumes.clear(),this.pendingApprovals.clear(),this.resumeGate=null,c.log("AgoClient destroyed")}reviveAfterDestroy(){this.registerActivityContext(),this.config.proactive&&!this.proactive&&k(this,this.config.proactive)}};Object.defineProperty(exports,"ActivityLedger",{enumerable:!0,get:function(){return D}});Object.defineProperty(exports,"AgoApiError",{enumerable:!0,get:function(){return p}});Object.defineProperty(exports,"AgoClient",{enumerable:!0,get:function(){return Ge}});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 A}});Object.defineProperty(exports,"ClientContextRegistry",{enumerable:!0,get:function(){return W}});Object.defineProperty(exports,"DEFAULT_MAX_ACTIVITY_ENTRIES",{enumerable:!0,get:function(){return X}});Object.defineProperty(exports,"EventEmitter",{enumerable:!0,get:function(){return H}});Object.defineProperty(exports,"FunctionRegistry",{enumerable:!0,get:function(){return M}});Object.defineProperty(exports,"SSEHandler",{enumerable:!0,get:function(){return K}});Object.defineProperty(exports,"SignalCollector",{enumerable:!0,get:function(){return j}});Object.defineProperty(exports,"attachmentsFromFiles",{enumerable:!0,get:function(){return Ne}});Object.defineProperty(exports,"canInlineImage",{enumerable:!0,get:function(){return Pe}});Object.defineProperty(exports,"createAgoProactive",{enumerable:!0,get:function(){return k}});Object.defineProperty(exports,"createStore",{enumerable:!0,get:function(){return L}});Object.defineProperty(exports,"formatFileSize",{enumerable:!0,get:function(){return Oe}});Object.defineProperty(exports,"isStreamNetworkError",{enumerable:!0,get:function(){return Te}});Object.defineProperty(exports,"logger",{enumerable:!0,get:function(){return c}});Object.defineProperty(exports,"matchesRoutePattern",{enumerable:!0,get:function(){return B}});Object.defineProperty(exports,"matchesWhen",{enumerable:!0,get:function(){return G}});Object.defineProperty(exports,"partialDeepMatch",{enumerable:!0,get:function(){return x}});Object.defineProperty(exports,"safeAttachmentUrl",{enumerable:!0,get:function(){return q}});Object.defineProperty(exports,"validateConfig",{enumerable:!0,get:function(){return I}});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function C(u={}){const{overrides:g={}}=u,a={id:"mock-msg-1",conversationId:"mock-conv-1",content:"Mock response",role:"assistant",status:"DONE",createdAt:new Date},l={id:"mock-conv-1",title:"Mock Conversation",lastMessageDate:new Date},o=new Map,r=[],m={sendMessage:async()=>a,continueMessage:async()=>a,resumePendingClientFunctions:async()=>null,registerResumeGate:()=>()=>{},getConversations:async()=>[l],getConversation:async()=>l,getMessages:async()=>[a],submitToolCallForm:async()=>({status:"completed"}),confirmToolCall:async()=>{},rejectToolCall:async()=>{},submitFeedback:async()=>{},registerFunction:()=>{},unregisterFunction:()=>!0,getRegisteredFunctions:()=>[],executeClientFunction:async()=>{},getContextSnapshot:()=>null,notifyContextChanged:()=>{},setContext:()=>{},removeContext:()=>!0,addDynamicContext:()=>{},removeDynamicContext:()=>!0,registerNavigationFunction:()=>{},unregisterNavigationFunction:()=>{},getConfiguredAgent:()=>{},getHttp:()=>({get:async()=>({}),post:async()=>({})}),emitProactiveEvent:(e,t)=>{const n=o.get(e);n&&[...n].forEach(s=>s(t))},on:(e,t)=>{o.has(e)||o.set(e,new Set),o.get(e).add(t)},off:(e,t)=>{o.get(e)?.delete(t)},once:(e,t)=>{const n=(...s)=>{o.get(e)?.delete(n),t(...s)};o.has(e)||o.set(e,new Set),o.get(e).add(n)},waitFor:(e,t)=>new Promise((n,s)=>{let c;const d=f=>{c&&clearTimeout(c),o.get(e)?.delete(d),n(f)};o.has(e)||o.set(e,new Set),o.get(e).add(d),t?.timeout&&(c=setTimeout(()=>{o.get(e)?.delete(d),s(new Error(`waitFor("${e}") timed out after ${t.timeout}ms`))},t.timeout))}),updateConfig:()=>{},destroy:()=>{o.clear()},...g},i={};for(const[e,t]of Object.entries(m))i[e]=(...n)=>(r.push({method:e,args:n}),t(...n));return i.proactive=null,i.__calls=r,i.__callsFor=e=>r.filter(t=>t.method===e),i.__emitEvent=(e,t)=>{const n=o.get(e);n&&[...n].forEach(s=>s(t))},i}Object.defineProperty(exports,"createMockClient",{enumerable:!0,get:function(){return C}});
|