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

@kuralle-syrinx/browser-client

v4.6.3

Published

Browser client for the Syrinx WebSocket audio protocol — mic capture, opus, jitter buffer, playout progress reporting, reconnect

Readme

@kuralle-syrinx/browser-client

The browser SDK for the Syrinx WebSocket voice protocol. Microphone capture, opus encoding, jitter-buffered playback, reconnect — and the typed message stream a voice agent sends back.

This is what you build your own voice UI on. Syrinx Studio is built entirely on it; so is anything you want to embed voice into.

npm install @kuralle-syrinx/browser-client

Talk to an agent

import { SyrinxBrowserClient } from "@kuralle-syrinx/browser-client";

const client = new SyrinxBrowserClient({
  url: "ws://localhost:4173/ws",
  audioContext: new AudioContext(),
  jitterBuffer: { targetBufferMs: 100 },
});

client.on((event) => {
  if (event.type === "message" && event.message.type === "stt_output") {
    console.log("you said:", event.message.transcript);
  }
  if (event.type === "message" && event.message.type === "agent_chunk") {
    process.stdout.write(event.message.text);
  }
});

await client.connect();

Send a turn as text instead of speaking — useful for iterating on prompts and tools without paying STT/TTS latency or cost:

client.sendText("What's the deadline for the CS masters?");

What the server sends you

SyrinxStudioMessage is a discriminated union of roughly twenty variants. The ones you will reach for first:

| Message | Carries | | --- | --- | | ready | negotiated sample rates, encoding, resume window | | speech_started / speech_ended | turn boundaries | | stt_chunk / stt_output | interim and final transcript (+ confidence) | | agent_chunk / agent_end | the reply, streamed | | agent_tool_call / agent_tool_result | tool use | | tool_call_started | delayed | complete | failed | the "thinking" cue lifecycle | | agent_interrupted | barge-in, with a reason | | turn_complete, tts_chunk, tts_end | turn and audio lifecycle | | metrics | per-turn latency decomposition | | error | component, category, message |

Handle unknown types gracefully. On Cloudflare the agents SDK also emits cf_agent_identity and cf_agent_mcp_servers, which are not in this union, and future versions will add more. Ignore what you do not recognise rather than throwing.

Structured session state — /record

Folding that stream by hand is tedious and easy to get wrong. The /record subpath does it for you, and is dependency-free and DOM-free so it also runs under Node (in a test, a CLI, or CI):

import { buildSessionRecord } from "@kuralle-syrinx/browser-client/record";

const record = buildSessionRecord(messages.map((m, i) => ({ message: m, atMs: i })));

record.turns[0]?.userTranscript;  // what the caller said
record.turns[0]?.agentText;       // what the agent replied
record.turns[0]?.interrupted;     // { atMs, reason } when barge-in cut it off
record.turns[0]?.toolCalls;       // merged across all four cue phases
record.turns[0]?.timings;         // optional — see the caveat below

It is a pure reducer: same messages in, same record out, no side effects. That is what lets you render a UI from a recorded fixture in a test instead of needing a live provider.

It is also bounded — 50 turns and 500 events per turn by default, evicting oldest and reporting droppedTurns / droppedEvents rather than losing data silently.

timings is optional and you must handle its absence. It comes from the metrics message, which the Node host emits and the Cloudflare Workers path currently does not. Render a turn without timings rather than assuming they arrive — showing zeroes would read as a zero-latency turn.

Derived views — /agent-state and /turn-timeline

Both are pure, Node-safe, and derived from the same record.

import { deriveAgentState, isStalled } from "@kuralle-syrinx/browser-client/agent-state";
import { buildTurnTimeline } from "@kuralle-syrinx/browser-client/turn-timeline";

deriveAgentState gives you idle → listening → endpointing → thinking → speaking → interrupted — what a client renders as a listening indicator. Note this is conversational state; SessionState in @kuralle-syrinx/core is session lifecycle and is a different thing. isStalled flags a state held far past plausibility, because a thirty-second "thinking" is a hung tool call, not patience.

buildTurnTimeline turns metrics into a latency waterfall with the slowest segment marked. It also flags replies faster than FAST_TURN_FLOOR_MS (700ms) — a sub-second reply is usually the endpointer firing while the caller is still speaking, not a fast agent.

Session aggregates — /session-metrics

import { buildSessionMetrics } from "@kuralle-syrinx/browser-client/session-metrics";

Median / p95 / max per stage across a session, plus the ids of turns that replied implausibly fast. Percentiles are nearest-rank, so every number reported is a measurement that actually occurred rather than an interpolation no turn ever produced.

Exports

| Path | Contents | Runs under Node? | | --- | --- | --- | | . | client, transport, audio, message types | no — needs AudioContext | | ./record | SessionRecord assembler | yes | | ./agent-state | conversational state machine | yes | | ./turn-timeline | per-turn latency waterfall | yes | | ./session-metrics | session aggregates | yes |

Licence

MIT.