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

omnix-chat

v1.5.6

Published

Embeddable Agent chat SDK for the browser. Configure with a DSN and drop a chat widget into any web page.

Readme

omnix-chat

Embeddable Agent chat SDK for the browser. Configure with a DSN, drop a chat widget into any web page, and let your end users talk to your agent backend.

  • 🪶 Headless coreomnix-chat is a small client; the full UI ships in omnix-chat/react with antd 6 + @ant-design/x bundled in (host does not need to install or upgrade antd).
  • 🔌 React only — install react + react-dom as peers; no antd 6, no @ant-design/x in your app.
  • 🛡 Host isolation — light-DOM shell with .ac-embedded-host-scoped static CSS, antd css-in-js confined to .ac-embedded-mount, and overlays in .ac-popup-layer (safe alongside antd 4 or other UI libraries).
  • 📦 ES2020 defaultomnix-chat/react is post-built to ES2020; webpack 5 / Vite hosts need no extraBabelIncludes / extraBabelPlugins for the package.
  • 🧩 Legacy entryomnix-chat/react/legacy (ES2018) for Umi 3 / nodeModulesTransform: 'none'.
  • 🖥 Panel layoutssidebar, floating, or fullscreen; compact session rail with expand/collapse; macOS-style window controls in floating mode.
  • 📍 Page context — route info always sent; optional extended pageContext with preview in the input footer.
  • ✍️ Write confirmation — destructive writes pause on an inline card on the assistant bubble (no fullscreen modal).
  • 🔧 Host tools — register browser-side tools; SSE host_action runs them after scope handlers.

Status: public beta. API surface is stable; backend HTTP contract may evolve.

Which entry should I use?

| Host stack | Import | Babel on node_modules | |------------|--------|-------------------------| | Vite / webpack 5 (modern) | omnix-chat/react | Not required | | Umi 3 / old Babel | omnix-chat/react/legacy | Not required |

Both entries bundle the same UI (antd 6 inside the package). Only syntax level differs.

Quick start

中文用法指南:docs/用法指南.zh-CN.md · Umi 改造:docs/umi-integration.zh-CN.md

CDN drop-in (<script> tag)

Only React is required besides the SDK — antd and @ant-design/x are bundled inside omnix-chat/react.

<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/omnix-chat@latest/dist/react.umd.js"></script>
<script>
  // Use the named export from the UMD build (see dist types for full API).
  const { AgentChat } = OmnixChatReact;
</script>

For the headless client (AgentChat.init without pre-built UI):

<script src="https://unpkg.com/omnix-chat@latest/dist/omnix-chat.umd.js"></script>
<script>
  OmnixChat.init({ dsn: 'your-app-dsn', baseUrl: 'https://api.example.com' });
</script>

npm + React(推荐,开箱即用)

无需在宿主项目安装 antd 6 或 @ant-design/x — 它们已内置在 omnix-chat/react 中。

pnpm add omnix-chat react react-dom
import { AgentChat } from 'omnix-chat/react';

export function App() {
  return (
    <AgentChat
      dsn="your-app-dsn"
      baseUrl="https://api.example.com"
      accountToken="optional-host-token"
      project={{ name: 'My Assistant', logo: '/logo.png' }}
      locale="zh-CN"
    />
  );
}

Umi 3 / webpack 5(推荐 legacy 入口)

Umi 3 请使用 omnix-chat/react/legacy,并关闭对 node_modules 的 Babel 转译(否则 会把包内预编译产物再跑一遍 Babel,出现 @babel/runtime/regenerator 等解析错误):

pnpm add omnix-chat react react-dom
import { AgentChat } from 'omnix-chat/react/legacy';
// config/config.ts 或 .umirc.ts
export default {
  nodeModulesTransform: {
    type: 'none',
  },
  // 若仍走 MFSU 预构建,可排除 omnix-chat:
  // mfsu: { exclude: ['omnix-chat'] },
};

react / react-dom必装 peer。包内 不发布 dist/recharts-*.js 等 async chunk,避免 webpack 在 node_modules/omnix-chat/dist 下解析失败。

npm + vanilla JavaScript(无 UI)

pnpm add omnix-chat
import { AgentChat } from 'omnix-chat';

AgentChat.init({
  dsn: 'your-app-dsn',
  baseUrl: 'https://api.example.com',
  locale: 'zh-CN',
});

React(自定义布局)

import { AgentChatProvider, AgentChatEmbedded, useAgentChat } from 'omnix-chat/react';

export function App() {
  return (
    <AgentChatProvider dsn="your-app-dsn" baseUrl="https://api.example.com">
      <YourApp />
      <ChatShell />
    </AgentChatProvider>
  );
}

