@usetuner.ai/livekit-sdk
v0.5.0
Published
Automatically ingest LiveKit Agents session data into the Tuner observability API
Readme
@usetuner.ai/livekit-sdk
Automatically ingest LiveKit Agents session data into the Tuner observability API.
Node.js / TypeScript port of tuner-livekit-sdk (Python).
Install
npm install @usetuner.ai/livekit-sdkPeer dependencies: @livekit/agents >= 1.4.0, @livekit/rtc-node >= 0.13.0. Node 18+.
Quickstart (2 lines)
import { TunerPlugin } from '@usetuner.ai/livekit-sdk';
import { voice, type JobContext } from '@livekit/agents';
export default async function entry(ctx: JobContext) {
const session = new voice.AgentSession({ /* ... */ });
new TunerPlugin(session, ctx); // wires itself automatically
await session.start({ agent, room: ctx.room });
}Set the env vars:
TUNER_API_KEY=tr_api_...
TUNER_WORKSPACE_ID=123
TUNER_AGENT_ID=my-agentThe plugin subscribes to metrics_collected, close, and participantConnected/Disconnected events, captures SIP correlation data, and submits the session payload on shutdown.
Configuration
Every option can be passed as a constructor argument or as an environment variable. Constructor arguments take precedence.
| Option | Env var | Required | Description |
| ----------------------- | --------------------- | -------- | --------------------------------------------------------------------------- |
| apiKey | TUNER_API_KEY | yes | Bearer token issued by Tuner. |
| workspaceId | TUNER_WORKSPACE_ID | yes | Integer workspace ID. |
| agentId | TUNER_AGENT_ID | yes | Stable identifier for this agent across sessions. |
| baseUrl | TUNER_BASE_URL | no | Defaults to https://api.usetuner.ai. |
| agentVersion | AGENT_VERSION | no | Free-form version string. Useful for A/B and regression analysis. |
| callType | — | no | "phone_call", "web_call", or any custom label. |
| recordingUrlResolver | — | no | async (roomName, jobId) => string \| null. Returns a playback URL. |
| costCalculator | — | no | (usage) => number. Compute per-call cost in USD. |
| sipCorrelationId | — | no | Override the auto-captured sip.callIDFull. See "Simulations" below. |
| extraMetadata | — | no | Free-form metadata merged into the call payload. |
| enabled | — | no | false disables the plugin entirely. Defaults to true. |
| timeoutSeconds | — | no | Per-attempt HTTP timeout. Defaults to 30. |
| maxRetries | — | no | Max retries on retryable HTTP errors. Defaults to 3. |
Full example
import { TunerPlugin } from '@usetuner.ai/livekit-sdk';
import type { metrics } from '@livekit/agents';
function calculateCost(usage: metrics.UsageSummary): number {
return (
usage.llmPromptTokens * 0.000_003 +
usage.llmCompletionTokens * 0.000_015 +
usage.ttsCharactersCount * 0.000_030
);
}
async function getRecordingUrl(roomName: string, jobId: string): Promise<string> {
return (await myStorage.getUrl(jobId)) ?? 'pending';
}
new TunerPlugin(session, ctx, {
apiKey: process.env.TUNER_API_KEY,
workspaceId: Number.parseInt(process.env.TUNER_WORKSPACE_ID!, 10),
agentId: process.env.TUNER_AGENT_ID,
callType: 'phone_call',
recordingUrlResolver: getRecordingUrl,
costCalculator: calculateCost,
extraMetadata: { env: 'prod', region: 'us-east-1' },
agentVersion: 42,
timeoutSeconds: 20,
maxRetries: 3,
enabled: process.env.NODE_ENV === 'production',
});Simulation correlation (SIP)
Tuner simulations dial into your agent through the same SIP trunk that handles production phone calls. To match a simulation run with the session your agent submits, the SDK forwards LiveKit's sip.callIDFull attribute as a sipCorrelationId.
This section covers the SDK wiring only. For LiveKit platform setup (SIP URI, inbound trunk, dispatch rule, Tuner SIP settings), see:
→ docs.usetuner.ai/docs/api-and-integrations/connecting-to-livekit/simulation-setup
Step 1 — The extractSipCorrelationId helper
import { ParticipantKind } from '@livekit/rtc-node';
import type { JobContext } from '@livekit/agents';
function extractSipCorrelationId(ctx: JobContext): string | undefined {
for (const participant of ctx.room.remoteParticipants.values()) {
if (participant.kind !== ParticipantKind.SIP) continue;
const sipCallIdFull = participant.attributes?.['sip.callIDFull'];
if (typeof sipCallIdFull === 'string' && sipCallIdFull) return sipCallIdFull;
}
return undefined;
}Step 2 — Pass it to TunerPlugin
export default async function entry(ctx: JobContext) {
const session = new voice.AgentSession({ /* ... */ });
await ctx.connect();
const sipCorrelationId = extractSipCorrelationId(ctx);
new TunerPlugin(session, ctx, {
sipCorrelationId,
// ...other options
});
await session.start({ agent, room: ctx.room });
}⚠️ Order matters:
ctx.room.remoteParticipantsis empty untilawait ctx.connect()completes. If you call the helper too early it will always returnundefinedand you'll silently lose correlation for every simulation — no error, just missing data in Tuner. Always: buildAgentSession→await ctx.connect()→ extract ID → attach plugin →await session.start(...).
API surface
import {
TunerPlugin,
TunerConfig,
DisconnectReason,
VERSION,
type TunerConfigInput,
type RecordingUrlResolver,
type CostCalculator,
} from '@usetuner.ai/livekit-sdk';Behavior notes
A few intentional differences vs. the Python SDK; nothing affects the wire format:
- Shutdown reason:
@livekit/agents(Node) currently invokes shutdown callbacks with no arguments, so this SDK passes a hardcoded'shutdown'tostate.finalize(...). Python receives the real reason fromJobContext. If LiveKit's Node SDK adds the parameter, see the comment insrc/plugin.ts— it's a one-line change. agentIdURL-encoding: this SDK percent-encodesagentIdwhen building the submit URL; the Python SDK does not. Use URL-safe agent IDs to stay portable.- Timestamp normalization: the mapper accepts
createdAtin either seconds or milliseconds (auto-detects by magnitude) for forward-compatibility with@livekit/agentsreleases.
Development
pnpm install
pnpm typecheck
pnpm test
pnpm buildPublishing to npm
The package is published to the public npm registry as @usetuner.ai/livekit-sdk.
First-time setup
# Create an account at npmjs.com, then authenticate
npm login
# Confirm you have access to the @usetuner.ai org on npm
npm org ls usetuner.aiReleasing a new version
Pick the release type based on what changed:
# Bug fixes / non-breaking internal changes
npm run release:patch # e.g. 0.1.0 → 0.1.1
# New backwards-compatible features
npm run release:minor # e.g. 0.1.0 → 0.2.0
# Breaking API changes
npm run release:major # e.g. 0.1.0 → 1.0.0Each command automatically:
- Bumps the version in
package.jsonand creates a git tag - Runs
prepublishOnly— typecheck → tests → build (publish is blocked if any step fails) - Publishes the built
dist/to npm
After publishing, push the version commit and tag:
git push && git push --tagsWhat gets published
Only the files listed in "files" are included in the npm package:
dist/— compiled ESM + CJS + type declarationsREADME.mdLICENSE
Source files, tests, and config are excluded.
License
MIT
