@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/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).