Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@livekit/agents-plugin-deepgram": "workspace:*",
"@livekit/agents-plugin-elevenlabs": "workspace:*",
"@livekit/agents-plugin-google": "workspace:*",
"@livekit/agents-plugin-inworld": "workspace:*",
"@livekit/agents-plugin-livekit": "workspace:*",
"@livekit/agents-plugin-neuphonic": "workspace:*",
"@livekit/agents-plugin-openai": "workspace:*",
Expand Down
117 changes: 117 additions & 0 deletions examples/src/inworld_tts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import {
type JobContext,
type JobProcess,
WorkerOptions,
cli,
defineAgent,
metrics,
voice,
} from '@livekit/agents';
import * as inworld from '@livekit/agents-plugin-inworld';
import * as livekit from '@livekit/agents-plugin-livekit';
import * as silero from '@livekit/agents-plugin-silero';
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
import { fileURLToPath } from 'node:url';

export default defineAgent({
prewarm: async (proc: JobProcess) => {
proc.userData.vad = await silero.VAD.load();
},
entry: async (ctx: JobContext) => {
const agent = new voice.Agent({
instructions:
"You are a helpful assistant, you can hear the user's message and respond to it in 1-2 short sentences.",
});

// Create TTS instance
const tts = new inworld.TTS({
timestampType: 'WORD',
voice: 'Hades',
model: 'inworld-tts-1',
encoding: 'LINEAR16',
textNormalization: 'ON',
bitRate: 64000,
sampleRate: 24000,
speakingRate: 1.0,
temperature: 1.1,
bufferCharThreshold: 100,
maxBufferDelayMs: 3000,
});

// List available voices
tts
.listVoices()
.then((voices: inworld.Voice[]) => {
console.log(`[Inworld TTS] ${voices.length} voices available in this workspace`);
if (voices.length > 0) {
console.log(
'[Inworld TTS] Logging information for first voice:',
JSON.stringify(voices[0], null, 2),
);
}
})
.catch((err: Error) => {
console.error('[Inworld TTS] Failed to list voices:', err);
});

const session = new voice.AgentSession({
// Speech-to-text (STT) is your agent's ears, turning the user's speech into text that the LLM can understand
// See all available models at https://docs.livekit.io/agents/models/stt/
stt: 'assemblyai/universal-streaming:en',
// A Large Language Model (LLM) is your agent's brain, processing user input and generating a response
// See all available models at https://docs.livekit.io/agents/models/llm/
llm: 'openai/gpt-4.1-mini',
// Text-to-speech (TTS) is your agent's voice, turning the LLM's text into speech that the user can hear
// See all available models as well as voice selections at https://docs.livekit.io/agents/models/tts/
tts,
// VAD and turn detection are used to determine when the user is speaking and when the agent should respond
// See more at https://docs.livekit.io/agents/build/turns
vad: ctx.proc.userData.vad! as silero.VAD,
turnDetection: new livekit.turnDetector.MultilingualModel(),
// to use realtime model, replace the stt, llm, tts and vad with the following
// llm: new openai.realtime.RealtimeModel(),
voiceOptions: {
// allow the LLM to generate a response while waiting for the end of turn
preemptiveGeneration: true,
},
});

// timestamp handling (if enabled)
session.tts!.on('alignment' as any, (data: any) => {
if (data.wordAlignment) {
const { words, starts, ends } = data.wordAlignment;
for (let i = 0; i < words.length; i++) {
console.log(`[Inworld TTS] Word: "${words[i]}", Start: ${starts[i]}, End: ${ends[i]}`);
}
}
if (data.characterAlignment) {
const { chars, starts, ends } = data.characterAlignment;
for (let i = 0; i < chars.length; i++) {
console.log(`[Inworld TTS] Char: "${chars[i]}", Start: ${starts[i]}, End: ${ends[i]}`);
}
}
});

const usageCollector = new metrics.UsageCollector();

session.on(voice.AgentSessionEventTypes.MetricsCollected, (ev) => {
metrics.logMetrics(ev.metrics);
usageCollector.collect(ev.metrics);
});

await session.start({
agent,
room: ctx.room,
inputOptions: {
noiseCancellation: BackgroundVoiceCancellation(),
},
});

session.say('Hello, how can I help you today?');
},
});

cli.runApp(new WorkerOptions({ agent: fileURLToPath(import.meta.url) }));
21 changes: 21 additions & 0 deletions plugins/inworld/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",

/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for
* standard settings to be shared across multiple projects.
*
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
* resolved using NodeJS require().
*
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
"extends": "../../api-extractor-shared.json",
"mainEntryPointFilePath": "./dist/index.d.ts"
}

52 changes: 52 additions & 0 deletions plugins/inworld/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@livekit/agents-plugin-inworld",
"version": "0.1.0",
"description": "Inworld plugin for LiveKit Node Agents",
"main": "dist/index.js",
"require": "dist/index.cjs",
"types": "dist/index.d.ts",
"exports": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"author": "LiveKit",
"type": "module",
"repository": "[email protected]:livekit/agents-js.git",
"license": "Apache-2.0",
"files": [
"dist",
"src",
"README.md"
],
"scripts": {
"build": "tsup --onSuccess \"pnpm build:types\"",
"build:types": "tsc --declaration --emitDeclarationOnly && node ../../scripts/copyDeclarationOutput.js",
"clean": "rm -rf dist",
"clean:build": "pnpm clean && pnpm build",
"lint": "eslint -f unix \"src/**/*.{ts,js}\"",
"api:check": "api-extractor run --typescript-compiler-folder ../../node_modules/typescript",
"api:update": "api-extractor run --local --typescript-compiler-folder ../../node_modules/typescript --verbose"
},
"devDependencies": {
"@livekit/agents": "workspace:*",
"@livekit/rtc-node": "^0.13.12",
"@microsoft/api-extractor": "^7.35.0",
"@types/ws": "^8.5.10",
"tsup": "^8.3.5",
"typescript": "^5.0.0"
},
"dependencies": {
"ws": "^8.16.0"
},
"peerDependencies": {
"@livekit/agents": "workspace:*",
"@livekit/rtc-node": "^0.13.12"
}
}

19 changes: 19 additions & 0 deletions plugins/inworld/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import { Plugin } from '@livekit/agents';
import './tts.js';

export * from './tts.js';

class InworldPlugin extends Plugin {
constructor() {
super({
title: 'Inworld',
version: '0.1.0',
package: '@livekit/agents-plugin-inworld',
});
}
}

Plugin.registerPlugin(new InworldPlugin());
Loading