function ChatShell() {
  const { instance, isOpen, open, close } = useAgentChat();
  return (
    <>
      <button type="button" onClick={isOpen ? close : open}>
        {isOpen ? 'Hide chat' : 'Need help?'}
      </button>
      <AgentChatEmbedded client={instance} />
    </>
  );
}

DSN format

A DSN is a URL-shaped string that encodes everything the SDK needs to talk to your backend:

https://<publicKey>@<host>[:<port>][/<basePath>]/<projectId>
  • publicKey — the project's public key. Visible on the client; never use it to sign sensitive operations.
  • host/port/basePath — origin and base path of your agent API. The SDK computes apiBase = ${host}:${port}${basePath}/v1.
  • projectId — string or numeric project identifier.

Examples:

https://[email protected]/42
https://[email protected]:8443/api/edge/proj-7
agent://[email protected]/9       # alias, normalized to https://

Parsing / normalization:

import { normalizeDsn, DEFAULT_BASE_URL } from 'omnix-chat';

const dsn = normalizeDsn('your-app-dsn');
await AgentChat.init({ dsn, baseUrl: DEFAULT_BASE_URL });

Configuration reference

| Option | Type | Default | Description | | --- | --- | --- | --- | | dsn | string | — | Required. See DSN format. | | user | UserContext | — | End-user identity. Forwarded to the backend at handshake time. | | locale | string | navigator.language | BCP-47 locale (en, zh-CN, …). Drives both SDK and antd copy. | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Visual theme. auto follows prefers-color-scheme. | | themeToken | Record<string, unknown> | — | antd theme token overrides (e.g. { colorPrimary: '#7c3aed' }). | | position | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | Where the floating launcher anchors. | | container | HTMLElement \| string | document.body | Where the widget host (.ac-embedded-host) is appended. | | autoOpen | boolean | false | Open the panel as soon as the widget mounts. | | baseUrl | string | — | Agent API origin when not fully encoded in the DSN. | | accountToken | string | — | Optional host auth token forwarded on API calls. | | project | { name, logo?, … } | — | Branding shown in the panel header. | | panelMode | 'sidebar' \| 'floating' \| 'fullscreen' | 'sidebar' | Initial panel layout. | | panelModeSwitchable | boolean | true | Let users switch layout from the header menu. | | pageContext | AgentChatPageContext | — | Extended business context (entity, metadata, …). | | attachPageContext | boolean | false | Default for the input-footer checkbox that merges pageContext into the next send. Route info is always sent. | | debug | boolean | false | Verbose console logs and unhandled-error warnings. | | headers | Record<string, string> | — | Extra HTTP headers added to every backend call. | | onReady | () => void | — | Convenience alias for instance.on('ready', ...). |

Events

const off = AgentChat.on('message', ({ message, source }) => {
  console.log(source, message.content.text);
});
off(); // unsubscribe

| Event | Payload | Notes | | --- | --- | --- | | ready | void | Fired after init() settles. | | error | { code, message, cause? } | Any SDK-level error. Subscribe in production to forward into Sentry/Datadog. | | open / close | void | The chat panel opened/closed. | | session | { sessionId, conversationId } | Active chat session changed. | | sessions | { sessions: ChatSessionSummary[] } | Sidebar session list refreshed (GET /chat). | | message | { message, source } | A user, agent, or system message hit the timeline (agent may stream incrementally). | | hostAction | { action } | Agent requests a host-page side effect (refresh data, navigate, …). | | destroyed | void | The instance was torn down. |

Host page actions

See docs/host-page-context-and-actions.md for the full spec: pageContext request format, SSE host_action envelope, field alignment, and integration examples.

When the agent needs the host page to run browser-side tools, the backend emits SSE host_action with a hostTools list (instead of legacy type: refresh).

Backend envelope (SSE event host_action, or message with action: host_action):

{
  "action": "host_action",
  "scope": "campaign-detail",
  "entity": { "type": "campaign", "id": "123" },
  "hostTools": [
    { "name": "refreshEntity", "args": { "entityType": "campaign", "entityId": "123" } }
  ],
  "reason": "agent_mutation_success",
  "runId": 42,
  "turnId": 7
}
  • scope must match pageContext.page on the user message.
  • hostTools is required (server only pushes when non-empty).
  • reason: plan_host_tool (mid-run Plan step) or agent_mutation_success (after HTTP write).

React page component — register while mounted:

import { useAgentChat, useHostAction, HOST_ACTION_REASON } from 'omnix-chat/react';

function CampaignDetailPage({ campaignId }: { campaignId: string }) {
  const { instance } = useAgentChat();
  const { refetch } = useCampaignDetail(campaignId);

  useEffect(() => {
    instance.setPageContext({
      page: 'campaign-detail',
      entity: { type: 'campaign', id: campaignId },
    });
  }, [campaignId, instance]);

  useHostAction('campaign-detail', async (action) => {
    if (action.entity?.id && action.entity.id !== campaignId) return;
    if (action.reason === HOST_ACTION_REASON.AGENT_MUTATION_SUCCESS) {
      await refetch();
    }
  });

  return <CampaignView />;
}

