@invergent/website-widget 2.7.4 → 2.7.6

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 CHANGED
@@ -46,29 +46,57 @@ pnpm add @invergent/website-widget @ag-ui/client @ag-ui/core rxjs
46
46
 
47
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
48
 
49
- ### CDN / `<script>` tag
49
+ ### Drop-in widget (`<script>` tag)
50
50
 
51
- For plain HTML sites without a bundler, use the IIFE build from a CDN. It bundles AG-UI and RxJS:
51
+ For plain HTML sites without a bundler, load the IIFE build from a CDN and
52
+ call `mount()`. One call renders a complete, self-contained chat widget — a
53
+ floating launcher button that opens a streaming chat panel, fully isolated in
54
+ a Shadow DOM so it can't collide with your site's CSS. The bundle includes
55
+ AG-UI, RxJS, and the UI:
52
56
 
53
57
  ```html
54
- <script src="https://cdn.surogates.com/widget/v1/surogates-widget.global.js"></script>
58
+ <script src="https://cdn.jsdelivr.net/npm/@invergent/website-widget@2/dist/surogates-widget.global.js"></script>
55
59
  <script>
56
- const agent = new SurogatesWidget.WebsiteAgent({
60
+ SurogatesWidget.mount({
57
61
  apiUrl: 'https://agent.acme.com',
58
62
  publishableKey: 'surg_wk_...',
63
+ // Optional appearance:
64
+ // title: 'Acme Support',
65
+ // accentColor: '#4f46e5',
66
+ // welcomeMessage: 'Hi! How can I help?',
67
+ // position: 'bottom-right',
59
68
  });
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
69
  </script>
69
70
  ```
70
71
 
71
- The IIFE exposes `WebsiteAgent`, `EventType`, `AbstractAgent`, the error classes, and the `Translator` on `window.SurogatesWidget`.
72
+ `mount(config)` returns a `WidgetHandle` — `{ open(), close(), toggle(),
73
+ destroy(), agent }` — for programmatic control.
74
+
75
+ | Option | Type | Notes |
76
+ |---|---|---|
77
+ | `apiUrl` | string, required | Base URL of the Surogates API. |
78
+ | `publishableKey` | string, required | `surg_wk_...` key. Safe to embed in browser JS. |
79
+ | `title` | string | Header label. Defaults to the deployed agent's name. |
80
+ | `accentColor` | string | Brand colour (hex) for the launcher/header/bubbles. |
81
+ | `welcomeMessage` | string | Greeting shown before the first turn (Markdown). |
82
+ | `position` | `'bottom-right'` \| `'bottom-left'` | Launcher corner. Default `bottom-right`. |
83
+ | `target` | `HTMLElement` | Host element. Defaults to `document.body`. |
84
+ | `inline` | boolean | Render the panel inline (no floating launcher). |
85
+ | `openByDefault` | boolean | Open the panel on load. |
86
+
87
+ The IIFE also exposes the headless `WebsiteAgent`, `EventType`,
88
+ `AbstractAgent`, the error classes, and the `Translator` on
89
+ `window.SurogatesWidget` for hosts that want to build their own UI.
90
+
91
+ #### Bundler consumers
92
+
93
+ If you build your own React/Vue app, import the headless client from the
94
+ package root, or the self-mounting UI from the `./ui` subpath:
95
+
96
+ ```ts
97
+ import { mount } from '@invergent/website-widget/ui';
98
+ mount({ apiUrl: 'https://agent.acme.com', publishableKey: 'surg_wk_...' });
99
+ ```
72
100
 
73
101
  ## API
74
102
 
@@ -160,11 +188,15 @@ Measured on the current build:
160
188
 
161
189
  | Target | Raw | Gzipped |
162
190
  |---|---|---|
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.
191
+ | ESM headless (`dist/index.js` + chunk) | ~23 KB | **~6 KB** |
192
+ | ESM widget UI (`dist/ui.js`) | 16 KB | **~5 KB** |
193
+ | IIFE (`dist/surogates-widget.global.js`) | 178 KB | **~49 KB** |
194
+
195
+ The npm headless numbers exclude AG-UI, RxJS, and zod (peer deps); the headless
196
+ entry stays Preact-free. The `./ui` build bundles Preact (peers still external).
197
+ The IIFE bundles everything (AG-UI + RxJS + Preact + UI) for script-tag users and
198
+ is built with `platform: 'browser'` so transitive deps resolve to their
199
+ Web-Crypto variants rather than Node's `require('crypto')`.
168
200
 
169
201
  ## Versioning
170
202
 
