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

@spekoai/sdk

v0.5.3

Published

Official Speko TypeScript SDK — one API, every voice provider

Readme

@spekoai/sdk

Official TypeScript SDK for Speko — one API, every voice provider.

Speko is a voice AI gateway that benchmarks every STT, LLM, and TTS provider across languages, then routes each request to the best provider in real time. Failover is handled. You write one integration; Speko picks the right provider for every call.

Installation

npm install @spekoai/sdk
# or
pnpm add @spekoai/sdk

Quickstart

import { Speko } from '@spekoai/sdk';
import { readFile } from 'node:fs/promises';

const speko = new Speko({ apiKey: process.env.SPEKO_API_KEY });

// Transcribe — best STT provider auto-routed for your language
const audio = await readFile('./call.wav');
const { text, provider, confidence } = await speko.transcribe(audio, {
  language: 'es-MX',
  region: 'us-east4', // optional — rank streaming providers in this region
});

// Synthesize — best TTS provider auto-routed
const speech = await speko.synthesize('Hello world', {
  language: 'en',
});

// Complete — best LLM provider auto-routed
const { text: reply } = await speko.complete({
  messages: [{ role: 'user', content: 'Hi!' }],
  intent: { language: 'en' },
});

// Streaming variants are also available:
// speko.transcribeStream(...), speko.synthesizeStream(...), speko.completeStream(...)

The client accepts baseURL as an alias for baseUrl — e.g. new Speko({ apiKey, baseURL: process.env.SPEKO_BASE_URL }). If both are set, baseUrl wins.

Registered tools

Tools registered against an agent (via speko.agents.tools.create(...) or the dashboard) can be loaded and handed straight to complete(). listChatTools(agentId) fetches the agent's tools and converts every source kind — inline, webhook, builtin, and integration — into the ChatTool[] shape complete() expects:

const speko = new Speko({
  apiKey: process.env.SPEKO_API_KEY,
  baseURL: process.env.SPEKO_BASE_URL,
});

// Fetch once, then pass straight to complete()
const tools = await speko.agents.tools.listChatTools(agentId);

const { text, toolCalls } = await speko.complete({
  messages: [{ role: 'user', content: 'Book me a slot tomorrow at 3pm' }],
  intent: { language: 'en' },
  tools,
});

Webhook, builtin, and integration tools run server-side and are folded back into the response; inline tools come back to you as toolCalls to execute yourself.

Programmable voice (call control)

speko.callControl drives human calls — on a browser softphone, phones on the PSTN — the way Telnyx Call Control does. Every leg of a call has an opaque controlId, and every verb is addressed to that handle, so a Telnyx integration ports one command to one command.

// Come online so inbound can ring you, and hold the presence connection open.
import { PRESENCE_STALE_AFTER_MS } from '@spekoai/sdk';

// The API key authenticates the org; brokerId identifies this softphone.
const speko = new Speko({ apiKey: process.env.SPEKO_API_KEY, brokerId: 'broker-42' });

await speko.callControl.register();
const presence = await speko.callControl.presenceToken(); // → { token, url, ... }
setInterval(() => speko.callControl.heartbeat(), PRESENCE_STALE_AFTER_MS / 3);

// Dial out. The call comes back with its legs attached, plus the room
// credentials for your own browser leg — you must be in the room before the far
// end answers, or the first moments are silent.
const { call, join } = await speko.callControl.dial({ to: '+12015551234' });
const mine = call.legs.find((leg) => leg.kind === 'browser')!;
const theirs = call.legs.find((leg) => leg.kind === 'pstn')!;
// In the browser, join the room with those credentials:
// VoiceConversation.create({ transportToken: join.token, transportUrl: join.url })

await speko.callControl.mute(mine.controlId);
// DTMF is addressed to the leg the tones are *for* — the customer's PSTN leg.
// (Speko relays them out through an in-room browser leg, because only a
// participant inside the room can publish SIP DTMF, but that's internal:
// naming a non-PSTN leg fails with UNSUPPORTED_COMMAND.)
await speko.callControl.dtmf(theirs.controlId, { digits: '1w2' });
await speko.callControl.transfer(mine.controlId, { to: '+12015559876', mode: 'warm' });
await speko.callControl.hangup(mine.controlId, { reason: 'resolved' });

Answering an inbound ring is the mirror image. A RingOffer arrives on the presence connection carrying your own leg's controlId; take a room token for it, connect, then answer:

// message: PresenceMessage, parsed off the presence data channel
if (message.type === 'incoming_call') {
  const credentials = await speko.callControl.join(message.controlId);
  await VoiceConversation.create({
    transportToken: credentials.token,
    transportUrl: credentials.url,
  });
  await speko.callControl.answer(message.controlId); // join the room first, then answer
}

The full verb set is answer, hangup, bridge, hold, unhold, mute, unmute, dtmf, transfer — each resolving to the leg's post-command state, so you never re-read to find out what you just did. get, list, and events read calls back; events is the recorded equivalent of the Telnyx webhook stream. The same events can be delivered as workspace webhooks (call.initiatedcall.hangup); those are sent once, without automatic retry, so treat events as the durable record and reconcile from it.

Broker identity

The API key authenticates the organization. brokerId, supplied once to new Speko({ apiKey, brokerId }), identifies the softphone. The SDK includes it automatically on dial, join, register, heartbeat, setStatus, and presenceToken.

The command surface — answer, hangup, hold/unhold, mute/unmute, dtmf, bridge, transfer, plus get, list, events — accepts API keys on purpose: server-side automation acting on a live call is the point of a programmable-voice API, and those are scoped by the leg's organization.

Two things differ from Telnyx and will bite if you assume otherwise:

  • hold is silent. It is synthesized from mute + unsubscribe; with no music-on-hold source configured the held party hears nothing at all. Say so in your UI — callers read silence as a dropped call.
  • There is no SIP registrar. Desk phones, third-party softphones, and other PBXes cannot register and be rung. Human legs are browser legs; browser and pstn are the only human-reachable leg kinds.

Errors carry stable codes on SpekoApiError.code, exported as VOICE_ERROR_CODES so you can branch without string-matching prose.

speko.voice.dial(...) is a different thing and is unchanged: it dials an AI agent out over the same telephony gateway.

Documentation

Full API reference and guides: https://docs.speko.dev

Contributing

See CONTRIBUTING.md.

License

MIT