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

@convbased/sdk

v0.1.0

Published

Convbased real-time voice conversion SDK — connect to the signaling service with an API key and stream audio through WebRTC.

Readme

@convbased/sdk

Browser SDK for the Convbased voice services. Authenticate with an API key and:

  • Real-time voice conversion — capture the microphone and stream the converted audio back over WebRTC (ConvbasedClient).
  • File inference (voice-to-voice) — upload an audio file and convert it as a discrete task within a live session (ConvbasedClient.runFileInference).
  • Text-to-speech — synthesize speech from text with a reference voice over GraphQL (TtsClient).

The SDK speaks the same signaling protocol as the official Convbased Web console (/signaling/ws on ServerAPI/signaling) and is the easiest way to embed Convbased voice features in third-party web apps.

Demo

Two ready-made examples ship in examples/:

  • examples/h5/ — single-file HTML demo that loads the SDK from the official CDN:
    <script src="https://cdn.weights.chat/sdk/convbased-sdk.global.js"></script>
  • examples/vanilla-ts/main.ts — a framework-free TypeScript snippet you can drop into a Vite / Vue / React project to wire the SDK to an existing <audio> tag. See tts.ts and file-inference.ts alongside it for the text-to-speech and voice-to-voice flows.

Install

bun add @convbased/sdk
# or
npm install @convbased/sdk

Quick start

import { ConvbasedClient } from "@convbased/sdk";

const client = new ConvbasedClient({
	apiKey: "sk_********************************",
	// Signaling + GraphQL endpoints default to the production Convbased
	// service (wss://api.weights.chat/api/signaling/ws), so an API key is
	// the only required field. Override `signalingUrl` / `graphqlUrl` for
	// self-hosted deployments.
});

client.on("track", ({ stream }) => {
	// `stream` is your own voice after conversion — play it to hear the result.
	const audio = document.querySelector<HTMLAudioElement>("#converted")!;
	audio.srcObject = stream;
	void audio.play();
});

client.on("state", ({ state }) => console.log("state:", state));
client.on("error", (err) => console.error(err));

await client.connect({
	modelId: "model_xxx",
	preferences: {
		pitch: 0,
		rms_mix_rate: 0.25,
		f0_autotune: false,
	},
});

// Adjust pitch live:
client.updateConfig({ pitch: 2 });

// End the session:
await client.disconnect();

Endpoints

The production endpoints are baked into the SDK and exposed as constants:

