> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kupe.in/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript realtime

> Mint a kupe-realtime session with npm install kupe-sdk and stream one text turn over WebSocket.

Mint with `kupe.realtime.sessions.create`, then `kupe.realtime.connect(session)`. Audio is PCM16 mono **24 kHz**. Model `kupe-realtime`. Web-only — no telephony minutes.

Identify the agent with **name** or **agent\_id**. A new name creates the agent with prompt, greeting, voice, and tools/mcp. Pass **voice** (sanitized name) or **voice\_id** — either one (copy the id from the voice library).

## Terminal

Node 18+ (ESM). From a folder where `kupe-sdk` is installed:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
npm install kupe-sdk
export KUPE_API_KEY=sk-kupe-YOUR_KEY
node --input-type=module <<'EOF'
import { Kupe } from "kupe-sdk";

const kupe = new Kupe();
const session = await kupe.realtime.sessions.create({
  name: "Priya",
  voice: "priya",
  prompt: "You collect overdue EMIs. Be warm and brief.",
  greeting: "Hi, this is Priya from the bank.",
});
console.log("ws", session.websocket_url);

const rt = await kupe.realtime.connect(session);
rt.sendText("Remind them EMI is due tomorrow.");
for await (const event of rt) {
  if (event.type === "response.output_audio_transcript.done") {
    console.log("agent:", event.transcript);
    rt.close();
    break;
  }
  if (event.type === "error") {
    console.error("error:", event);
    rt.close();
    break;
  }
}
EOF
```

File form (`realtime.mjs`):

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Kupe } from "kupe-sdk";

const kupe = new Kupe();
const session = await kupe.realtime.sessions.create({
  name: "Priya",
  voice: "priya",
  prompt: "You collect overdue EMIs. Be warm and brief.",
  greeting: "Hi, this is Priya from the bank.",
});
const rt = await kupe.realtime.connect(session);
rt.sendText("Remind them EMI is due tomorrow.");
for await (const event of rt) {
  if (event.type === "response.output_audio_transcript.done") {
    console.log("agent:", event.transcript);
    rt.close();
    break;
  }
}
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
node realtime.mjs
```

## Events you will see

| `event.type`                            | Meaning                                |
| --------------------------------------- | -------------------------------------- |
| `session.created`                       | Handshake done                         |
| `response.output_audio.delta`           | Base64 PCM16 chunk                     |
| `response.output_audio_transcript.done` | Final spoken text (`event.transcript`) |
| `response.done`                         | Turn finished, includes usage          |
| `error`                                 | Protocol or runtime error              |

`sendText` / `send_text` send `conversation.item.create` then `response.create`. Mic path: `rt.appendAudio(pcm16)` at 24 kHz.

## Echo: speakers vs headset

If you play the agent's audio through **open speakers** next to the mic, the
mic records the agent and `appendAudio` sends its own voice back as user
speech. The agent then answers itself, and its greeting shows up in the
transcript as a user turn.

Pass `echoSuppression: "half_duplex"` to mute the mic while the agent is
still speaking:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const rt = await kupe.realtime.connect(session, { echoSuppression: "half_duplex" });

rt.appendAudio(pcm16); // sent as silence while the agent talks
```

| Option             | Use when                                                                                                        | Barge-in                                                    |
| ------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `"none"` (default) | Headset, phone line, or a browser `getUserMedia({ echoCancellation: true })` stream — echo is already cancelled | Yes                                                         |
| `"half_duplex"`    | Open speakers, e.g. a Node script on a laptop                                                                   | **No** — the server does not hear you until the agent stops |

`appendAudio` returns `true` when the chunk was sent and `false` when it was
muted; `rt.suppressedChunks` counts the muted chunks, and `rt.agentIsSpeaking`
exposes the gate. Pass `{ force: true }` to bypass it for one chunk. Tune the
hold after playback ends with `echoTailMs` (default `250`).

A muted chunk is still sent, as silence. The server runs streaming VAD and STT
over a continuous audio stream, so sending nothing at all would stall turn
detection and the agent would stop hearing you even after it stopped talking.

<Note>
  In the browser you normally want the default `"none"`: request the mic with
  `getUserMedia({ audio: { echoCancellation: true } })` and the browser cancels
  the echo for you, keeping barge-in working.
</Note>

Full event tables: [Realtime WebSocket](/realtime).
