From e6d46643055ae969af9e676db92d578ccca890e7 Mon Sep 17 00:00:00 2001 From: "rosetta-livekit-bot[bot]" <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:20:13 +0000 Subject: [PATCH] feat(protoface): add avatar plugin --- .changeset/clever-protoface-plugin.md | 5 + examples/package.json | 1 + examples/src/protoface_avatar.ts | 38 ++++ plugins/protoface/CHANGELOG.md | 7 + plugins/protoface/README.md | 15 ++ plugins/protoface/api-extractor.json | 8 + .../etc/agents-plugin-protoface.api.md | 90 ++++++++ plugins/protoface/package.json | 51 +++++ plugins/protoface/src/api.ts | 175 +++++++++++++++ plugins/protoface/src/avatar.ts | 204 ++++++++++++++++++ plugins/protoface/src/index.ts | 19 ++ plugins/protoface/src/log.ts | 7 + plugins/protoface/tsconfig.json | 15 ++ plugins/protoface/tsup.config.ts | 9 + pnpm-lock.yaml | 28 +++ turbo.json | 3 + 16 files changed, 675 insertions(+) create mode 100644 .changeset/clever-protoface-plugin.md create mode 100644 examples/src/protoface_avatar.ts create mode 100644 plugins/protoface/CHANGELOG.md create mode 100644 plugins/protoface/README.md create mode 100644 plugins/protoface/api-extractor.json create mode 100644 plugins/protoface/etc/agents-plugin-protoface.api.md create mode 100644 plugins/protoface/package.json create mode 100644 plugins/protoface/src/api.ts create mode 100644 plugins/protoface/src/avatar.ts create mode 100644 plugins/protoface/src/index.ts create mode 100644 plugins/protoface/src/log.ts create mode 100644 plugins/protoface/tsconfig.json create mode 100644 plugins/protoface/tsup.config.ts diff --git a/.changeset/clever-protoface-plugin.md b/.changeset/clever-protoface-plugin.md new file mode 100644 index 000000000..d7935903a --- /dev/null +++ b/.changeset/clever-protoface-plugin.md @@ -0,0 +1,5 @@ +--- +'@livekit/agents-plugin-protoface': patch +--- + +Add Protoface avatar plugin. diff --git a/examples/package.json b/examples/package.json index 1f2081386..bc820ddac 100644 --- a/examples/package.json +++ b/examples/package.json @@ -41,6 +41,7 @@ "@livekit/agents-plugin-neuphonic": "workspace:*", "@livekit/agents-plugin-openai": "workspace:*", "@livekit/agents-plugin-phonic": "workspace:*", + "@livekit/agents-plugin-protoface": "workspace:*", "@livekit/agents-plugin-resemble": "workspace:*", "@livekit/agents-plugin-rime": "workspace:*", "@livekit/agents-plugin-runway": "workspace:*", diff --git a/examples/src/protoface_avatar.ts b/examples/src/protoface_avatar.ts new file mode 100644 index 000000000..16657d31f --- /dev/null +++ b/examples/src/protoface_avatar.ts @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { type JobContext, ServerOptions, cli, defineAgent, voice } from '@livekit/agents'; +import * as google from '@livekit/agents-plugin-google'; +import * as protoface from '@livekit/agents-plugin-protoface'; +import { fileURLToPath } from 'node:url'; + +export default defineAgent({ + entry: async (ctx: JobContext) => { + const session = new voice.AgentSession({ + llm: new google.realtime.RealtimeModel({ + voice: 'Charon', + }), + turnHandling: { + interruption: { + resumeFalseInterruption: false, + }, + }, + }); + + await ctx.connect(); + + const avatar = new protoface.AvatarSession({ + avatarId: process.env.PROTOFACE_AVATAR_ID || protoface.DEFAULT_STOCK_AVATAR_ID, + }); + await avatar.start(session, ctx.room); + + await session.start({ + agent: new voice.Agent({ + instructions: 'Talk to me!', + }), + room: ctx.room, + }); + }, +}); + +cli.runApp(new ServerOptions({ agent: fileURLToPath(import.meta.url) })); diff --git a/plugins/protoface/CHANGELOG.md b/plugins/protoface/CHANGELOG.md new file mode 100644 index 000000000..2db160b78 --- /dev/null +++ b/plugins/protoface/CHANGELOG.md @@ -0,0 +1,7 @@ +# @livekit/agents-plugin-protoface + +## 1.4.9 + +### Patch Changes + +- Add Protoface avatar plugin. diff --git a/plugins/protoface/README.md b/plugins/protoface/README.md new file mode 100644 index 000000000..7167ff3aa --- /dev/null +++ b/plugins/protoface/README.md @@ -0,0 +1,15 @@ +# Protoface plugin for LiveKit Agents + +Support for the [Protoface](https://protoface.com/) virtual avatar. + +See the [Protoface docs](https://docs.protoface.com/) for more information. + +## Installation + +```bash +npm install @livekit/agents-plugin-protoface +``` + +## Pre-requisites + +You'll need an API key from Protoface. It can be set as an environment variable: `PROTOFACE_API_KEY` diff --git a/plugins/protoface/api-extractor.json b/plugins/protoface/api-extractor.json new file mode 100644 index 000000000..baa041649 --- /dev/null +++ b/plugins/protoface/api-extractor.json @@ -0,0 +1,8 @@ +/** + * 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", + "extends": "../../api-extractor-shared.json", + "mainEntryPointFilePath": "./dist/index.d.ts" +} diff --git a/plugins/protoface/etc/agents-plugin-protoface.api.md b/plugins/protoface/etc/agents-plugin-protoface.api.md new file mode 100644 index 000000000..a91b6c2d0 --- /dev/null +++ b/plugins/protoface/etc/agents-plugin-protoface.api.md @@ -0,0 +1,90 @@ +## API Report File for "@livekit/agents-plugin-protoface" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { APIConnectOptions } from '@livekit/agents'; +import type { Room } from '@livekit/rtc-node'; +import { voice } from '@livekit/agents'; + +// @public +export class AvatarSession extends voice.AvatarSession { + constructor(options?: AvatarSessionOptions); + // (undocumented) + aclose(): Promise; + // (undocumented) + get avatarIdentity(): string; + // (undocumented) + get provider(): string; + get sessionId(): string | null; + // (undocumented) + start(agentSession: voice.AgentSession, room: Room, options?: StartOptions): Promise; +} + +// @public (undocumented) +export interface AvatarSessionOptions { + apiKey?: string | null; + apiUrl?: string | null; + avatarId?: string; + avatarParticipantIdentity?: string | null; + avatarParticipantName?: string | null; + connOptions?: APIConnectOptions; + maxDurationSeconds?: number | null; +} + +// @public (undocumented) +export const DEFAULT_API_URL = "https://api.protoface.com"; + +// @public (undocumented) +export const DEFAULT_STOCK_AVATAR_ID = "av_stock_001"; + +// @public +export class ProtofaceAPI { + constructor(options?: ProtofaceAPIOptions); + // (undocumented) + endSession(sessionId: string): Promise>; + // (undocumented) + startSession(options: StartSessionOptions): Promise; +} + +// @public (undocumented) +export interface ProtofaceAPIOptions { + apiKey?: string | null; + apiUrl?: string | null; + connOptions?: APIConnectOptions; +} + +// @public (undocumented) +export class ProtofaceException extends Error { + constructor(message: string); +} + +// @public (undocumented) +export type ProtofaceSession = Record & { + id?: string; +}; + +// @public +export interface StartOptions { + // (undocumented) + livekitApiKey?: string | null; + // (undocumented) + livekitApiSecret?: string | null; + // (undocumented) + livekitUrl?: string | null; +} + +// @public (undocumented) +export interface StartSessionOptions { + // (undocumented) + avatarId: string; + // (undocumented) + maxDurationSeconds?: number | null; + // (undocumented) + transport: Record; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/plugins/protoface/package.json b/plugins/protoface/package.json new file mode 100644 index 000000000..124efae66 --- /dev/null +++ b/plugins/protoface/package.json @@ -0,0 +1,51 @@ +{ + "name": "@livekit/agents-plugin-protoface", + "version": "1.4.9", + "description": "Protoface avatar 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": "git@github.com: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": "catalog:", + "@microsoft/api-extractor": "^7.35.0", + "pino": "^8.19.0", + "tsup": "^8.3.5", + "typescript": "^5.0.0" + }, + "dependencies": { + "livekit-server-sdk": "^2.13.3" + }, + "peerDependencies": { + "@livekit/agents": "workspace:*", + "@livekit/rtc-node": "catalog:" + } +} diff --git a/plugins/protoface/src/api.ts b/plugins/protoface/src/api.ts new file mode 100644 index 000000000..ff6bbc2ba --- /dev/null +++ b/plugins/protoface/src/api.ts @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { + type APIConnectOptions, + APIConnectionError, + APIStatusError, + APITimeoutError, + DEFAULT_API_CONNECT_OPTIONS, + intervalForRetry, +} from '@livekit/agents'; +import { log } from './log.js'; + +/** @public */ +export const DEFAULT_API_URL = 'https://api.protoface.com'; + +const USER_AGENT = `@livekit/agents-plugin-protoface/${__PACKAGE_VERSION__}`; + +/** @public */ +export class ProtofaceException extends Error { + constructor(message: string) { + super(message); + this.name = 'ProtofaceException'; + } +} + +/** @public */ +export interface ProtofaceAPIOptions { + /** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */ + apiKey?: string | null; + /** Override the Protoface API base URL. */ + apiUrl?: string | null; + /** API retry/timeout options. */ + connOptions?: APIConnectOptions; +} + +/** @public */ +export interface StartSessionOptions { + avatarId: string; + transport: Record; + maxDurationSeconds?: number | null; +} + +/** @public */ +export type ProtofaceSession = Record & { id?: string }; + +/** + * Async client for the Protoface session API. + * + * @public + */ +export class ProtofaceAPI { + private apiKey: string; + private apiUrl: string; + private connOptions: APIConnectOptions; + + #logger = log(); + + constructor(options: ProtofaceAPIOptions = {}) { + const apiKey = options.apiKey ?? process.env.PROTOFACE_API_KEY ?? ''; + if (!apiKey) { + throw new ProtofaceException( + 'apiKey must be set by passing it to ProtofaceAPI or setting the PROTOFACE_API_KEY environment variable', + ); + } + + this.apiKey = apiKey; + this.apiUrl = (options.apiUrl ?? process.env.PROTOFACE_API_URL ?? DEFAULT_API_URL).replace( + /\/+$/, + '', + ); + this.connOptions = options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS; + } + + async startSession(options: StartSessionOptions): Promise { + const body: Record = { + avatar_id: options.avatarId, + transport: options.transport, + }; + if (options.maxDurationSeconds != null) { + body.max_duration_seconds = options.maxDurationSeconds; + } + + return (await this.json('POST', '/v1/sessions', body)) as ProtofaceSession; + } + + async endSession(sessionId: string): Promise> { + return (await this.json('POST', `/v1/sessions/${sessionId}/end`)) as Record; + } + + private async json( + method: string, + path: string, + body?: Record, + ): Promise> { + const url = `${this.apiUrl}${path}`; + let lastError: unknown; + + for (let i = 0; i <= this.connOptions.maxRetry; i++) { + try { + const response = await fetch(url, { + method, + headers: { + accept: 'application/json', + authorization: `Bearer ${this.apiKey}`, + 'content-type': 'application/json', + 'user-agent': USER_AGENT, + }, + body: body == null ? undefined : JSON.stringify(body), + signal: AbortSignal.timeout(this.connOptions.timeoutMs), + }); + const payload = await readPayload(response); + if (response.ok) { + if (!isRecord(payload)) { + throw new APIStatusError({ + message: 'Protoface API returned a non-object JSON response', + options: { statusCode: response.status, body: { payload }, retryable: false }, + }); + } + return payload; + } + + throw new APIStatusError({ + message: 'Protoface API returned an error', + options: { statusCode: response.status, body: isRecord(payload) ? payload : { payload } }, + }); + } catch (error) { + if (error instanceof APIStatusError && !error.retryable) { + throw error; + } + lastError = normalizeConnectionError(error); + } + + if (i < this.connOptions.maxRetry) { + this.#logger.warn( + { attempt: i + 1, method, path }, + 'protoface api request failed, retrying', + ); + await new Promise((resolve) => setTimeout(resolve, intervalForRetry(this.connOptions, i))); + } + } + + throw new APIConnectionError({ + message: 'Failed to call Protoface API after all retries.', + options: { body: isRecord(lastError) ? lastError : null }, + }); + } +} + +async function readPayload(response: Response): Promise { + const text = await response.text(); + if (!text) { + return {}; + } + + try { + return JSON.parse(text) as unknown; + } catch { + return { raw: text }; + } +} + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function normalizeConnectionError(error: unknown): unknown { + if (error instanceof APIStatusError) { + return error; + } + if (error instanceof DOMException && error.name === 'TimeoutError') { + return new APITimeoutError({ message: 'Protoface API request timed out.' }); + } + return error; +} diff --git a/plugins/protoface/src/avatar.ts b/plugins/protoface/src/avatar.ts new file mode 100644 index 000000000..bb1c3c232 --- /dev/null +++ b/plugins/protoface/src/avatar.ts @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { + type APIConnectOptions, + DEFAULT_API_CONNECT_OPTIONS, + getJobContext, + voice, +} from '@livekit/agents'; +import type { Room } from '@livekit/rtc-node'; +import { TrackKind } from '@livekit/rtc-node'; +import type { VideoGrant } from 'livekit-server-sdk'; +import { AccessToken } from 'livekit-server-sdk'; +import { ProtofaceAPI, ProtofaceException } from './api.js'; +import { log } from './log.js'; + +/** @public */ +export const DEFAULT_STOCK_AVATAR_ID = 'av_stock_001'; + +const ATTRIBUTE_PUBLISH_ON_BEHALF = 'lk.publish_on_behalf'; +const SAMPLE_RATE = 16000; +const AVATAR_AGENT_IDENTITY = 'protoface-avatar-agent'; +const AVATAR_AGENT_NAME = 'protoface-avatar-agent'; + +/** @public */ +export interface AvatarSessionOptions { + /** Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. */ + avatarId?: string; + /** Override the Protoface API base URL. */ + apiUrl?: string | null; + /** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */ + apiKey?: string | null; + /** Optional maximum session duration. Protoface applies the lower of this and the plan limit. */ + maxDurationSeconds?: number | null; + /** Identity for the avatar participant. Defaults to `protoface-avatar-agent`. */ + avatarParticipantIdentity?: string | null; + /** Display name for the avatar participant. Defaults to `protoface-avatar-agent`. */ + avatarParticipantName?: string | null; + /** API retry/timeout options. */ + connOptions?: APIConnectOptions; +} + +/** + * Optional LiveKit credentials for {@link AvatarSession.start}; falls back to env vars. + * + * @public + */ +export interface StartOptions { + livekitUrl?: string | null; + livekitApiKey?: string | null; + livekitApiSecret?: string | null; +} + +/** + * A Protoface avatar session for LiveKit Agents. + * + * @public + */ +export class AvatarSession extends voice.AvatarSession { + private avatarId: string; + private maxDurationSeconds?: number | null; + private avatarParticipantIdentity: string; + private avatarParticipantName: string; + private api: ProtofaceAPI; + private sessionIdValue: string | null = null; + + #logger = log(); + + constructor(options: AvatarSessionOptions = {}) { + super(); + this.avatarId = options.avatarId ?? DEFAULT_STOCK_AVATAR_ID; + this.maxDurationSeconds = options.maxDurationSeconds; + this.avatarParticipantIdentity = options.avatarParticipantIdentity || AVATAR_AGENT_IDENTITY; + this.avatarParticipantName = options.avatarParticipantName || AVATAR_AGENT_NAME; + this.api = new ProtofaceAPI({ + apiKey: options.apiKey, + apiUrl: options.apiUrl, + connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS, + }); + } + + override get avatarIdentity(): string { + return this.avatarParticipantIdentity; + } + + override get provider(): string { + return 'protoface'; + } + + /** Protoface session ID after `start()` succeeds, otherwise `null`. */ + get sessionId(): string | null { + return this.sessionIdValue; + } + + async start( + agentSession: voice.AgentSession, + room: Room, + options: StartOptions = {}, + ): Promise { + if (this.sessionIdValue !== null) { + throw new Error('AvatarSession.start() called twice; create a new AvatarSession.'); + } + + await super.start(agentSession, room); + + const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL; + const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY; + const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET; + if (!livekitUrl || !livekitApiKey || !livekitApiSecret) { + throw new ProtofaceException( + 'livekitUrl, livekitApiKey, and livekitApiSecret must be set by arguments or environment variables', + ); + } + + const workerToken = await this.mintWorkerToken({ room, livekitApiKey, livekitApiSecret }); + const session = await this.api.startSession({ + avatarId: this.avatarId, + transport: { + type: 'livekit', + url: livekitUrl, + room_name: room.name, + worker_token: workerToken, + worker_identity: this.avatarParticipantIdentity, + audio_source: 'data_stream', + }, + maxDurationSeconds: this.maxDurationSeconds, + }); + + if (typeof session.id !== 'string') { + throw new ProtofaceException('Protoface API response missing session id'); + } + + this.sessionIdValue = session.id; + this.#logger.debug( + { sessionId: this.sessionIdValue, avatarId: this.avatarId }, + 'protoface session started', + ); + + agentSession.output.audio = new voice.DataStreamAudioOutput({ + room, + destinationIdentity: this.avatarParticipantIdentity, + sampleRate: SAMPLE_RATE, + waitRemoteTrack: TrackKind.KIND_VIDEO, + }); + } + + async aclose(): Promise { + const sessionId = this.sessionIdValue; + this.sessionIdValue = null; + try { + if (sessionId !== null) { + try { + await this.api.endSession(sessionId); + } catch (error) { + this.#logger.warn({ error: String(error), sessionId }, 'failed to end protoface session'); + } + } + } finally { + await super.aclose(); + } + } + + private async mintWorkerToken({ + room, + livekitApiKey, + livekitApiSecret, + }: { + room: Room; + livekitApiKey: string; + livekitApiSecret: string; + }): Promise { + let localParticipantIdentity = ''; + try { + const jobCtx = getJobContext(); + localParticipantIdentity = jobCtx.agent?.identity || ''; + } catch { + // Fall back to the connected room below when no job context is available. + } + + if (!localParticipantIdentity && room.isConnected && room.localParticipant) { + localParticipantIdentity = room.localParticipant.identity; + } + + if (!localParticipantIdentity) { + throw new ProtofaceException('failed to get local participant identity'); + } + + const token = new AccessToken(livekitApiKey, livekitApiSecret, { + identity: this.avatarParticipantIdentity, + name: this.avatarParticipantName, + }); + token.kind = 'agent'; + token.attributes = { [ATTRIBUTE_PUBLISH_ON_BEHALF]: localParticipantIdentity }; + token.addGrant({ + roomJoin: true, + room: room.name, + canPublish: true, + canSubscribe: true, + canPublishData: true, + } as VideoGrant); + + return token.toJwt(); + } +} diff --git a/plugins/protoface/src/index.ts b/plugins/protoface/src/index.ts new file mode 100644 index 000000000..a9aa0c7ef --- /dev/null +++ b/plugins/protoface/src/index.ts @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { Plugin } from '@livekit/agents'; + +export * from './api.js'; +export * from './avatar.js'; + +class ProtofacePlugin extends Plugin { + constructor() { + super({ + title: 'protoface', + version: __PACKAGE_VERSION__, + package: __PACKAGE_NAME__, + }); + } +} + +Plugin.registerPlugin(new ProtofacePlugin()); diff --git a/plugins/protoface/src/log.ts b/plugins/protoface/src/log.ts new file mode 100644 index 000000000..44b8166bf --- /dev/null +++ b/plugins/protoface/src/log.ts @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { log as agentsLog } from '@livekit/agents'; +import type { Logger } from 'pino'; + +export const log = (): Logger => agentsLog().child({ plugin: 'protoface' }); diff --git a/plugins/protoface/tsconfig.json b/plugins/protoface/tsconfig.json new file mode 100644 index 000000000..65e26d178 --- /dev/null +++ b/plugins/protoface/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src"], + "compilerOptions": { + "rootDir": "./src", + "declarationDir": "./dist", + "outDir": "./dist" + }, + "typedocOptions": { + "name": "plugins/agents-plugin-protoface", + "entryPointStrategy": "resolve", + "readme": "none", + "entryPoints": ["src/index.ts"] + } +} diff --git a/plugins/protoface/tsup.config.ts b/plugins/protoface/tsup.config.ts new file mode 100644 index 000000000..46011fa8c --- /dev/null +++ b/plugins/protoface/tsup.config.ts @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { defineConfig } from 'tsup'; +import defaults from '../../tsup.config.js'; + +export default defineConfig({ + ...defaults, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd07b1a76..8a0b570ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,6 +302,9 @@ importers: '@livekit/agents-plugin-phonic': specifier: workspace:* version: link:../plugins/phonic + '@livekit/agents-plugin-protoface': + specifier: workspace:* + version: link:../plugins/protoface '@livekit/agents-plugin-resemble': specifier: workspace:* version: link:../plugins/resemble @@ -1119,6 +1122,31 @@ importers: specifier: ^5.0.0 version: 5.9.3 + plugins/protoface: + dependencies: + livekit-server-sdk: + specifier: ^2.13.3 + version: 2.14.1 + devDependencies: + '@livekit/agents': + specifier: workspace:* + version: link:../../agents + '@livekit/rtc-node': + specifier: 'catalog:' + version: 0.13.29 + '@microsoft/api-extractor': + specifier: ^7.35.0 + version: 7.43.7(@types/node@25.6.0) + pino: + specifier: ^8.19.0 + version: 8.21.0 + tsup: + specifier: ^8.3.5 + version: 8.4.0(@microsoft/api-extractor@7.43.7(@types/node@25.6.0))(postcss@8.5.15)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + plugins/resemble: dependencies: ws: diff --git a/turbo.json b/turbo.json index b0bc90527..a15e1672d 100644 --- a/turbo.json +++ b/turbo.json @@ -61,6 +61,9 @@ "NEUPHONIC_API_KEY", "NUM_CPUS", "PHONIC_API_KEY", + "PROTOFACE_API_KEY", + "PROTOFACE_API_URL", + "PROTOFACE_AVATAR_ID", "RESEMBLE_API_KEY", "LIVEKIT_REMOTE_EOT_URL", "LIVEKIT_SIP_NUMBER",