@useago/sdk 1.0.0 → 1.0.2
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 +44 -0
- package/README.md +103 -74
- package/dist/AgoClient-_fig8FoN.cjs +6 -0
- package/dist/{AgoClient-BrYhSO5M.js → AgoClient-bQozE6-A.js} +174 -134
- package/dist/angular.cjs +1 -1
- package/dist/angular.d.cts +131 -1
- package/dist/angular.d.ts +131 -1
- package/dist/angular.js +1 -1
- package/dist/{createConversationSession-Dmqd80DX.js → createConversationSession-BbK3ou1k.js} +1 -1
- package/dist/{createConversationSession-CGRO0bs2.cjs → createConversationSession-DJUFgOXy.cjs} +1 -1
- package/dist/createFormCollector-CdQjGAl_.cjs +1 -0
- package/dist/createFormCollector-D-TpSd9x.js +257 -0
- package/dist/devtools.d.cts +131 -1
- package/dist/devtools.d.ts +131 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +126 -4
- package/dist/index.d.ts +126 -4
- package/dist/index.js +94 -92
- package/dist/react.cjs +20 -20
- package/dist/react.d.cts +125 -30
- package/dist/react.d.ts +125 -30
- package/dist/react.js +2207 -2135
- package/dist/testing.d.cts +131 -1
- package/dist/testing.d.ts +131 -1
- package/dist/vue.cjs +1 -1
- package/dist/vue.d.cts +131 -1
- package/dist/vue.d.ts +131 -1
- package/dist/vue.js +1 -1
- package/dist/widget.cjs +4 -4
- package/dist/widget.d.cts +115 -5
- package/dist/widget.d.ts +115 -5
- package/dist/widget.js +408 -322
- package/package.json +2 -2
- package/dist/AgoClient-CPqPT5s6.cjs +0 -6
- package/dist/createFormCollector-CPS2phYq.js +0 -245
- package/dist/createFormCollector-DaWZsBsI.cjs +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ All notable changes to `@useago/sdk` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `AgoConfig.warnOnEmptyReply` (default `true`) and a `message:empty` event:
|
|
13
|
+
a reply that completes `DONE` with no content, tool calls, or follow-ups
|
|
14
|
+
(usually an unknown `agent` slug) now warns on the console once per
|
|
15
|
+
conversation and emits a host-consumable event instead of failing silently.
|
|
16
|
+
- Stable error codes as the compatibility surface (match on `code`, not
|
|
17
|
+
message text): `config_missing_base_url`, `config_suspect_base_url`
|
|
18
|
+
(warning), `stream_no_body`, `function_invalid_registration`. Code registry
|
|
19
|
+
documented in `docs/general/configuration.md#error-codes`.
|
|
20
|
+
- `message:error` events now carry the stable `code` when the failure was an
|
|
21
|
+
`AgoError`.
|
|
22
|
+
- `validateConfig` is exported.
|
|
23
|
+
- CI now runs lint, typecheck, build and `check:package`, plus a Node 18
|
|
24
|
+
packed-tarball smoke test (ESM import + CJS require + instantiation).
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `new AgoClient({})` (or missing/empty `baseUrl`) now throws an actionable
|
|
29
|
+
`AgoError` (`config_missing_base_url`) instead of a raw `TypeError`.
|
|
30
|
+
Same-origin `/path` baseUrls remain supported; a baseUrl with no protocol
|
|
31
|
+
and no leading slash logs a one-time warning.
|
|
32
|
+
- `updateConfig` validates the merged config and no longer half-applies an
|
|
33
|
+
invalid update; explicit `undefined` values are treated as absent.
|
|
34
|
+
- Error messages now include a one-line fix hint (network, no-body stream,
|
|
35
|
+
401/403/404 HTTP errors) and surface the server's `doc_url` when present.
|
|
36
|
+
- `createAgo()` and `mountChatWidget` throw coded `AgoError`s for missing
|
|
37
|
+
config instead of plain `Error`s.
|
|
38
|
+
- The `simple-html` examples import the published package from
|
|
39
|
+
`https://esm.sh/@useago/sdk@1`, so opening the file directly in a browser
|
|
40
|
+
works with no build step.
|
|
41
|
+
- The visitor anonymous id is now sent as the `X-User-Anon-Id` header. The
|
|
42
|
+
`widgetId` config option and the `ago_widget_id` storage key are unchanged.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- `generateAnonId` no longer crashes on stock Node 18 (global
|
|
47
|
+
`crypto.randomUUID` is flag-gated there); it feature-detects and falls back.
|
|
48
|
+
- `AgoProvider` no longer constructs a throwaway internal client when an
|
|
49
|
+
external `client` prop is provided.
|
|
50
|
+
- `examples/simple-html/test.html` imported from a wrong-depth path.
|
|
51
|
+
|
|
8
52
|
## [1.0.0] - 2026-06-11
|
|
9
53
|
|
|
10
54
|
First stable public release.
|
package/README.md
CHANGED
|
@@ -1,97 +1,115 @@
|
|
|
1
1
|
# @useago/sdk
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@useago/sdk)
|
|
4
|
-
[](./LICENSE)
|
|
5
4
|
[](https://www.npmjs.com/package/@useago/sdk)
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
about what the user is doing.
|
|
6
|
+
**The agent layer for your frontend stack.** AGO's agents plug into React, Vue,
|
|
7
|
+
Angular or plain TypeScript like any other dependency: send a message, stream
|
|
8
|
+
the reply, and let the agent call **your** code and navigate **your** UI.
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
same
|
|
10
|
+
In two minutes you'll have a working chat on your page and an agent that can
|
|
11
|
+
navigate your app. The quickstart below uses **React**; every other stack gets
|
|
12
|
+
the same two steps through its own guide:
|
|
13
|
+
|
|
14
|
+
| Stack | Guide |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/react/react-original.svg" width="14" /> React | **you're reading it**, just scroll down |
|
|
17
|
+
| <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/vuejs/vuejs-original.svg" width="14" /> Vue 3 | [Vue guide](docs/frameworks/vue.md) |
|
|
18
|
+
| <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/angular/angular-original.svg" width="14" /> Angular | [Angular guide](docs/frameworks/angular.md) |
|
|
19
|
+
| <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg" width="14" /> Plain JavaScript / TypeScript | [Core guide](docs/general/core.md) |
|
|
20
|
+
| <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/html5/html5-original.svg" width="14" /> Any website, no build step (`<script>`) | [Widget guide](docs/general/widget.md) |
|
|
21
|
+
|
|
22
|
+
## <img src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/react/react-original.svg" width="22" /> Try it in 30 seconds
|
|
15
23
|
|
|
16
24
|
```bash
|
|
17
25
|
npm install @useago/sdk
|
|
18
26
|
```
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
The SDK has no framework dependency of its own. The React bindings need `react`
|
|
29
|
+
and `react-dom` (>=17): already there in an existing React app, otherwise
|
|
30
|
+
install them too.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// App.tsx
|
|
34
|
+
import { AgoProvider, ChatWidget } from "@useago/sdk/react";
|
|
35
|
+
|
|
36
|
+
export default function App() {
|
|
37
|
+
return (
|
|
38
|
+
<AgoProvider baseUrl="https://playground.api.useago.com" agent="generic-guide">
|
|
39
|
+
<ChatWidget title="AGO" welcomeMessage="Ask me anything!" height={500} />
|
|
40
|
+
</AgoProvider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
Start your dev server. A chat panel renders on the page; type a message and
|
|
46
|
+
watch the reply stream in. That's a complete integration. (`generic-guide` is a
|
|
47
|
+
public demo agent; swap `baseUrl` and `agent` for your own once you have a tenant.)
|
|
24
48
|
|
|
25
|
-
##
|
|
49
|
+
## Let the agent drive your existing routes
|
|
26
50
|
|
|
27
|
-
|
|
51
|
+
This is the part existing apps care about. You already have a router and pages;
|
|
52
|
+
describe them to the agent and *"show me my invoices"* actually navigates there.
|
|
53
|
+
One hook in the layout you already have, nothing to restructure:
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| React | [docs/frameworks/react.md](docs/frameworks/react.md) |
|
|
33
|
-
| Vue 3 | [docs/frameworks/vue.md](docs/frameworks/vue.md) |
|
|
34
|
-
| Angular | [docs/frameworks/angular.md](docs/frameworks/angular.md) |
|
|
35
|
-
| Embeddable widget (`<script>`) | [docs/general/widget.md](docs/general/widget.md) |
|
|
55
|
+
```tsx
|
|
56
|
+
import { Outlet, useNavigate } from "react-router-dom";
|
|
57
|
+
import { useAgoNavigation } from "@useago/sdk/react";
|
|
36
58
|
|
|
37
|
-
|
|
59
|
+
// Your existing layout component. Only the hook is new.
|
|
60
|
+
function AppLayout() {
|
|
61
|
+
const navigate = useNavigate();
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
useAgoNavigation(navigate, [
|
|
64
|
+
{ name: "dashboard", path: "/dashboard", description: "KPIs and recent activity" },
|
|
65
|
+
{ name: "invoices", path: "/invoices", description: "List, search and download invoices" },
|
|
66
|
+
{ name: "settings", path: "/settings", description: "Account, billing and team settings" },
|
|
67
|
+
]);
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
return <Outlet />; // your existing routes render as before
|
|
70
|
+
}
|
|
71
|
+
```
|
|
46
72
|
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
Each route needs a `name`, its existing `path`, and a `description` the agent
|
|
74
|
+
uses to pick the right page. It calls **your** `navigate` function, so route
|
|
75
|
+
guards, auth and layouts keep working exactly as they do today.
|
|
76
|
+
`react-router-dom` here is *your* router, not an SDK dependency:
|
|
77
|
+
`useAgoNavigation` accepts any `(path: string) => void` function.
|
|
49
78
|
|
|
50
|
-
|
|
79
|
+
**Next: the [Getting started guide](docs/general/getting-started.md)** has both
|
|
80
|
+
examples assembled into a running app, plus everything else the SDK can do, in
|
|
81
|
+
about 5 minutes.
|
|
51
82
|
|
|
52
|
-
|
|
83
|
+
## Or clone a running example
|
|
53
84
|
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/useago/ago-sdk.git
|
|
87
|
+
cd ago-sdk && npm install && npm run build # build the SDK once
|
|
88
|
+
cd examples/simple-react
|
|
89
|
+
npm install && npm run dev
|
|
56
90
|
```
|
|
57
91
|
|
|
92
|
+
The examples ship pre-configured against the demo backend, so they answer
|
|
93
|
+
immediately. [`examples/`](examples/) has one per stack: React (with
|
|
94
|
+
react-router navigation), Vue, Angular, plain TypeScript, and no-build HTML.
|
|
95
|
+
|
|
58
96
|
---
|
|
59
97
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
| Send message (streaming) | ✅ | ✅ `useChat`/`useMessages` | ✅ `useChat`/`useMessages` | ✅ `AgoService.sendMessage` |
|
|
71
|
-
| File attachments | ✅ | ✅ | ✅ | ✅ |
|
|
72
|
-
| List / load conversations | ✅ | ✅ `useConversation` | ✅ `useConversation` | ✅ `AgoService` |
|
|
73
|
-
| All-in-one chat state | — | ✅ `useChat` | ✅ `useChat` | ➖ compose manually |
|
|
74
|
-
| Pre-built `<ChatWidget>` UI | — | ✅ | ➖ see example | — |
|
|
75
|
-
| Markdown / `<Message>` / `<ChatInput>` | — | ✅ | — | — |
|
|
76
|
-
| Client-side functions | ✅ `registerFunction` | ✅ `useAgoFunction` | ✅ `useAgoFunction` | ✅ `AgoService` |
|
|
77
|
-
| `defineFunction` / `withHandler` | ✅ | ✅ | ✅ | ✅ |
|
|
78
|
-
| Pre-built helpers (`showToast`, …) | ✅ | ✅ `helpers` prop | ✅ | ✅ |
|
|
79
|
-
| Navigation function | ✅ `registerNavigationFunction` | ✅ `useAgoNavigation` | ✅ `useAgoNavigation` | ✅ `AgoService` |
|
|
80
|
-
| Client context | ✅ `setContext` / `addDynamicContext` | ✅ `useAgoContext` | ✅ core API | ✅ core API |
|
|
81
|
-
| Auto page context | ✅ `enableAutoPageContext()` | ✅ `pageContext="auto"` | ✅ core API | ✅ core API |
|
|
82
|
-
| Events (`on`/`off`/`once`/`waitFor`) | ✅ | ✅ (via client) | ✅ `useAgoEvents` | ✅ `messages$` Observables |
|
|
83
|
-
| Streaming helpers / async generator | ✅ | ✅ | ✅ | ✅ |
|
|
84
|
-
| Tool calls (form/confirm/reject) | ✅ | ✅ | ✅ | ✅ `AgoService` |
|
|
85
|
-
| Message feedback | ✅ `submitFeedback` | ✅ | ✅ | ✅ `AgoService` |
|
|
86
|
-
| Observable store | ✅ `createStore` | ✅ `useAgoStore` | ✅ `useAgoStore` | ➖ core API |
|
|
87
|
-
| Dev panel / devtools | ✅ `initDevPanel` (`/devtools`) | ✅ | ✅ | ✅ |
|
|
88
|
-
| Testing mock client | ✅ `createMockClient` | ✅ | ✅ | ✅ |
|
|
89
|
-
|
|
90
|
-
Legend: ✅ first-class binding · ➖ supported via the core API, no dedicated sugar · — not applicable.
|
|
98
|
+
## Docs
|
|
99
|
+
|
|
100
|
+
- [**Getting started**](docs/general/getting-started.md): send a message, wire the router, see the feature map
|
|
101
|
+
- [**Feature matrix**](docs/general/feature-matrix.md): everything that ships, per stack
|
|
102
|
+
- [**Functions & context**](docs/general/functions-and-context.md): let the agent run your code and see what the user sees
|
|
103
|
+
- [**Events & streaming**](docs/general/events-and-streaming.md): low-level hooks into the message stream
|
|
104
|
+
- [**Testing**](docs/general/testing.md): mock client for unit tests
|
|
105
|
+
- [**Configuration & auth**](docs/general/configuration.md): every `AgoConfig` option, headers, errors
|
|
106
|
+
|
|
107
|
+
Full documentation: [ago.mintlify.app](https://ago.mintlify.app/)
|
|
91
108
|
|
|
92
109
|
---
|
|
93
110
|
|
|
94
|
-
|
|
111
|
+
<details>
|
|
112
|
+
<summary><strong>Package entry points</strong> (subpath exports)</summary>
|
|
95
113
|
|
|
96
114
|
The package is published with several subpath exports so bundlers only pull in
|
|
97
115
|
what you use:
|
|
@@ -104,20 +122,31 @@ what you use:
|
|
|
104
122
|
| `@useago/sdk/angular` | Angular service and provider |
|
|
105
123
|
| `@useago/sdk/helpers` | Pre-built client functions only |
|
|
106
124
|
| `@useago/sdk/widget` | `window.AGO` widget config types |
|
|
107
|
-
| `@useago/sdk/devtools` | `initDevPanel
|
|
108
|
-
| `@useago/sdk/testing` | `createMockClient
|
|
125
|
+
| `@useago/sdk/devtools` | `initDevPanel`, an in-browser debug overlay (DOM/CSS) |
|
|
126
|
+
| `@useago/sdk/testing` | `createMockClient`, a mock client for tests |
|
|
109
127
|
|
|
110
128
|
ESM and CJS builds are both shipped, with full TypeScript declarations.
|
|
111
129
|
|
|
112
|
-
|
|
130
|
+
</details>
|
|
113
131
|
|
|
114
|
-
##
|
|
132
|
+
## Dependencies
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
The core SDK depends on nothing but the platform: `fetch` and `ReadableStream`,
|
|
135
|
+
so any modern browser or Node 18+. Framework packages are **optional peer
|
|
136
|
+
dependencies**; you only need the one whose bindings you import:
|
|
137
|
+
|
|
138
|
+
| You import | Needs | Notes |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `@useago/sdk` (core, widget, helpers, testing) | nothing | works everywhere |
|
|
141
|
+
| `@useago/sdk/react` | `react` + `react-dom` `>=17` | already present in any React app |
|
|
142
|
+
| `@useago/sdk/vue` | `vue` `>=3.3` | already present in any Vue 3 app |
|
|
143
|
+
| `@useago/sdk/angular` | nothing | no hard Angular dependency, works with any DI container |
|
|
144
|
+
|
|
145
|
+
Your router (`react-router-dom`, `vue-router`, Angular Router) is **never** an
|
|
146
|
+
SDK dependency: the navigation helpers just take your navigate function.
|
|
147
|
+
|
|
148
|
+
---
|
|
120
149
|
|
|
121
150
|
## License
|
|
122
151
|
|
|
123
|
-
[Apache 2.0](./LICENSE) · [Documentation](https://
|
|
152
|
+
[Apache 2.0](./LICENSE) · [Documentation](https://ago.mintlify.app/) · [Website](https://useago.com)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
var h=class extends Error{constructor(e,t,s){super(e),this.code=t,this.statusCode=s,this.name="AgoError"}},u=class v extends h{constructor(t,s,r,n,i,a){super(t,s,r),this.type=n,this.param=i,this.docUrl=a,this.name="AgoApiError"}static fromResponse(t,s){const r=t.error;return new v(r.doc_url?`${r.message} See ${r.doc_url}`:r.message,r.code,s,r.type,r.param,r.doc_url)}},d=class extends h{constructor(e,t){super(e,"network_error"),this.originalError=t,this.name="AgoNetworkError"}},g=class extends h{constructor(e,t="stream_error"){super(e,t),this.name="AgoStreamError"}},m=class extends h{constructor(e,t,s){super(e,"function_error"),this.functionName=t,this.originalError=s,this.name="AgoFunctionError"}},A=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)}},o=new A,p="ago_widget_id";function x(){if(typeof localStorage<"u"){const t=localStorage.getItem(p);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(p,e)}catch{}return e}var $=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.headers={"X-User-Anon-Id":e.widgetId||x()},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){return this.request("POST",e,t)}async postStream(e,t){const s=`${this.baseUrl}${e}`;o.debug("POST (stream)",s,t);try{const r=await fetch(s,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0});return r.ok||await this.handleErrorResponse(r),r}catch(r){throw r instanceof u?r:new d(`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 postFormData(e,t){const s=`${this.baseUrl}${e}`;o.debug("POST (formData)",s);const r={...this.headers};try{const n=await fetch(s,{method:"POST",headers:r,body:t});return n.ok||await this.handleErrorResponse(n),n}catch(n){throw n instanceof u?n:new d(`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,s){const r=`${this.baseUrl}${t}`;o.debug(e,r,s);try{const n=await fetch(r,{method:e,headers:{...this.headers,"Content-Type":"application/json"},body:s?JSON.stringify(s):void 0});return n.ok||await this.handleErrorResponse(n),n.status===204?void 0:n.json()}catch(n){throw n instanceof u?n:new d(`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 handleErrorResponse(e){let t;try{t=await e.json()}catch{}if(t?.error)throw u.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 u(`HTTP ${e.status}: ${e.statusText}.${s}`,"http_error",e.status,"api_error")}},b=class{constructor(){this.functions=new Map}register(e,t,s){if(typeof e=="object"){const{name:n,handler:i,description:a,parameters:l}=e;return this.register(n,i,{description:a,parameters:l})}const r=e;if(!t||!s)throw new h(`registerFunction("${r}"): 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(r)&&o.warn(`Function "${r}" is being overwritten`),this.functions.set(r,{schema:{...s,name:r},handler:t}),o.log(`Registered function: ${r}`)}unregister(e){const t=this.functions.delete(e);return t&&o.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 m(`Function "${e}" is not registered`,e);o.log(`Executing function: ${e}`,t);try{const r=await s.handler(t);return o.log(`Function ${e} completed:`,r),r}catch(r){throw o.error(`Function ${e} failed:`,r),new m(`Function "${e}" execution failed: ${r instanceof Error?r.message:"Unknown error"}`,e,r instanceof Error?r:void 0)}}clear(){this.functions.clear(),o.log("Cleared all registered functions")}get size(){return this.functions.size}},C=class{constructor(){this.entries=new Map,this.dynamicProviders=new Map}set(e,t){this.entries.set(e,t),o.log(`ClientContext set: ${e}`)}remove(e){const t=this.entries.delete(e);return t&&o.log(`ClientContext removed: ${e}`),t}addDynamicProvider(e,t){this.dynamicProviders.set(e,t),o.log(`DynamicContext provider added: ${e}`)}removeDynamicProvider(e){const t=this.dynamicProviders.delete(e);return t&&o.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 r=s();r&&(e[t]=r)}catch(r){o.error(`DynamicContext provider "${t}" threw:`,r)}return Object.keys(e).length===0?null:{entries:e}}clear(){this.entries.clear(),this.dynamicProviders.clear(),o.log("ClientContext cleared")}},E=class{constructor(e){this.callbacks=e,this.buffer="",this.message={},this.toolCalls=[],this.sources=[],this.followUpReplies=[],this.isFirstChunk=!0,this.answerCompleteEmitted=!1}async processStream(e){if(!e.body)throw new g("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:r,value:n}=await t.read();if(r)break;const i=s.decode(n,{stream:!0});this.buffer+=i,this.processBuffer()}return this.buffer.trim()&&this.processBuffer(),this.buildFinalMessage()}catch(r){const n=r instanceof Error?r:new g("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 r of s)if(!r.startsWith(": ")&&r.startsWith("data: ")){const n=r.slice(6);try{const i=JSON.parse(n);this.handleChunk(i)}catch(i){o.warn("Failed to parse SSE data:",n,i)}}}}handleChunk(e){if(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.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&&this.callbacks.onClientFunction?.({invocationId:e.id||"",functionName:e.function_name,arguments:e.arguments||{},conversationId:this.message.conversationId||""}),e.tool_call_data&&e.type&&e.type!=="client_function"){const t=this.parseToolCall(e);this.callbacks.onToolCall?.(t);const s=this.toolCalls.findIndex(r=>r.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()))}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 this.callbacks.onComplete?.(e),e}};function R(e){const t=e.message.toLowerCase();return t.includes("load failed")||t.includes("failed to fetch")||t.includes("network")||t.includes("abort")}var _=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(r=>{try{r(t)}catch(n){console.error(`Error in event handler for ${String(e)}:`,n)}})}once(e,t){const s=(r=>{this.off(e,s),t(r)});this.on(e,s)}waitFor(e,t){return new Promise((s,r)=>{let n;const i=(a=>{n&&clearTimeout(n),s(a)});this.once(e,i),t?.timeout&&(n=setTimeout(()=>{this.off(e,i),r(new Error(`waitFor("${String(e)}") timed out after ${t.timeout}ms`))},t.timeout))})}removeAllListeners(e){e?this.handlers.delete(e):this.handlers.clear()}},y='`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).',I=/^[a-z][a-z0-9+.-]*:\/\//i,w=!1;function f(e,t="AgoClient"){if(typeof e!="object"||e===null)throw new h(`${t}: ${y}`,"config_missing_base_url");const s=e.baseUrl;if(typeof s!="string"||s.trim()==="")throw new h(`${t}: ${y}`,"config_missing_base_url");!s.startsWith("/")&&!I.test(s)&&(w||(w=!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).`)))}var U=class S{constructor(t){this.warnedEmptyConversations=new Set,f(t,"AgoClient"),this.config=t,this.httpClient=new $(t),this.functionRegistry=new b,this.contextRegistry=new C,this.eventEmitter=new _,t.debug&&o.enable(),o.log("AgoClient initialized")}async sendMessage(t,s){const r=this.functionRegistry.getSchemas(),n=this.contextRegistry.getSnapshot(),i=this.config.agent||this.config.defaultAgentId,a={content:t,conversation_id:s?.conversationId,agent_id:s?.agentId||i};r.length>0&&(a.client_functions=r),n&&(a.client_context=n);let l;if(s?.files&&s.files.length>0){const c=new FormData;c.append("content",t),s.conversationId&&c.append("conversation_id",s.conversationId),(s.agentId||i)&&c.append("agent_id",s.agentId||i||""),r.length>0&&c.append("client_functions",JSON.stringify(r)),n&&c.append("client_context",JSON.stringify(n));for(const k of s.files)c.append("files",k);l=await this.httpClient.postFormData("/api/sdk/v1/messages",c)}else l=await this.httpClient.postStream("/api/sdk/v1/messages",a);return this.processSSEResponse(l)}async processSSEResponse(t){let s=!1;const r=await new E({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=>{s=!0,this.eventEmitter.emit("function:invoke",n),await this.handleClientFunctionInvocation(n)},onAnswerComplete:n=>{this.eventEmitter.emit("message:answer-complete",n)},onComplete:n=>{this.eventEmitter.emit("message:complete",n)},onError:n=>{this.eventEmitter.emit("message:error",{error:n.message,code:n instanceof h?n.code:void 0})}}).processStream(t);return this.maybeFlagEmptyReply(r,s),r}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 r=t.conversationId||"(no conversation)";if(this.warnedEmptyConversations.has(r))return;this.warnedEmptyConversations.add(r);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,r;try{s=await this.functionRegistry.execute(t.functionName,t.arguments)}catch(n){r=n instanceof Error?n.message:"Unknown error",o.error("Client function execution failed:",n)}if(t.invocationId)try{await this.submitToolCallForm(t.invocationId,{result:s,error:r,_type:"client_function_result"})}catch(n){o.error("Failed to submit function result:",n)}this.eventEmitter.emit("function:result",{invocationId:t.invocationId,result:s,error:r})}async getConversations(){return(await this.httpClient.get("/api/sdk/v1/conversations")).items.map(t=>({id:t.id,title:t.title,lastMessageDate:new Date(t.last_message_date)}))}async getConversation(t){const s=await this.httpClient.get(`/api/sdk/v1/conversations/${t}`),r={id:s.id,title:s.title,lastMessageDate:new Date(s.last_message_date),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),toolCalls:S.mapPersistedToolCalls(n.tool_call_data)}))};return this.eventEmitter.emit("conversation:loaded",r),r}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 submitToolCallForm(t,s){await 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",{destination:t,values:s})}async submitFeedback(t,s){await this.httpClient.post(`/api/sdk/v1/messages/${t}/feedback`,{rating:s})}registerFunction(t,s,r){typeof t=="object"?this.functionRegistry.register(t):this.functionRegistry.register(t,s,r)}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()}registerNavigationFunction(t,s){const r=s.map(i=>i.name),n=s.map(i=>`- "${i.name}": ${i.description}`).join(`
|
|
5
|
+
`);this.registerFunction("navigateToPage",async i=>{const a=i.page,l=s.find(c=>c.name===a);return l?(t(l.path),{success:!0,navigatedTo:l.path}):{success:!1,error:`Unknown page: ${a}`}},{description:`Navigate the user to a page in the application. Available pages:
|
|
6
|
+
${n}`,parameters:{type:"object",properties:{page:{type:"string",description:"The page to navigate to",enum:r}},required:["page"]}})}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())}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()}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,i]of Object.entries(t))i!==void 0&&(s[n]=i);const r={...this.config,...s};f(r,"AgoClient.updateConfig"),this.config=r,this.httpClient.updateConfig(s),s.debug!==void 0&&(s.debug?o.enable():o.disable())}destroy(){this.eventEmitter.removeAllListeners(),this.functionRegistry.clear(),this.contextRegistry.clear(),o.log("AgoClient destroyed")}};Object.defineProperty(exports,"AgoApiError",{enumerable:!0,get:function(){return u}});Object.defineProperty(exports,"AgoClient",{enumerable:!0,get:function(){return U}});Object.defineProperty(exports,"AgoError",{enumerable:!0,get:function(){return h}});Object.defineProperty(exports,"AgoFunctionError",{enumerable:!0,get:function(){return m}});Object.defineProperty(exports,"AgoNetworkError",{enumerable:!0,get:function(){return d}});Object.defineProperty(exports,"AgoStreamError",{enumerable:!0,get:function(){return g}});Object.defineProperty(exports,"ClientContextRegistry",{enumerable:!0,get:function(){return C}});Object.defineProperty(exports,"EventEmitter",{enumerable:!0,get:function(){return _}});Object.defineProperty(exports,"FunctionRegistry",{enumerable:!0,get:function(){return b}});Object.defineProperty(exports,"SSEHandler",{enumerable:!0,get:function(){return E}});Object.defineProperty(exports,"isStreamNetworkError",{enumerable:!0,get:function(){return R}});Object.defineProperty(exports,"logger",{enumerable:!0,get:function(){return o}});Object.defineProperty(exports,"validateConfig",{enumerable:!0,get:function(){return f}});
|