rich-monitor-70665
12/30/2025, 8:35 AMpython myagent.py consoletall-airline-16756
12/30/2025, 8:35 AMtall-airline-16756
12/30/2025, 8:35 AMpython myagent.py console
does.
It launches your agent in console mode so you can run and interact with it locally in a terminal for testing and debugging. In this mode the agent connects to a mocked LiveKit room and accepts voice or text input directly from the console, letting you validate agent behavior without a real LiveKit server or live calls. It's intended for development/iteration, not production call handling.
Sources: Simple Call Answering Agent | LiveKit docs | Voice AI quickstart | LiveKit docsrich-monitor-70665
12/30/2025, 8:35 AMtall-airline-16756
12/30/2025, 8:36 AMpython myagent.py console.
What to use
• Use the dev script (e.g., pnpm dev or npm run dev) to start the agent in development mode. This mode lets you run and interact with the agent locally and test behavior without a full production deployment.
Quick setup
1. Add a dev script to package.json, for example:
"scripts": {
"dev": "tsx agent.ts dev"
}
1. Run:
pnpm dev
(or npm run dev)
Notes
• Dev mode connects the JS agent to LiveKit for real-time testing; it doesn’t use the exact mocked-console flow of the Python console command but serves the same development/testing purpose.
• If you want, tell me which JS agent file you have (`agent.ts`/`agent.js`) and your package manager and I’ll suggest the exact script and command. Sources: Voice AI quickstart | LiveKit docsrich-monitor-70665
12/30/2025, 8:36 AMtall-airline-16756
12/30/2025, 8:37 AMnode-record-lpcm16 / mic / naudiodon / sounddevice / Web Audio API)
If you want, say “Node.js + mic + speaker” and I’ll return a minimal working example that:
• captures mic input,
• pushes frames into the AgentSession/RTC track,
• subscribes to agent audio output and plays to speakers,
and note required package.json scripts and audio sample-rate/channel handling.
Sources: Agent speech and audio | LiveKit docs | Playing Audio | LiveKit docs | Simple Content Filter | LiveKit docs | Text-to-speech (TTS) models | LiveKit Docs | Agent session | LiveKit docsrich-monitor-70665
12/30/2025, 8:37 AMtall-airline-16756
12/30/2025, 8:38 AM@livekit/agents, @livekit/rtc, and @livekit/plugins-* you have.
1. Confirm runtime: "Node.js" (desktop) — yes/no?
2. Which TTS provider to include for playback:
◦ "openai" (Cartesia/Advanced Voice),
◦ "cartesia" (if different),
◦ "none" (text-only).
Once you confirm those three items I’ll return a tested, runnable project:
• exact package.json with versions,
• full agent.ts (or .js) with imports,
• concrete mic → publish pipeline using naudiodon,
• precise PCM16 ↔️ Float32 conversion helpers,
• AudioSource/LocalAudioTrack publish code matching the SDK,
• AudioSink subscription and speaker playback,
• env vars and run commands. Sources: Voice AI quickstart | LiveKit docs | OpenAI and LiveKit partner to turn Advanced Voice into an APIrich-monitor-70665
12/30/2025, 8:39 AMtall-airline-16756
12/30/2025, 8:39 AMrich-monitor-70665
12/30/2025, 8:40 AMtall-airline-16756
12/30/2025, 8:40 AMrich-monitor-70665
12/30/2025, 8:40 AMtall-airline-16756
12/30/2025, 8:41 AMrich-monitor-70665
12/30/2025, 8:42 AMtall-airline-16756
12/30/2025, 8:42 AMrich-monitor-70665
12/30/2025, 8:43 AMtall-airline-16756
12/30/2025, 8:44 AMrich-monitor-70665
12/30/2025, 8:44 AMtall-airline-16756
12/30/2025, 8:45 AM{
"name": "livekit-agent-local-audio",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"agent": "tsx src/agent.ts",
"client": "tsx src/client.ts",
"token-server": "tsx src/token-server.ts"
},
"dependencies": {
"@livekit/agents": "latest",
"@livekit/client": "latest",
"@livekit/rtc-node": "latest",
"livekit-server-sdk": "latest",
"livekit-plugins-openai": "latest",
"naudiodon": "latest",
"dotenv": "latest"
},
"devDependencies": {
"typescript": "latest",
"tsx": "latest",
"@types/node": "latest"
}
}
2) tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "src",
"strict": true,
"skipLibCheck": true
},
"include": [
"src/**/*"
]
}
3) .env (copy and fill)
# LiveKit credentials (from LiveKit Console)
LIVEKIT_API_KEY=YOUR_LIVEKIT_API_KEY
LIVEKIT_API_SECRET=YOUR_LIVEKIT_API_SECRET
LIVEKIT_URL=<wss://YOUR_LIVEKIT_URL>
# OpenAI key
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
# Basic server settings
TOKEN_SERVER_PORT=3000
ROOM_NAME=test-room
4) src/token-server.ts
import express from "express";
import dotenv from "dotenv";
import { AccessToken, RoomServiceClient } from "livekit-server-sdk";
dotenv.config();
const PORT = Number(process.env.TOKEN_SERVER_PORT || 3000);
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY!;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET!;
const ROOM_NAME = process.env.ROOM_NAME || "test-room";
if (!LIVEKIT_API_KEY || !LIVEKIT_API_SECRET) {
throw new Error("Set LIVEKIT_API_KEY and LIVEKIT_API_SECRET in .env");
}
const app = express();
app.get("/token/:identity", (req, res) => {
const identity = req.params.identity || `user-${Math.floor(Math.random() * 1000)}`;
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, { identity });
at.addGrant({
room: ROOM_NAME,
roomJoin: true,
canPublish: true,
canSubscribe: true
});
const token = at.toJwt();
res.json({ token, identity, room: ROOM_NAME });
});
app.listen(PORT, () => {
console.log(`Token server running on <http://localhost:${PORT}`>);
console.log(`GET /token/:identity to create a token (room=${ROOM_NAME})`);
});
5) src/agent.ts
import dotenv from "dotenv";
import { defineAgent } from "@livekit/agents";
import * as openai from "livekit-plugins-openai";
dotenv.config();
// Agent process: connects as an agent participant to any room created and runs AgentSession.
// This file is intended to be launched with: pnpm run agent (or npm run agent)
export default defineAgent({
entry: async (ctx) => {
// Connect already handled by CLI runner when using lk agent run / console mode.
// Create AgentSession using OpenAI for STT/LLM/TTS.
const session = new ctx.voice.AgentSession({
stt: new openai.STT({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o-transcribe" // streaming STT model
}),
llm: new openai.LLM({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o-mini"
}),
tts: new openai.TTS({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-tts-1" // or "tts-1" depending on plugin mapping
}),
preemptiveGeneration: true
});
await session.start({
room: ctx.room,
agent: new ctx.voice.Agent({
instructions: "You are a concise helpful voice assistant."
})
});
// Optionally generate an initial prompt
await session.generateReply({
instructions: "Greet the user briefly."
});
// Keep running for the duration of the job
}
});
Notes about agent.ts:
• This file follows the agent definition pattern. In local development you typically run the agent with LiveKit CLI runner (e.g., lk agent run --agent-file src/agent.ts or console mode).
• Ensure your LiveKit CLI is configured with the project credentials or use environment variables when running the agent runner.
6) src/client.ts
/**
* Node client that:
* - requests a token from local token server
* - connects to LiveKit room as a participant using @livekit/client + rtc-node
* - captures microphone audio with naudiodon and publishes as a LocalAudioTrack
* - subscribes to remote audio tracks (agent) and plays them via naudiodon speaker
*
* Run: pnpm run client
*/
import dotenv from "dotenv";
import fetch from "node-fetch";
import { connect, LocalAudioTrack, LocalTrackPublication, RemoteTrack, RemoteAudioTrack } from "@livekit/client";
import { createLocalAudioTrack } from "@livekit/rtc-node";
import Naudi from "naudiodon";
dotenv.config();
const TOKEN_SERVER = `<http://localhost:${process.env.TOKEN_SERVER_PORT> || 3000}`;
const ROOM = process.env.ROOM_NAME || "test-room";
const SAMPLE_RATE = 48000;
const CHANNELS = 1;
// Helper: create a node audio input stream and push into LocalAudioTrack
async function createMicTrack() {
// naudiodon input stream
const ai = new Naudi.AudioInput({
sampleRate: SAMPLE_RATE,
channelCount: CHANNELS,
sampleFormat: Naudi.SampleFormat16Bit
});
// Start stream
const stream = ai.start();
// create LocalAudioTrack from raw audio source (rtc-node helper)
// createLocalAudioTrack expects options; use a MediaStream or source. For node, @livekit/rtc-node exports a createLocalAudioTrack helper.
const track = await createLocalAudioTrack({
// rtc-node accepts a source object with .read() providing Float32Array frames -- we will use a simple wrapper below
// But createLocalAudioTrack can accept an AudioSource-like object
name: "mic-track"
} as any);
// Pipe audio data from naudiodon to track's writer if available.
// Implementation detail: if track has a writer or audio source API you can push PCM16 frames converted to Float32.
// Simple approach: read raw data and call track.writer?.pushAudio(...) if present.
ai.on("data", (chunk: Buffer) => {
// Convert PCM16 buffer to Float32Array normalized [-1,1]
const int16 = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / Int16Array.BYTES_PER_ELEMENT);
const float32 = new Float32Array(int16.length);
for (let i = 0; i < int16.length; i++) {
float32[i] = int16[i] / 32768;
}
// If track has a write method
// @ts-ignore
if ((track as any)._writer && typeof (track as any)._writer.write === "function") {
// push raw PCM float frames (this is internal API, may vary by versions)
try {
(track as any)._writer.write(float32);
} catch (e) {
// fallback: ignore
}
}
});
return { track, ai };
}
// Play remote audio via Naudiodon output
function playRemoteTrack(remoteAudio: RemoteAudioTrack) {
const ao = new Naudi.AudioOutput({
channelCount: CHANNELS,
sampleRate: SAMPLE_RATE,
sampleFormat: Naudi.SampleFormat16Bit
});
const outStream = ao.start();
remoteAudio.on("frame", (frame: any) => {
// frame.data might be Float32Array; convert to PCM16 Buffer
let floatData: Float32Array;
if (frame.data instanceof Float32Array) {
floatData = frame.data;
} else {
// try to extract
floatData = new Float32Array(frame.samples || frame.data.length);
for (let i = 0; i < floatData.length; i++) floatData[i] = frame.data[i];
}
const buf = Buffer.alloc(floatData.length * 2);
for (let i = 0; i < floatData.length; i++) {
let s = Math.max(-1, Math.min(1, floatData[i]));
buf.writeInt16LE(Math.floor(s * 32767), i * 2);
}
outStream.write(buf);
});
}
async function run() {
// request token
const identity = `local-client-${Math.floor(Math.random() * 1000)}`;
const res = await fetch(`${TOKEN_SERVER}/token/${identity}`);
const j = await res.json();
const token = j.token;
console.log("Connecting to room...", process.env.LIVEKIT_URL);
// Connect using @livekit/client with rtc-node
const room = await connect(process.env.LIVEKIT_URL!, token, {
wsUrl: process.env.LIVEKIT_URL,
rtcConfig: {}
} as any);
console.log("Connected to room:", ROOM, "as", identity);
// publish mic track
const { track: micTrack, ai } = await createMicTrack();
await room.localParticipant.publishTrack(micTrack);
console.log("Published mic track.");
// listen for remote tracks (agent)
room.on("trackSubscribed", (track: RemoteTrack) => {
if (track.kind === "audio") {
const audioTrack = track as RemoteAudioTrack;
console.log("Subscribed to remote audio track. Playing...");
playRemoteTrack(audioTrack);
}
});
process.on("SIGINT", async () => {
try {
await room.disconnect();
} catch {}
process.exit(0);
});
}
run().catch((e) => {
console.error("Client error:", e);
process.exit(1);
});
Important notes about src/client.ts:
• The @livekit/rtc-node + @livekit/client APIs for creating a LocalAudioTrack from raw node audio are version-sensitive. The sample above uses a generic approach: it creates a LocalAudioTrack and attempts to push Float32 frames into internal writer APIs — in some SDK versions you must use specific AudioSource/AudioSink classes. If you run into an API mismatch, I can adapt the snippet to the exact versions installed.
• The client uses naudiodon to capture PCM16 and converts to Float32 to push into the track.
• Playback converts Float32 frames to PCM16 and writes to the Naudiodon output stream.
7) Commands to run
• Install:
◦ pnpm install
◦ or npm install
• Start token server:
◦ NODE_OPTIONS=--experimental-specifier-resolution=node pnpm run token-server
◦ (visit http://localhost:3000/token/you to get tokens)
• Start agent (in another terminal)
◦ Ensure LiveKit CLI is configured and agent runner can load src/agent.ts, or run via LiveKit agent runner:
▪︎ lk agent run --agent-file src/agent.ts
◦ Or run directly (some features require agent runner):
▪︎ pnpm run agent
• Start client (in another terminal)
◦ pnpm run client
Behavior:
• client publishes your microphone audio to the LiveKit room.
• agent (running separately) subscribes and processes audio with OpenAI STT/LLM/TTS and publishes a response audio track; the client subscribes to the agent track and plays audio to your speakers.
If you hit an API mismatch when creating LocalAudioTrack or pushing audio frames (these node audio APIs change between versions), tell me the exact package versions printed from:
• pnpm list @livekit/rtc-node @livekit/client @livekit/agents naudiodon
and I’ll adapt the minimal code to match the installed APIs.
Would you like me to produce a pared-down version that uses mocked audio frames (no naudiodon) so you can verify the agent flow first, or adapt client code to your exact installed versions now?