import {
	DEFAULT_SIGNALING_URL, // wss://api.weights.chat/api/signaling/ws
	DEFAULT_GRAPHQL_URL,   // https://api.weights.chat/api/v1/graphql
} from "@convbased/sdk";
  • For the official Convbased service, don't pass signalingUrl / graphqlUrl — the defaults are correct.
  • For a self-hosted deployment, override either or both. signalingUrl accepts a full final URL (ending in /ws), the bare …/signaling path, or just a host (ws://localhost:3010 → SDK appends /signaling/ws).
  • To disable the TURN auto-fetch entirely, pass graphqlUrl: false. The SDK will then use iceServers if provided, or public Google STUN as a last resort.

Authentication

Pass either:

  • apiKey — long-lived key created in the Convbased dashboard. Sent as ?api_key=… on the WebSocket and x-api-key on the GraphQL fetch.
  • accessToken — short-lived JWT obtained via /auth/login. Sent as ?token=… and Authorization: Bearer ….

The signaling server rejects connections with code 1008 if the credential is invalid or the account has zero balance.

Reusing an existing MediaStream

Pass a captured stream to skip getUserMedia (useful with custom effect pipelines like noise suppression or pitch-shift workers):

const raw = await navigator.mediaDevices.getUserMedia({ audio: true });
const processed = await applyEffects(raw);
await client.connect({ modelId, audio: processed });

File inference (voice-to-voice)

Convert a whole audio file through the model instead of the live mic. This runs as a discrete task inside an existing live session, so you must connect() first (that's what provisions the inference node). The converted result is returned as a presigned download URL, not as a live track.

await client.connect({ modelId: "model_xxx" });

// One-call helper: upload, start the task, wait for completion.
const result = await client.runFileInference({
	audio: fileInput.files[0], // a File/Blob, or pass `audioKey` if already uploaded
	preferences: { pitch: 2, f0_method: "rmvpe" },
	onProgress: ({ progress }) => console.log(`progress: ${progress}`),
});
console.log("converted audio:", result.downloadUrl);

Prefer the lower-level API when you want to drive the task lifecycle yourself:

const { key } = await client.uploadAudio(file);
const taskId = client.startTask({ audioKey: key, preferences: { pitch: 2 } });

client.on("taskAck", ({ status, queuePosition }) => { /* queued | started */ });
client.on("taskProgress", ({ progress }) => { /* 0..1 */ });
client.on("taskFinished", ({ status, downloadUrl, error }) => { /* … */ });

client.stopTask(taskId); // cancel

Must be connected first. The inference node enforces that a task only runs once the live session is SERVICE_READY (offer → answer → model loaded). Calling startTask / runFileInference before connect() resolves throws; sending a task to a not-yet-ready node fails it with "service not ready".

Text-to-speech

TtsClient is a standalone, GraphQL-only client — it does not open a WebSocket or a peer connection. Synthesis is asynchronous (submit → poll), wrapped by a one-call synthesize():

import { TtsClient } from "@convbased/sdk";

const tts = new TtsClient({ apiKey: "sk_********************************" });

// `referenceAudio` is the voice to clone; pass a previously uploaded
// `referenceKey` instead to skip the upload.
const result = await tts.synthesize({
	referenceAudio: referenceFile, // a File/Blob
	text: "你好,这是一段合成语音。",
	params: { temperature: 0.8 },
	onJob: (job) => console.log(job.status, "queue:", job.position),
});

const audio = document.querySelector<HTMLAudioElement>("#tts")!;
audio.src = result.url!; // presigned, valid ~1h

Lower-level pieces are available too: tts.uploadReferenceAudio(file), tts.submit({ referenceKey, text, params }), tts.getJob(jobId), tts.cancel(jobId), and tts.getPricing().

Audio upload limits. Both client.uploadAudio and tts.uploadReferenceAudio are validated by the service on filename extension — one of mp3, wav, ogg, flac, m4a, aac — and a max size of 100 MB. When uploading a bare Blob (no filename), pass { filename: "source.wav" } so the extension check passes.

Events

| Event | Payload | Notes | | -------------- | -------------------------------------------------- | --------------------------------------------------------------------- | | state | { state, previous } | idle → signaling → negotiating → connecting → connected → closed | | message | { code?, message?, raw } | Every JSON frame the server sends. | | ready | { code: 3009, message? } | Inference node has loaded the model and converted audio is flowing. | | track | { stream, track } | The converted audio track to wire into an <audio> element. | | taskAck | { taskId, status, queuePosition?, code? } | File-inference task accepted (queued / started). | | taskProgress | { taskId, progress, code? } | File-inference progress, progress in [0, 1]. | | taskFinished | { taskId, status, resultKey?, downloadUrl?, error? } | File-inference terminal state (success / failure / cancelled). | | error | Error | Connection or server-reported failure. | | closed | { code?, reason? } | The signaling socket has gone away. |

API surface

ConvbasedClient — live conversion + file inference

  • new ConvbasedClient(options)
  • client.connect({ modelId, audio?, preferences?, sampleRate? })
  • client.updateConfig(preferences)
  • client.setMuted(muted)
  • client.replaceLocalStream(stream)
  • client.getStats(){ rttMs, jitter, packetsLost }
  • client.getConvertedStream() / client.getPeerConnection()
  • client.uploadAudio(file, opts?){ key }
  • client.runFileInference({ audio? | audioKey?, preferences?, ... })Promise<TaskFinishedEvent>
  • client.startTask({ audioKey, preferences?, ... }) / client.stopTask(taskId?)
  • client.disconnect()

TtsClient — text-to-speech (GraphQL only)

  • new TtsClient(options)
  • tts.synthesize({ referenceAudio? | referenceKey?, text, params?, ... })Promise<TtsResult>
  • tts.uploadReferenceAudio(file, opts?){ key }
  • tts.submit({ referenceKey, text, params? }) / tts.getJob(jobId) / tts.cancel(jobId)
  • tts.getPricing(){ pricePerToken, minCharge }

Notes

  • The SDK is browser-only. WebRTC support in pure Node requires wrtc or aiortc, which are out of scope here.
  • One client = one session. Create a new instance after disconnect().
  • The server rejects upfront if the wallet is empty, so a thrown error during connect() may indicate insufficient balance — check error.message.