# ZRT — Zero Runtime JS SDK

**Build real-time AI voice agents in JavaScript / TypeScript — without running the infrastructure.**
You write the agent (instructions, tools, logic); **Zero Runtime** runs the live
speech-to-speech pipeline — speech-to-text → LLM → text-to-speech, with turn
detection, denoising, and interruptions — at low latency in the cloud.

> **Write the agent. We run the runtime.**

The **ZRT JS SDK** — build voice agents in JavaScript / TypeScript with idiomatic
conventions (ESM, a clean options-object API) over the **Zero Runtime**.

## A different kind of voice SDK

Most voice frameworks make you run the hard part — media servers, GPUs, turn-taking,
autoscaling. No-code platforms hide all that but lock you into a dashboard.
**Zero Runtime is the middle:** real TypeScript and your own providers, with none of the
real-time infrastructure to operate.

| | Self-hosted frameworks | No-code platforms | **Zero Runtime** |
|---|:---:|:---:|:---:|
| Write real code + custom tools | ✅ | ❌ (dashboard) | ✅ |
| Run media servers / GPUs / scaling | ❌ *you run it* | ✅ managed | ✅ managed |
| Swap any STT / LLM / TTS provider | ✅ | limited | ✅ |
| Low-latency speech-to-speech | you tune it | managed | managed |

## Requirements

- Node.js **18+**
- A ZRT auth token (from [app.zeroruntime.ai](https://app.zeroruntime.ai))
- API key(s) for the providers you use (e.g. Deepgram, Google, Cartesia)

## Install

```bash
npm install @zrt/js-sdk
```

## Quickstart

**1. Set your environment**

```bash
export ZRT_AUTH_TOKEN=<your-token>       # from app.zeroruntime.ai

export DEEPGRAM_API_KEY=<key>    # speech-to-text
export GOOGLE_API_KEY=<key>      # LLM (Gemini)
export CARTESIA_API_KEY=<key>    # text-to-speech
```

**2. Write your agent** — `agent.ts`

```typescript
import { Agent, Pipeline, Room, serve, invoke, functionTool } from '@zrt/js-sdk';
import { CartesiaTTS, DeepgramSTT, GoogleLLM, SileroVAD, TurnDetector } from '@zrt/js-sdk/plugins';

const AGENT_ID = 'assistant';

const getWeather = functionTool({
  name: 'get_weather',
  description: 'Get the current weather for a city.',
  parameters: {
    type: 'object',
    properties: { city: { type: 'string', description: 'Name of the city to look up.' } },
    required: ['city'],
  },
  execute: async ({ city }: { city: string }) => ({
    city,
    temperature_c: 28,
    condition: 'Sunny',
    humidity: 55,
  }),
});

class Assistant extends Agent {
  constructor() {
    super(
      'You are a friendly voice assistant. Keep replies short and natural. ' +
        'When asked about the weather, call the get_weather tool.',
      {
        name: 'Assistant',
        agentId: AGENT_ID,
        tools: [getWeather],
        pipeline: Pipeline({
          stt: DeepgramSTT({ model: 'nova-2-conversationalai' }),
          llm: GoogleLLM({ model: 'gemini-3-flash-preview', thinkingBudget: 0 }),
          tts: CartesiaTTS({ model: 'sonic-3.5' }),
          vad: SileroVAD(),
          turnDetector: TurnDetector({ model: 'echo-large' }),
        }),
      },
    );
  }

  async onEnter(): Promise<void> {
    await this.session!.say("Hi! I'm your assistant. Ask me about the weather in any city.");
  }

  async onExit(): Promise<void> {
    await this.session!.say('Thanks for calling. Goodbye!');
  }
}

// Register the agent, then start a session once it is ready.
serve(new Assistant(), {
  onReady: () => invoke(AGENT_ID, { room: Room({ playground: true }) }),
});
```

**3. Run it**

```bash
npx tsx agent.ts
```

That's it — speech in → your agent → speech out, in real time.

## How it works

| Piece | What it is |
|---|---|
| **`Agent`** | Your behavior — instructions, tools, what it says on enter/exit. |
| **`Pipeline`** | The voice stack: STT (hear) → LLM (think) → TTS (speak), plus VAD, turn detection, and denoising. |
| **`serve`** | Registers your agent with the Zero Runtime and serves dispatched sessions. |
| **`invoke`** | Starts a session for a registered agent — call it from anywhere. |

## Give your agent tools

Tools are TypeScript functions the LLM can call. Because JavaScript has no runtime type
introspection, each is declared explicitly with `functionTool({ name, description, parameters,
execute })` — the JSON-schema `parameters` describe the arguments — then passed to the agent
via `tools: [...]` (see `get_weather` in the quickstart above). Your tool runs in your worker;
the runtime calls it when the LLM decides to.

## Providers

Mix and match — bring the best model for each stage, swap any one in a line:

- **Speech-to-text (STT):** Deepgram, AssemblyAI, Google, Azure, Gladia, NVIDIA, Sarvam
- **LLM:** OpenAI, Google Gemini, Anthropic Claude, AWS Bedrock, Groq, Cerebras, xAI Grok, Sarvam, CometAPI
- **Text-to-speech (TTS):** Cartesia, ElevenLabs, Google, AWS Polly, Azure, Rime, LMNT, Neuphonic, Hume AI, Inworld, Murf, Resemble, Smallest, Speechify, CambAI, NVIDIA, Papla
- **Realtime speech-to-speech:** OpenAI Realtime, Gemini Live, Ultravox, Azure Voice Live, xAI
- **Turn detection:** Namo · **VAD:** Silero · **Denoise:** RNNoise

```typescript
import { ElevenLabsTTS, AnthropicLLM } from '@zrt/js-sdk/plugins';   // swap any provider
```

All providers are importable from the single `@zrt/js-sdk/plugins` entry point or individually from `@zrt/js-sdk/plugins/<name>` for
finer-grained imports.

## Use cases

Phone & telephony agents, IVR replacement, customer-support voice bots, voice
assistants, outbound/inbound call automation, and any real-time conversational AI.

## API conventions

This SDK follows idiomatic JavaScript / TypeScript conventions:

- A consistent public API: every option uses the same naming style.
- Providers and components are created from a **single options object**, with no `new`
  (`GoogleLLM({ maxOutputTokens: 8192 })`).
- Tools are declared with an **explicit `functionTool({ name, description, parameters, execute })`**
  descriptor.
- **Settings map cleanly to the runtime** — option names, configuration keys, and event
  names are forwarded exactly as the Zero Runtime expects.

## FAQ

**How is this different from a voice-agent framework?**
* Frameworks make you host and scale the real-time runtime (media, GPUs, turn-taking).
ZRT runs that for you — you only write and deploy the agent.

**How is it different from a no-code voice platform?**
* You write real TypeScript with your own tools, logic, and providers — not a dashboard
configuration. Full code control, zero infrastructure.

**Can I use my own STT / LLM / TTS providers?**
* Yes — mix any supported providers, and bring your own API keys.

**What do I need to run it?**
* A ZRT runtime endpoint + token and the provider keys for the stages you use.

## Resources

- **Docs:** https://docs.zeroruntime.ai/
- **Examples:** https://github.com/ZeroRuntimeAI/zrt-js-sdk-examples
- **Dashboard & tokens:** https://app.zeroruntime.ai
- **Also available for Python:** https://pypi.org/project/zrt/
- **Support:** support@zeroruntime.ai

Copyright © 2026 Zujo Tech Pvt Ltd. All rights reserved.