@@ -0,0 +1,174 @@
1
+ import { AbstractAgent, AgentConfig, RunAgentInput, BaseEvent } from '@ag-ui/client';
2
+ import { Observable } from 'rxjs';
3
+
4
+ /**
5
+ * HTTP + SSE wire layer.
6
+ *
7
+ * Pure functions that talk to the website channel's three endpoints,
8
+ * isolated here so :class:`WebsiteAgentClient` can stay focused on
9
+ * state-machine orchestration. All fetches use ``credentials:
10
+ * 'include'`` so the browser sends the HttpOnly session cookie that
11
+ * ``bootstrap`` set -- without it the message/SSE/end routes return
12
+ * 401 and the whole channel falls apart.
13
+ *
14
+ * SSE is handled by the native ``EventSource`` rather than a custom
15
+ * fetch-based parser because the native implementation is zero bytes,
16
+ * auto-reconnects on network drops, and respects browser-internal
17
+ * backoff heuristics. The one thing it can't do is resume with a
18
+ * server-side cursor, so we manage the ``?after=N`` reopen logic
19
+ * explicitly in :class:`WebsiteAgentClient`.
20
+ */
21
+ interface BootstrapResult {
22
+ sessionId: string;
23
+ csrfToken: string;
24
+ expiresAt: number;
25
+ agentName: string;
26
+ }
27
+
28
+ /**
29
+ * AG-UI ``AbstractAgent`` implementation for the Surogates public-website channel.
30
+ *
31
+ * A ``WebsiteAgent`` bridges the channel-specific HTTP protocol
32
+ * (publishable-key bootstrap, HttpOnly cookie, CSRF double-submit,
33
+ * ``/v1/website/sessions/{id}/events`` SSE) onto AG-UI's standard
34
+ * ``run()`` contract so any AG-UI-aware host app -- CopilotKit,
35
+ * custom chat UIs, LangGraph-style clients -- can talk to a Surogates
36
+ * backend with no custom glue beyond instantiation.
37
+ *
38
+ * Responsibilities
39
+ * ----------------
40
+ * * **Lazy bootstrap.** The first ``runAgent`` call POSTs to
41
+ * ``/v1/website/sessions`` to exchange the publishable key for a
42
+ * session cookie + CSRF token; subsequent calls reuse them. A cookie
43
+ * expiry (HTTP 401) triggers a transparent re-bootstrap so the host
44
+ * app never has to manage auth state.
45
+ *
46
+ * * **Message → POST.** On each ``runAgent`` call the agent finds the
47
+ * latest user message in ``input.messages`` and posts its content to
48
+ * ``/v1/website/sessions/{id}/messages`` with the CSRF header.
49
+ *
50
+ * * **SSE → AG-UI.** The channel's native event stream is converted
51
+ * to AG-UI events by :class:`Translator` frame-by-frame. The agent
52
+ * opens a fresh EventSource per run (with ``?after=<cursor>`` to
53
+ * skip already-processed frames) and closes it after a short grace
54
+ * drain on turn completion so trailing server frames (late
55
+ * ``memory.update``, ``session.done``) advance the cursor rather
56
+ * than replaying on the next run.
57
+ *
58
+ * * **RUN lifecycle.** AG-UI requires every run to be bracketed by
59
+ * ``RUN_STARTED`` and exactly one of ``RUN_FINISHED`` / ``RUN_ERROR``.
60
+ * Surogates has no such sentinel on the wire, so the agent emits
61
+ * them itself; a per-run ``terminated`` flag guarantees at most one
62
+ * terminal event even under concurrent SSE ``onerror`` + translator
63
+ * end-of-turn detection.
64
+ */
65
+
66
+ /**
67
+ * Constructor config for :class:`WebsiteAgent`. Extends AG-UI's
68
+ * base ``AgentConfig`` with the two Surogates-specific fields the
69
+ * bootstrap needs; everything else (``agentId``, ``threadId``,
70
+ * ``initialMessages``, ``initialState``, ``debug``) is inherited
71
+ * unchanged so consumers reuse the AG-UI patterns they already know.
72
+ */
73
+ interface WebsiteAgentConfig extends AgentConfig {
74
+ /** Base URL of the Surogates API, e.g. ``https://agent.acme.com``. */
75
+ apiUrl: string;
76
+ /** Publishable key issued by ops (``surg_wk_...``). */
77
+ publishableKey: string;
78
+ }
79
+ declare class WebsiteAgent extends AbstractAgent {
80
+ readonly apiUrl: string;
81
+ readonly publishableKey: string;
82
+ /** Cached bootstrap result. ``undefined`` until the first ``run()``. */
83
+ private bootstrapResult;
84
+ /** Monotonic cursor across runs. Passed to the SSE ``?after=`` param. */
85
+ private cursor;
86
+ constructor(config: WebsiteAgentConfig);
87
+ /**
88
+ * Ensure we have a live session cookie + CSRF token.
89
+ *
90
+ * Idempotent; returns the cached result when one already exists.
91
+ * ``bootstrap()`` is the only call that requires the publishable
92
+ * key to travel in an Authorization header, so callers that want
93
+ * to eagerly validate their configuration (e.g. to surface a
94
+ * setup error at widget-load time) can invoke this directly.
95
+ */
96
+ ensureBootstrapped(): Promise<BootstrapResult>;
97
+ /**
98
+ * Close the current session on the server side and drop local state.
99
+ *
100
+ * Callers should invoke this when the visitor closes the chat UI
101
+ * so the server can mark the session complete instead of waiting
102
+ * for the idle-reset timer. Safe to call when no session has been
103
+ * bootstrapped yet.
104
+ */
105
+ end(): Promise<void>;
106
+ /**
107
+ * Implementation of the AG-UI ``run()`` contract.
108
+ *
109
+ * Returns a cold observable: one subscription per run. AG-UI's
110
+ * ``runAgent`` pipeline pipes this through chunk transformation,
111
+ * verification, and subscriber notifications before updating
112
+ * ``this.messages``.
113
+ */
114
+ run(input: RunAgentInput): Observable<BaseEvent>;
115
+ /**
116
+ * Async body of the observable factory.
117
+ *
118
+ * Emits ``RUN_STARTED`` up front so every downstream failure is
119
+ * well-bracketed by the AG-UI lifecycle. ``RUN_FINISHED`` is
120
+ * emitted by the SSE listeners when the translator detects end-of-
121
+ * turn; ``RUN_ERROR`` is emitted from the catch below when bootstrap
122
+ * or message-send fails before the SSE has any chance to close out.
123
+ *
124
+ * The ``ctx.terminated`` guard is the single enforcement point for
125
+ * AG-UI's "exactly one terminal event" invariant. Every emission
126
+ * site checks it before calling ``subscriber.next`` with RUN_FINISHED
127
+ * or RUN_ERROR, and every terminal call sets it to true first.
128
+ */
129
+ private driveRun;
130
+ /**
131
+ * Tear down whatever EventSource is currently live on ``ctx`` and
132
+ * open a fresh one against *sessionId* starting at *after*. Called
133
+ * both for the initial stream and after cookie recovery. Always
134
+ * clears the stale ``onerror`` first so the old source can't race
135
+ * a ``RUN_ERROR`` into the subscriber after it's been replaced.
136
+ */
137
+ private replaceStreamSource;
138
+ /**
139
+ * Try ``sendMessage``; on auth failure re-bootstrap once and retry.
140
+ *
141
+ * The re-bootstrap mints a new session id, so the caller passes an
142
+ * ``onRebootstrap`` callback that reopens the SSE stream against the
143
+ * fresh session before the retry. Visitor continuity across
144
+ * bootstraps is a deliberate non-feature for v1; the new session
145
+ * starts empty. Only frames emitted on the OLD session between
146
+ * SSE-open and send-failure are lost -- and those are always
147
+ * previous-turn events, since the current turn's ``sendMessage`` is
148
+ * what 401-ed.
149
+ */
150
+ private sendWithCookieRecovery;
151
+ /**
152
+ * Wire up the EventSource to translate and forward every frame,
153
+ * and terminate the observable when the translator flags
154
+ * end-of-turn or a transport failure occurs.
155
+ *
156
+ * After the translator flips end-of-turn we emit ``RUN_FINISHED``
157
+ * immediately, then keep the stream open for ``DRAIN_WINDOW_MS``
158
+ * so trailing frames (late ``memory.update``, the sentinel
159
+ * ``session.done``) advance ``this.cursor`` rather than arriving
160
+ * on the next run. The ``ctx.terminated`` gate stops additional
161
+ * subscriber emissions during the drain.
162
+ */
163
+ private attachStreamListeners;
164
+ /**
165
+ * Find the last user-authored message body to post. AG-UI supports
166
+ * multimodal content arrays; this v1 only handles string content
167
+ * (the website channel's ``POST /messages`` endpoint accepts plain
168
+ * text). Multimodal support lands once the server grows a media
169
+ * upload path.
170
+ */
171
+ private extractUserText;
172
+ }
173
+
174
+ export { WebsiteAgent as W, type WebsiteAgentConfig as a };
@@ -0,0 +1,174 @@
1
+ import { AbstractAgent, AgentConfig, RunAgentInput, BaseEvent } from '@ag-ui/client';
2
+ import { Observable } from 'rxjs';
3
+
4
+ /**
5
+ * HTTP + SSE wire layer.
6
+ *
7
+ * Pure functions that talk to the website channel's three endpoints,
8
+ * isolated here so :class:`WebsiteAgentClient` can stay focused on
9
+ * state-machine orchestration. All fetches use ``credentials:
10
+ * 'include'`` so the browser sends the HttpOnly session cookie that
11
+ * ``bootstrap`` set -- without it the message/SSE/end routes return
12
+ * 401 and the whole channel falls apart.
13
+ *
14
+ * SSE is handled by the native ``EventSource`` rather than a custom
15
+ * fetch-based parser because the native implementation is zero bytes,
16
+ * auto-reconnects on network drops, and respects browser-internal
17
+ * backoff heuristics. The one thing it can't do is resume with a
18
+ * server-side cursor, so we manage the ``?after=N`` reopen logic
19
+ * explicitly in :class:`WebsiteAgentClient`.
20
+ */
21
+ interface BootstrapResult {
22
+ sessionId: string;
23
+ csrfToken: string;
24
+ expiresAt: number;
25
+ agentName: string;
26
+ }
27
+
28
+ /**
29
+ * AG-UI ``AbstractAgent`` implementation for the Surogates public-website channel.
30
+ *
31
+ * A ``WebsiteAgent`` bridges the channel-specific HTTP protocol
32
+ * (publishable-key bootstrap, HttpOnly cookie, CSRF double-submit,
33
+ * ``/v1/website/sessions/{id}/events`` SSE) onto AG-UI's standard
34
+ * ``run()`` contract so any AG-UI-aware host app -- CopilotKit,
35
+ * custom chat UIs, LangGraph-style clients -- can talk to a Surogates
36
+ * backend with no custom glue beyond instantiation.
37
+ *
38
+ * Responsibilities
39
+ * ----------------
40
+ * * **Lazy bootstrap.** The first ``runAgent`` call POSTs to
41
+ * ``/v1/website/sessions`` to exchange the publishable key for a
42
+ * session cookie + CSRF token; subsequent calls reuse them. A cookie
43
+ * expiry (HTTP 401) triggers a transparent re-bootstrap so the host
44
+ * app never has to manage auth state.
45
+ *
46
+ * * **Message → POST.** On each ``runAgent`` call the agent finds the
47
+ * latest user message in ``input.messages`` and posts its content to
48
+ * ``/v1/website/sessions/{id}/messages`` with the CSRF header.
49
+ *
50
+ * * **SSE → AG-UI.** The channel's native event stream is converted
51
+ * to AG-UI events by :class:`Translator` frame-by-frame. The agent
52
+ * opens a fresh EventSource per run (with ``?after=<cursor>`` to
53
+ * skip already-processed frames) and closes it after a short grace
54
+ * drain on turn completion so trailing server frames (late
55
+ * ``memory.update``, ``session.done``) advance the cursor rather
56
+ * than replaying on the next run.
57
+ *
58
+ * * **RUN lifecycle.** AG-UI requires every run to be bracketed by
59
+ * ``RUN_STARTED`` and exactly one of ``RUN_FINISHED`` / ``RUN_ERROR``.
60
+ * Surogates has no such sentinel on the wire, so the agent emits
61
+ * them itself; a per-run ``terminated`` flag guarantees at most one
62
+ * terminal event even under concurrent SSE ``onerror`` + translator
63
+ * end-of-turn detection.
64
+ */
65
+
66
+ /**
67
+ * Constructor config for :class:`WebsiteAgent`. Extends AG-UI's
68
+ * base ``AgentConfig`` with the two Surogates-specific fields the
69
+ * bootstrap needs; everything else (``agentId``, ``threadId``,
70
+ * ``initialMessages``, ``initialState``, ``debug``) is inherited
71
+ * unchanged so consumers reuse the AG-UI patterns they already know.
72
+ */
73
+ interface WebsiteAgentConfig extends AgentConfig {
74
+ /** Base URL of the Surogates API, e.g. ``https://agent.acme.com``. */
75
+ apiUrl: string;
76
+ /** Publishable key issued by ops (``surg_wk_...``). */
77
+ publishableKey: string;
78
+ }
79
+ declare class WebsiteAgent extends AbstractAgent {
80
+ readonly apiUrl: string;
81
+ readonly publishableKey: string;
82
+ /** Cached bootstrap result. ``undefined`` until the first ``run()``. */
83
+ private bootstrapResult;
84
+ /** Monotonic cursor across runs. Passed to the SSE ``?after=`` param. */
85
+ private cursor;
86
+ constructor(config: WebsiteAgentConfig);
87
+ /**
88
+ * Ensure we have a live session cookie + CSRF token.
89
+ *
90
+ * Idempotent; returns the cached result when one already exists.
91
+ * ``bootstrap()`` is the only call that requires the publishable
92
+ * key to travel in an Authorization header, so callers that want
93
+ * to eagerly validate their configuration (e.g. to surface a
94
+ * setup error at widget-load time) can invoke this directly.
95
+ */
96
+ ensureBootstrapped(): Promise<BootstrapResult>;
97
+ /**
98
+ * Close the current session on the server side and drop local state.
99
+ *
100
+ * Callers should invoke this when the visitor closes the chat UI
101
+ * so the server can mark the session complete instead of waiting
102
+ * for the idle-reset timer. Safe to call when no session has been
103
+ * bootstrapped yet.
104
+ */
105
+ end(): Promise<void>;
106
+ /**
107
+ * Implementation of the AG-UI ``run()`` contract.
108
+ *
109
+ * Returns a cold observable: one subscription per run. AG-UI's
110
+ * ``runAgent`` pipeline pipes this through chunk transformation,
111
+ * verification, and subscriber notifications before updating
112
+ * ``this.messages``.
113
+ */
114
+ run(input: RunAgentInput): Observable<BaseEvent>;
115
+ /**
116
+ * Async body of the observable factory.
117
+ *
118
+ * Emits ``RUN_STARTED`` up front so every downstream failure is
119
+ * well-bracketed by the AG-UI lifecycle. ``RUN_FINISHED`` is
120
+ * emitted by the SSE listeners when the translator detects end-of-
121
+ * turn; ``RUN_ERROR`` is emitted from the catch below when bootstrap
122
+ * or message-send fails before the SSE has any chance to close out.
123
+ *
124
+ * The ``ctx.terminated`` guard is the single enforcement point for
125
+ * AG-UI's "exactly one terminal event" invariant. Every emission
126
+ * site checks it before calling ``subscriber.next`` with RUN_FINISHED
127
+ * or RUN_ERROR, and every terminal call sets it to true first.
128
+ */
129
+ private driveRun;
130
+ /**
131
+ * Tear down whatever EventSource is currently live on ``ctx`` and
132
+ * open a fresh one against *sessionId* starting at *after*. Called
133
+ * both for the initial stream and after cookie recovery. Always
134
+ * clears the stale ``onerror`` first so the old source can't race
135
+ * a ``RUN_ERROR`` into the subscriber after it's been replaced.
136
+ */
137
+ private replaceStreamSource;
138
+ /**
139
+ * Try ``sendMessage``; on auth failure re-bootstrap once and retry.
140
+ *
141
+ * The re-bootstrap mints a new session id, so the caller passes an
142
+ * ``onRebootstrap`` callback that reopens the SSE stream against the
143
+ * fresh session before the retry. Visitor continuity across
144
+ * bootstraps is a deliberate non-feature for v1; the new session
145
+ * starts empty. Only frames emitted on the OLD session between
146
+ * SSE-open and send-failure are lost -- and those are always
147
+ * previous-turn events, since the current turn's ``sendMessage`` is
148
+ * what 401-ed.
149
+ */
150
+ private sendWithCookieRecovery;
151
+ /**
152
+ * Wire up the EventSource to translate and forward every frame,
153
+ * and terminate the observable when the translator flags
154
+ * end-of-turn or a transport failure occurs.
155
+ *
156
+ * After the translator flips end-of-turn we emit ``RUN_FINISHED``
157
+ * immediately, then keep the stream open for ``DRAIN_WINDOW_MS``
158
+ * so trailing frames (late ``memory.update``, the sentinel
159
+ * ``session.done``) advance ``this.cursor`` rather than arriving
160
+ * on the next run. The ``ctx.terminated`` gate stops additional
161
+ * subscriber emissions during the drain.
162
+ */
163
+ private attachStreamListeners;
164
+ /**
165
+ * Find the last user-authored message body to post. AG-UI supports
166
+ * multimodal content arrays; this v1 only handles string content
167
+ * (the website channel's ``POST /messages`` endpoint accepts plain
168
+ * text). Multimodal support lands once the server grows a media
169
+ * upload path.
170
+ */
171
+ private extractUserText;
172
+ }
173
+
174
+ export { WebsiteAgent as W, type WebsiteAgentConfig as a };