hostTools are executed automatically by the SDK; use useHostAction for guards and extra logic keyed on reason.

Imperative / non-React pages:

import { registerHostAction, HOST_ACTION_REASON } from 'omnix-chat';

const off = registerHostAction('campaign-detail', (action) => {
  if (action.entity?.id && action.entity.id !== campaignId) return;
  if (action.reason === HOST_ACTION_REASON.AGENT_MUTATION_SUCCESS) {
    loadCampaignData();
  }
});
// later: off();

The SDK also emits instance.on('hostAction', ({ action }) => …) if you prefer listening on the client instead of the registry.

Host tool registration

Register named tools the agent can invoke via hostTools in SSE host_action:

import {
  registerHostTool,
  registerHostToolsWithSync,
  type HostToolDefinition,
} from 'omnix-chat/react';

registerHostTool('refreshEntity', async (args) => {
  await refetchCampaign(args.entityId as string);
  return { ok: true };
});

// Optional: sync tool metadata to the backend catalog
await registerHostToolsWithSync(instance, [
  {
    name: 'refreshEntity',
    description: 'Refetch a campaign entity after agent writes',
    parameters: { type: 'object', properties: { entityId: { type: 'string' } } },
  } satisfies HostToolDefinition,
]);

registerHostToolsWithSync calls POST /host-tool/client/register so the agent knows which tools exist on this page. runHostTool is available for imperative calls; the SDK auto-runs matching hostTools entries after registerHostAction handlers when an SSE host_action arrives.

Migration (v1.5.0): host_action no longer requires status: 'completed'. Payloads must include non-empty hostTools and a scope matching pageContext.page. See CHANGELOG.

Page context

Route information (routePath, routeParams) is always attached to user messages. Extended fields (page, entity, metadata, …) are sent only when the user enables attach page context (footer checkbox) or you set attachPageContext: true on init.

<AgentChat
  pageContext={{ page: 'campaign-detail', entity: { type: 'campaign', id } }}
  attachPageContext={false}
/>
instance.setPageContext({ page: 'campaign-detail', entity: { type: 'campaign', id } });
instance.setAttachPageContext(true); // or toggle via UI checkbox

The input footer shows a preview icon: inspect route + extended fields and the JSON that will be sent on the next message. Full spec: docs/host-page-context-and-actions.md.

Write confirmation

When the agent proposes a destructive write, the backend emits confirmation_required on the SSE stream. The SDK shows an inline card on the assistant message (not a modal) with confirm / cancel actions.

// Programmatic (same as clicking the inline buttons)
await instance.confirmWrite();
await instance.cancelWrite();

Listen for UI state via getState().writeConfirmation or subscribe to state changes after confirmation_required / complete events.

Panel layout

| Mode | Behaviour | | --- | --- | | sidebar | Docked panel with session rail on the left (default). | | floating | Draggable window with macOS-style close / minimize / fullscreen controls. | | fullscreen | Occupies the host viewport; session rail collapses to a slim icon strip. |

In sidebar and floating, the session rail is 116px when expanded (titles visible) and 40px when collapsed. Use panelModeSwitchable: false to lock the layout, or instance.setPanelMode('floating') imperatively.

Lifecycle

AgentChat.init({ dsn });
AgentChat.open();
AgentChat.identify({ id: 'u-99' });
// Resolves with the **user** message once POST succeeds.
// Assistant replies arrive via `message` events (SSE stream per sessionId).
const userMsg = await AgentChat.sendMessage('Hello!');

AgentChat.selectSession('existing-session-id'); // switch sidebar session

AgentChat.destroy();    // clean up DOM, abort in-flight requests
AgentChat.init({ dsn }); // safe to re-init after destroy

For multi-tenant pages use createAgentChat() to obtain independent instances.

React Strict Mode

If you call the imperative AgentChat.init() from inside a React useEffect under <StrictMode>, dev-mode will run setup → cleanup → setup, which otherwise causes the launcher to flash on screen. Prefer <AgentChat dsn="..." /> from omnix-chat/react, which handles init/teardown for you. If you still call the imperative API from an effect, guard with a deferred destroy:

const ref = useRef<{ key: string | null; timer: number | null }>({ key: null, timer: null });

useEffect(() => {
  if (ref.current.timer != null) {
    clearTimeout(ref.current.timer);
    ref.current.timer = null;
  }
  if (ref.current.key !== dsn) {
    if (ref.current.key) AgentChat.destroy();
    AgentChat.init({ dsn });
    ref.current.key = dsn;
  }
  return () => {
    ref.current.timer = window.setTimeout(() => {
      AgentChat.destroy();
      ref.current.key = null;
      ref.current.timer = null;
    }, 0);
  };
}, [dsn]);

