@invergent/website-widget 0.2.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/LICENSE.AGPL-3.0 +661 -0
- package/README.md +175 -0
- package/dist/index.cjs +712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +406 -0
- package/dist/index.d.ts +406 -0
- package/dist/index.js +694 -0
- package/dist/index.js.map +1 -0
- package/dist/surogates-widget.global.js +26 -0
- package/dist/surogates-widget.global.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @invergent/website-widget
|
|
2
|
+
|
|
3
|
+
AG-UI-compatible TypeScript client for the Surogates public-website channel. Wraps the channel-specific bootstrap, HttpOnly cookie, CSRF double-submit, and SSE stream behind a standard [AG-UI](https://docs.ag-ui.com/) `AbstractAgent` so any widget built against AG-UI works on top of Surogates with no custom glue.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { WebsiteAgent } from '@invergent/website-widget';
|
|
9
|
+
|
|
10
|
+
const agent = new WebsiteAgent({
|
|
11
|
+
apiUrl: 'https://agent.acme.com',
|
|
12
|
+
publishableKey: 'surg_wk_...',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
agent.subscribe({
|
|
16
|
+
onTextMessageContentEvent: ({ event }) => renderDelta(event.delta),
|
|
17
|
+
onToolCallStartEvent: ({ event }) => showToolPill(event.toolCallName),
|
|
18
|
+
onRunFinishedEvent: () => markDone(),
|
|
19
|
+
onRunErrorEvent: ({ event }) => showError(event.message),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
agent.addMessage({ role: 'user', content: 'How do I cancel my subscription?' });
|
|
23
|
+
await agent.runAgent();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. Everything else -- publishable-key verification, the HttpOnly + Secure + SameSite cookie, `X-CSRF-Token` on every POST, SSE reconnect with cursor, per-turn `RUN_STARTED`/`RUN_FINISHED`, mapping Surogates-native events (`llm.delta`, `tool.call`, `policy.denied`, `expert.delegation`) onto AG-UI's standard vocabulary -- happens inside `WebsiteAgent`.
|
|
27
|
+
|
|
28
|
+
## Why AG-UI
|
|
29
|
+
|
|
30
|
+
AG-UI is the industry-standard agent-to-UI protocol (CopilotKit, LangGraph, Mastra, CrewAI). A widget written against it today can swap the backend from Surogates to any other AG-UI-compatible agent without rewriting the frontend. You get:
|
|
31
|
+
|
|
32
|
+
- Typed streaming text (`TEXT_MESSAGE_START` / `CONTENT` / `END`)
|
|
33
|
+
- Typed tool calls with incremental argument streaming (`TOOL_CALL_*`)
|
|
34
|
+
- Reasoning visibility (`REASONING_*`)
|
|
35
|
+
- Run lifecycle (`RUN_STARTED` / `RUN_FINISHED` / `RUN_ERROR`)
|
|
36
|
+
- Step tracking (`STEP_STARTED` / `STEP_FINISHED`) for sub-agents and expert delegation
|
|
37
|
+
- Middleware, subscribers, and state management out of the box
|
|
38
|
+
|
|
39
|
+
Surogates-specific signals that don't have a first-class AG-UI equivalent (`memory.update`, `context.compact`, `session.reset`, `policy.denied`, internal saga steps) are forwarded as AG-UI `CUSTOM` events with the original Surogates event name in `name`. Consumers that want them can match on `name`; consumers that don't simply ignore them.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @invergent/website-widget @ag-ui/client @ag-ui/core rxjs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`@ag-ui/client`, `@ag-ui/core`, and `rxjs` are **peer dependencies** -- they likely already exist in your app's bundle (especially if you're using CopilotKit or another AG-UI consumer), so we don't duplicate them.
|
|
48
|
+
|
|
49
|
+
### CDN / `<script>` tag
|
|
50
|
+
|
|
51
|
+
For plain HTML sites without a bundler, use the IIFE build from a CDN. It bundles AG-UI and RxJS:
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<script src="https://cdn.surogates.com/widget/v1/surogates-widget.global.js"></script>
|
|
55
|
+
<script>
|
|
56
|
+
const agent = new SurogatesWidget.WebsiteAgent({
|
|
57
|
+
apiUrl: 'https://agent.acme.com',
|
|
58
|
+
publishableKey: 'surg_wk_...',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
agent.subscribe({
|
|
62
|
+
onTextMessageContentEvent: ({ event }) => document.body.append(event.delta),
|
|
63
|
+
onRunFinishedEvent: () => console.log('done'),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
agent.addMessage({ role: 'user', content: 'hello' });
|
|
67
|
+
agent.runAgent();
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The IIFE exposes `WebsiteAgent`, `EventType`, `AbstractAgent`, the error classes, and the `Translator` on `window.SurogatesWidget`.
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
### `new WebsiteAgent(config)`
|
|
76
|
+
|
|
77
|
+
| Option | Type | Notes |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `apiUrl` | string, required | Base URL of the Surogates API (e.g. `https://agent.acme.com`). No trailing slash required. |
|
|
80
|
+
| `publishableKey` | string, required | `surg_wk_...` key configured at deploy time via `website.publishable_key`. Safe to embed in browser JS. |
|
|
81
|
+
| `threadId` | string, optional | AG-UI thread id. One is minted if not provided. |
|
|
82
|
+
| `agentId` | string, optional | AG-UI agent id. |
|
|
83
|
+
| `initialMessages` | `Message[]`, optional | Pre-populated conversation history. |
|
|
84
|
+
| `initialState` | `State`, optional | Pre-populated agent state. |
|
|
85
|
+
|
|
86
|
+
Plus every other field accepted by AG-UI's `AgentConfig`.
|
|
87
|
+
|
|
88
|
+
### Inherited from `AbstractAgent`
|
|
89
|
+
|
|
90
|
+
* `runAgent(parameters?, subscriber?): Promise<RunAgentResult>` — primary entry point
|
|
91
|
+
* `subscribe(subscriber): { unsubscribe() }`
|
|
92
|
+
* `addMessage(message)` / `addMessages(messages)` / `setMessages(messages)`
|
|
93
|
+
* `abortRun()`
|
|
94
|
+
* `messages`, `state`, `threadId`, `agentId`
|
|
95
|
+
|
|
96
|
+
See [AG-UI docs](https://docs.ag-ui.com/sdk/js/client/abstract-agent) for the full interface.
|
|
97
|
+
|
|
98
|
+
### Additional methods
|
|
99
|
+
|
|
100
|
+
#### `ensureBootstrapped(): Promise<BootstrapResult>`
|
|
101
|
+
Exchange the publishable key for a session cookie + CSRF token. Called automatically by the first `runAgent()`; expose this to validate configuration eagerly (e.g. at widget-load time).
|
|
102
|
+
|
|
103
|
+
#### `end(): Promise<void>`
|
|
104
|
+
Mark the server-side session `completed` and clear the session cookie. Call when the visitor closes your chat UI.
|
|
105
|
+
|
|
106
|
+
### Error taxonomy
|
|
107
|
+
|
|
108
|
+
Every error the SDK throws or emits via `RUN_ERROR` derives from `SurogatesError`:
|
|
109
|
+
|
|
110
|
+
| Class | When |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `SurogatesAuthError` | Publishable key invalid, Origin not in allow-list, CSRF mismatch. Non-retryable. |
|
|
113
|
+
| `SurogatesRateLimitError` | HTTP 429 or per-session message cap reached. Exposes `retryAfter` (seconds). |
|
|
114
|
+
| `SurogatesProtocolError` | Malformed response, SDK/server version mismatch. Priority-1 diagnostic signal. |
|
|
115
|
+
| `SurogatesNetworkError` | Network blip, DNS, CORS preflight refusal. Retryable. |
|
|
116
|
+
|
|
117
|
+
## Event mapping
|
|
118
|
+
|
|
119
|
+
The Surogates server emits event types defined in `surogates/session/events.py`. They map to AG-UI as follows:
|
|
120
|
+
|
|
121
|
+
| Surogates | AG-UI | Notes |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `llm.delta` | `TEXT_MESSAGE_CHUNK` (role=`assistant`) | Expanded to `TEXT_MESSAGE_START/CONTENT/END` by AG-UI's client transform |
|
|
124
|
+
| `llm.response` | — (closes the running chunk stream) | Also drives end-of-turn detection |
|
|
125
|
+
| `llm.thinking` | `REASONING_START` + `REASONING_MESSAGE_START` + `REASONING_MESSAGE_CONTENT` | |
|
|
126
|
+
| `tool.call` | `TOOL_CALL_CHUNK` | Full args in one chunk; AG-UI expands to `TOOL_CALL_START/ARGS/END` |
|
|
127
|
+
| `tool.result` | `TOOL_CALL_RESULT` | |
|
|
128
|
+
| `expert.delegation` | `STEP_STARTED` (stepName=`expert:<name>`) | |
|
|
129
|
+
| `expert.result` | `STEP_FINISHED` | |
|
|
130
|
+
| `session.fail`, `harness.crash` | `RUN_ERROR` | Terminal for the run |
|
|
131
|
+
| `session.done`, `session.complete` | closes stream + emits `RUN_FINISHED` | |
|
|
132
|
+
| `policy.denied`, `memory.update`, `context.compact`, and every other Surogates-specific event | `CUSTOM` | `name` carries the original Surogates type |
|
|
133
|
+
| `user.message`, `llm.request`, `session.start`, `sandbox.*`, `policy.allowed`, `harness.wake` | dropped | Internal orchestration, not user-facing |
|
|
134
|
+
|
|
135
|
+
Plus the lifecycle envelope every run is wrapped in: `RUN_STARTED` at the top, `RUN_FINISHED` or `RUN_ERROR` at the bottom.
|
|
136
|
+
|
|
137
|
+
## Security model
|
|
138
|
+
|
|
139
|
+
The agent enforces the website channel's security contract transparently:
|
|
140
|
+
|
|
141
|
+
- **Publishable key** is sent only on bootstrap, only to the configured `apiUrl`, as `Authorization: Bearer`. Never persisted by the SDK.
|
|
142
|
+
- **Origin**: the browser sets it automatically on every cross-origin request; the server re-checks it on every call against the agent's allow-list.
|
|
143
|
+
- **Session cookie** is HttpOnly + Secure + SameSite=None, Path=/. Set by the server, managed by the browser.
|
|
144
|
+
- **CSRF**: the bootstrap response returns a CSRF token that the SDK caches in memory and attaches to every `POST` as `X-CSRF-Token`. The server compares it constant-time against the `csrf` claim baked into the cookie JWT.
|
|
145
|
+
|
|
146
|
+
See the [website channel documentation](https://github.com/invergent-ai/surogates/blob/master/docs/channels/website.md) for the full threat model and the server-side invariants.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pnpm install # first time
|
|
152
|
+
pnpm test # vitest
|
|
153
|
+
pnpm typecheck # tsc --noEmit
|
|
154
|
+
pnpm build # ESM + CJS + IIFE to ./dist
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Bundle size
|
|
158
|
+
|
|
159
|
+
Measured on the current build:
|
|
160
|
+
|
|
161
|
+
| Target | Raw | Gzipped |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| ESM (`dist/index.js`) | 21 KB | **6 KB** |
|
|
164
|
+
| CJS (`dist/index.cjs`) | 22 KB | **6 KB** |
|
|
165
|
+
| IIFE (`dist/surogates-widget.global.js`) | 285 KB | **66 KB** |
|
|
166
|
+
|
|
167
|
+
The npm/ESM numbers exclude AG-UI, RxJS, and zod (peer deps). The IIFE bundles everything for script-tag users.
|
|
168
|
+
|
|
169
|
+
## Versioning
|
|
170
|
+
|
|
171
|
+
This package follows semantic versioning; the wire protocol version is tracked separately in `PROTOCOL_VERSION`. The SDK sends `X-Surogates-Widget-Version: <semver>` on every request so server logs can correlate a buggy build with its error surface. A breaking change to the Surogates channel protocol bumps both `PROTOCOL_VERSION` and the major version of this package.
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
AGPL-3.0-or-later (same as the parent Surogates project).
|