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

@myflowai/web-sdk

v0.5.0

Published

Browser SDK for embedding the FlowAI agent orchestrator (chat + voice) on any webpage.

Downloads

436

Readme

@myflowai/web-sdk

Browser SDK for embedding the FlowAI agent orchestrator on any webpage. Wraps the orchestrator's WebSocket chat transport and ElevenLabs-powered voice flow behind a single typed event-emitter surface.

Distribution: published to public npmjs.com as @myflowai/web-sdk and served as a UMD bundle (window.FlowAI) via unpkg / jsDelivr. The orchestrator endpoint is baked into the bundle — integrators do not configure URLs.

Versioned (recommended):

https://unpkg.com/@myflowai/[email protected]/dist/flowai.min.js

@latest works too but pins your page to whatever ships next — not recommended for production.

Quick start — chat

<script src="https://unpkg.com/@myflowai/[email protected]/dist/flowai.min.js"></script>
<script>
  const chat = new FlowAI.ChatClient({
    tenantId: "acme",
    agentId: "support-v1",
  });

  // Assistant reply streams token-by-token. A turn can produce several
  // message segments (see "public event surface" below); `message` closes
  // each and `final` ends the turn.
  chat.on("token", (t) => appendDelta(t.turnId, t.text));
  chat.on("message", (m) => sealSegment(m.turnId, m.text));
  chat.on("final", () => endTurn());
  // `agent_message` is a human supervisor message (warm handoff). It may
  // also carry `attachments[]` when the supervisor sent files / images.
  chat.on("agent_message", (m) => {
    render({ from: "human", text: m.text });
    for (const a of m.attachments ?? []) renderAttachment(a);
  });
  chat.on("transfer_to_human", (e) => showHandoffNotice(e));
  chat.on("error", (e) => console.error(e.message));

  await chat.connect();
  chat.send("Hello, I need help with my order.");
</script>

Quick start — file & image attachments

Two-step upload: Upload the attachment and refer the attachment in the user message.

<input type="file" id="picker" accept="image/*,application/pdf" />
<script>
  document.getElementById("picker").addEventListener("change", async (ev) => {
    const file = ev.target.files[0];
    if (!file) return;

    // 1. Upload (presign + PUT). Returns an AttachmentRef including a
    //    client-side SHA-256 for integrity tracking.
    const ref = await chat.uploadAttachment(file, {
      onProgress: (f) => setBar(`${Math.round(f * 100)}%`),
    });

    // 2. Send a turn referencing the attachment. Pass an empty text for
    //    image-only sends, or text + attachments together.
    chat.send("What do you see in this picture?", [ref]);
  });
</script>

Rendering inbound attachments

When a supervisor sends a file, the agent_message event carries attachments[]. Each ref includes a short-TTL viewUrl for immediate rendering, plus an s3Key you can use with the GET fallback if the URL is stale (e.g. after a page reload).

chat.on("agent_message", (m) => {
  for (const a of m.attachments ?? []) {
    const src = chat.attachmentUrl(a);
    if (a.mimeType.startsWith("image/")) {
      const img = document.createElement("img");
      img.src = src;
      img.alt = a.filename ?? a.attachmentId;
      transcriptEl.appendChild(img);
    } else {
      const link = document.createElement("a");
      link.href = src;
      link.target = "_blank";
      link.textContent = a.filename ?? a.attachmentId;
      transcriptEl.appendChild(link);
    }
  }
});

Supported types & limits

  • Images: JPEG, PNG, WebP, GIF, HEIC, HEIF.
  • Documents: PDF.
  • Max size: 20 MB.

Quick start — voice (ElevenLabs)

<script src="https://unpkg.com/@myflowai/[email protected]/dist/flowai.min.js"></script>
<script src="https://unpkg.com/@elevenlabs/client@latest/dist/lib.umd.js"></script>
<script>
  const voice = new FlowAI.VoiceClient({
    tenantId: "acme",
    agentId: "support-v1", // optional
    elevenlabsAgentId: "el_abc",
    // The chat bundle externalizes @elevenlabs/client; load it yourself
    // (above) and pass a tiny loader that returns it from window.
    elevenlabsClientLoader: () => Promise.resolve(window.ElevenLabs),
  });

  voice.on("transcript", (t) => console.log(`${t.role}: ${t.text}`));
  voice.on("audioState", (s) => console.log("audio:", s.state));
  voice.on("start", (s) => console.log("session:", s.sessionId));

  await voice.start();
</script>

On start(), the SDK first calls POST /v1/sessions to create the orchestrator session (seeded with the tenant's config), then starts the ElevenLabs call — injecting session_id, tenant_id, and agent_id as dynamic variables. The tenant's ElevenLabs agent must:

  1. Use the orchestrator's /v1/chat/completions endpoint as its Custom LLM.
  2. Forward those dynamic variables as request headers — configure X-Session-Id: {{session_id}}, X-Tenant-Id: {{tenant_id}}, and X-Agent-Id: {{agent_id}} in the Custom LLM "Request headers" section so the orchestrator can resolve the session per turn.

To attach to a session you created yourself, pass sessionId (and agentId) and the SDK skips the create call.

ChatClient — public event surface

The SDK exposes the assistant's streamed reply (token / final) plus a minimal control-plane surface. Tool calls, KB retrievals, and other wire-internal events are consumed internally and not surfaced.

| Event | Payload | When | |---|---|---| | ready | { sessionId } | Init handshake completed | | token | { turnId, text } | Incremental assistant delta for the current message segment | | message | { turnId, text } | A completed assistant message segment — its authoritative full text | | final | { turnId, text, stopReason } | Turn boundary (no per-segment content; use for typing/inflight reset) | | agent_message | { text, source: "human", at, messageId, attachments? } | A human supervisor sent a message (possibly with files/images) | | transfer_to_human | { reason?, destination?, transferNote? } | Warm handoff to a human agent | | error | { message } | Protocol-level error | | close | { code, reason } | Socket closed |

A single turn can produce multiple assistant messages (one per graph node that yields text — e.g. a pre-tool "let me check…" followed by the answer). Render each as its own bubble:

  • On token, append the delta to the current bubble.
  • On message, snap that bubble to the authoritative text and seal it — the next token starts a fresh bubble.
  • On final, the turn is over: clear the typing indicator / inflight state. Do not render final.text (segment content already arrived via message).
let bubble = null;
chat.on("token", (t) => { (bubble ??= newBubble()).textContent += t.text; });
chat.on("message", (m) => { (bubble ??= newBubble()).textContent = m.text; bubble = null; });
chat.on("final", () => { bubble = null; stopTypingIndicator(); });

Do not rely on final.text being the whole turn — in a multi-node turn it carries only the last node's text. Use message for content.

Behavior

  • Barge-in: calling chat.send(text) while a turn is still streaming on the server first emits a cancel frame, then submits the new message. The previous turn's events are dropped.

  • Reconnect: transient closes (network drop, server shutdown 4503) trigger automatic reconnect with exponential backoff using the saved sessionId — the conversation continues on a fresh task. Terminal closes (4400 / 4404 / 4422) do not retry.

  • Persist session across page reloads — the SDK doesn't store anything; wire it yourself:

    const saved = sessionStorage.getItem("flowai.sessionId") ?? undefined;
    const chat = new FlowAI.ChatClient({ tenantId, agentId, sessionId: saved });
    chat.on("ready", (e) => sessionStorage.setItem("flowai.sessionId", e.sessionId));