Multi-session chat & streaming

The widget shows a left sidebar of conversations (GET /chat). Selecting one loads GET /chat/{sessionId} into the message pane.

sendMessage() pipeline (SDK)

User hits Send
  → optimistic user bubble (pending) + notifyStateChange     ← UI shows immediately
  → ensure sessionId
  → GET /chat/{sessionId}/stream (SSE connected)
  → POST user message (existing session only)
  → SSE events → agent bubble updates + notifyStateChange    ← UI streams assistant

Existing session (has activeSessionId):

| Order | HTTP | Purpose | | --- | --- | --- | | 1 | GET /chat/{sessionId}/stream | Listen before agent runs | | 2 | POST /chat/{sessionId}/messages | { role: "user", content } triggers agent | | 3 | same SSE | think / result / complete / error |

First message (no session yet):

| Order | HTTP | Purpose | | --- | --- | --- | | 1 | POST /chat | { role, content }{ sessionId } + starts agent (backend contract) | | 2 | GET /chat/{sessionId}/stream | Subscribe (ReplaySubject replays recent events) | | — | skip POST .../messages | User text already persisted in step 1 |

Other APIs

| Step | HTTP | Notes | | --- | --- | --- | | List sessions | GET /chat | Sidebar | | Load session | GET /chat/{sessionId} | selectSession() / prefetch() |

SSE → UI mapping lives in src/core/chat-sse.ts. React reads getState().messages via useClientState in ChatBody (useSyncExternalStore + notifyStateChange).

sendMessage() resolves when the user bubble is sent or failed. Assistant content streams through repeated message events and state updates.

Backend HTTP contract (legacy v1 sketch)

The SDK historically documented two endpoints:

POST {apiBase}/sessions

Headers: X-Agent-Public-Key, X-Agent-Project-Id, X-Agent-Sdk-Version, Content-Type: application/json.

Body:

{
  "locale": "zh-CN",
  "sdkVersion": "1.5.0",
  "origin": "https://app.example.com",
  "user": { "id": "u-123", "email": "[email protected]" },
  "existingConversationId": "conv_abc"
}

Response (200):

{
  "sessionId": "sess_...",
  "sessionToken": "tok_...",
  "expiresAt": 1731567890000,
  "conversation": { "id": "conv_...", "messages": [] }
}

POST {apiBase}/messages

Headers: Authorization: Bearer <sessionToken>, plus the same X-Agent-* headers.

Body:

{
  "conversationId": "conv_abc",
  "content": { "type": "text", "text": "Hello!" }
}

Response (200):

{
  "message": {
    "id": "msg_...",
    "role": "agent",
    "content": { "type": "text", "text": "Hi there!" },
    "createdAt": 1731567890000,
    "status": "sent"
  }
}

Errors should follow the shape { "error": { "code": "STRING_CODE", "message": "Human readable" } } so the SDK can surface them as typed AgentChatError instances.

Security

  • The DSN's publicKey is public. Any rate-limiting or origin allow-list must be enforced server-side (the SDK forwards Origin in the handshake body so you can check it).
  • For trusted user identification pass an HMAC of the user id as user.signature (validate it on the backend with a shared secret).
  • The SDK never executes eval or new Function. Static widget CSS is injected once under #agent-chat-embedded-styles with every selector prefixed by .ac-embedded-host; antd css-in-js stays inside .ac-embedded-mount. CSP typically needs style-src 'unsafe-inline' for css-in-js.

Local development

pnpm install
pnpm dev          # runs the demo playground at http://localhost:5173
pnpm build        # produces dist/omnix-chat.es.js + .umd.js + react.es.js + .umd.js + types
pnpm build:watch  # rebuild dist/ on src/ changes (use while link-debugging in another app)
pnpm pack:local   # build + pack/omnix-chat-x.y.z.tgz for offline install
pnpm typecheck
pnpm size         # asserts gzipped budgets

Link into your local app (pnpm)

# SDK repo — build once (or run build:watch in another terminal)
cd /path/to/agent-chat
pnpm install && pnpm build

# Your app — symlink the package
cd /path/to/your-app
pnpm add "link:/path/to/agent-chat"

In your app:

import { AgentChat } from 'omnix-chat/react';

While developing the SDK, keep pnpm build:watch running in the agent-chat repo; your app picks up changes after each rebuild (Vite may need a refresh).

Tarball instead of symlink: pnpm pack:local then pnpm add /path/to/agent-chat/pack/omnix-chat-*.tgz.

See examples/react/README.md for a minimal consumer app.

The playground intercepts /api/edge/*/v1/sessions and /api/edge/*/v1/messages via a built-in Vite middleware, so you can develop without a real backend.

License

MIT