npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@invergent/website-widget

v1.6.8

Published

AG-UI-compatible TypeScript client for the Surogates public-website channel. Wraps the publishable-key bootstrap, HttpOnly cookie, CSRF double-submit, and SSE streaming behind a standard AbstractAgent so widgets built for AG-UI work out of the box.

Readme

@invergent/website-widget

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 AbstractAgent so any widget built against AG-UI works on top of Surogates with no custom glue.

What you get

import { WebsiteAgent } from '@invergent/website-widget';

const agent = new WebsiteAgent({
  apiUrl: 'https://agent.acme.com',
  publishableKey: 'surg_wk_...',
});

agent.subscribe({
  onTextMessageContentEvent: ({ event }) => renderDelta(event.delta),
  onToolCallStartEvent:      ({ event }) => showToolPill(event.toolCallName),
  onRunFinishedEvent:        () => markDone(),
  onRunErrorEvent:           ({ event }) => showError(event.message),
});

agent.addMessage({ role: 'user', content: 'How do I cancel my subscription?' });
await agent.runAgent();

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.

Why AG-UI

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:

  • Typed streaming text (TEXT_MESSAGE_START / CONTENT / END)
  • Typed tool calls with incremental argument streaming (TOOL_CALL_*)
  • Reasoning visibility (REASONING_*)
  • Run lifecycle (RUN_STARTED / RUN_FINISHED / RUN_ERROR)
  • Step tracking (STEP_STARTED / STEP_FINISHED) for sub-agents and expert delegation
  • Middleware, subscribers, and state management out of the box

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.

Install

pnpm add @invergent/website-widget @ag-ui/client @ag-ui/core rxjs

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

CDN / <script> tag

For plain HTML sites without a bundler, use the IIFE build from a CDN. It bundles AG-UI and RxJS:

<script src="https://cdn.surogates.com/widget/v1/surogates-widget.global.js"></script>
<script>
  const agent = new SurogatesWidget.WebsiteAgent({
    apiUrl: 'https://agent.acme.com',
    publishableKey: 'surg_wk_...',
  });

  agent.subscribe({
    onTextMessageContentEvent: ({ event }) => document.body.append(event.delta),
    onRunFinishedEvent: () => console.log('done'),
  });

  agent.addMessage({ role: 'user', content: 'hello' });
  agent.runAgent();
</script>

The IIFE exposes WebsiteAgent, EventType, AbstractAgent, the error classes, and the Translator on window.SurogatesWidget.

API

new WebsiteAgent(config)

| Option | Type | Notes | |---|---|---| | apiUrl | string, required | Base URL of the Surogates API (e.g. https://agent.acme.com). No trailing slash required. | | publishableKey | string, required | surg_wk_... key configured at deploy time via website.publishable_key. Safe to embed in browser JS. | | threadId | string, optional | AG-UI thread id. One is minted if not provided. | | agentId | string, optional | AG-UI agent id. | | initialMessages | Message[], optional | Pre-populated conversation history. | | initialState | State, optional | Pre-populated agent state. |

Plus every other field accepted by AG-UI's AgentConfig.

Inherited from AbstractAgent

  • runAgent(parameters?, subscriber?): Promise<RunAgentResult> — primary entry point
  • subscribe(subscriber): { unsubscribe() }
  • addMessage(message) / addMessages(messages) / setMessages(messages)
  • abortRun()
  • messages, state, threadId, agentId

See AG-UI docs for the full interface.

Additional methods

ensureBootstrapped(): Promise<BootstrapResult>

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

end(): Promise<void>

Mark the server-side session completed and clear the session cookie. Call when the visitor closes your chat UI.

Error taxonomy

Every error the SDK throws or emits via RUN_ERROR derives from SurogatesError:

| Class | When | |---|---| | SurogatesAuthError | Publishable key invalid, Origin not in allow-list, CSRF mismatch. Non-retryable. | | SurogatesRateLimitError | HTTP 429 or per-session message cap reached. Exposes retryAfter (seconds). | | SurogatesProtocolError | Malformed response, SDK/server version mismatch. Priority-1 diagnostic signal. | | SurogatesNetworkError | Network blip, DNS, CORS preflight refusal. Retryable. |

Event mapping

The Surogates server emits event types defined in surogates/session/events.py. They map to AG-UI as follows:

| Surogates | AG-UI | Notes | |---|---|---| | llm.delta | TEXT_MESSAGE_CHUNK (role=assistant) | Expanded to TEXT_MESSAGE_START/CONTENT/END by AG-UI's client transform | | llm.response | — (closes the running chunk stream) | Also drives end-of-turn detection | | llm.thinking | REASONING_START + REASONING_MESSAGE_START + REASONING_MESSAGE_CONTENT | | | tool.call | TOOL_CALL_CHUNK | Full args in one chunk; AG-UI expands to TOOL_CALL_START/ARGS/END | | tool.result | TOOL_CALL_RESULT | | | expert.delegation | STEP_STARTED (stepName=expert:<name>) | | | expert.result | STEP_FINISHED | | | session.fail, harness.crash | RUN_ERROR | Terminal for the run | | session.done, session.complete | closes stream + emits RUN_FINISHED | | | policy.denied, memory.update, context.compact, and every other Surogates-specific event | CUSTOM | name carries the original Surogates type | | user.message, llm.request, session.start, sandbox.*, policy.allowed, harness.wake | dropped | Internal orchestration, not user-facing |

Plus the lifecycle envelope every run is wrapped in: RUN_STARTED at the top, RUN_FINISHED or RUN_ERROR at the bottom.

Security model

The agent enforces the website channel's security contract transparently:

  • Publishable key is sent only on bootstrap, only to the configured apiUrl, as Authorization: Bearer. Never persisted by the SDK.
  • 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.
  • Session cookie is HttpOnly + Secure + SameSite=None, Path=/. Set by the server, managed by the browser.
  • 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.

See the website channel documentation for the full threat model and the server-side invariants.

Development

pnpm install       # first time
pnpm test          # vitest
pnpm typecheck     # tsc --noEmit
pnpm build         # ESM + CJS + IIFE to ./dist

Bundle size

Measured on the current build:

| Target | Raw | Gzipped | |---|---|---| | ESM (dist/index.js) | 21 KB | 6 KB | | CJS (dist/index.cjs) | 22 KB | 6 KB | | IIFE (dist/surogates-widget.global.js) | 285 KB | 66 KB |

The npm/ESM numbers exclude AG-UI, RxJS, and zod (peer deps). The IIFE bundles everything for script-tag users.

Versioning

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.

License

AGPL-3.0-or-later (same as the parent Surogates project).