@livekit/agents-plugin-phonic 1.0.46 → 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,7 @@ class RealtimeModel extends import_agents.llm.RealtimeModel {
45
45
  // TODO @Phonic-Co: Implement tool support
46
46
  // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.
47
47
  autoToolReplyGeneration: true,
48
+ manualFunctionCalls: false,
48
49
  audioOutput: true
49
50
  });
50
51
  const apiKey = options.apiKey || process.env.PHONIC_API_KEY;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/realtime/realtime_model.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { APIConnectOptions } from '@livekit/agents';\nimport {\n AudioByteStream,\n DEFAULT_API_CONNECT_OPTIONS,\n llm,\n log,\n shortuuid,\n stream,\n} from '@livekit/agents';\nimport { AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { Phonic } from 'phonic';\nimport { PhonicClient } from 'phonic';\nimport type { ServerEvent, Voice } from './api_proto.js';\n\nconst PHONIC_INPUT_SAMPLE_RATE = 44100;\nconst PHONIC_OUTPUT_SAMPLE_RATE = 44100;\nconst PHONIC_NUM_CHANNELS = 1;\nconst PHONIC_INPUT_FRAME_MS = 20;\nconst DEFAULT_MODEL = 'merritt';\nconst WS_CLOSE_NORMAL = 1000;\n\nexport interface RealtimeModelOptions {\n apiKey: string;\n model: string;\n phonicAgent?: string;\n voice?: Voice | string;\n welcomeMessage?: string;\n generateWelcomeMessage?: boolean;\n project?: string;\n connOptions: APIConnectOptions;\n baseUrl?: string;\n languages?: string[];\n audioSpeed?: number;\n phonicTools?: string[];\n boostedKeywords?: string[];\n generateNoInputPokeText?: boolean;\n noInputPokeSec?: number;\n noInputPokeText?: string;\n noInputEndConversationSec?: number;\n /** Set by `updateInstructions` via `voice.Agent` rather than the RealtimeModel constructor */\n instructions?: string;\n}\n\nexport class RealtimeModel extends llm.RealtimeModel {\n /** @internal */\n _options: RealtimeModelOptions;\n\n get model(): string {\n return this._options.model;\n }\n\n constructor(\n options: {\n /**\n * Phonic API key. If not provided, will attempt to read from PHONIC_API_KEY environment variable\n */\n apiKey?: string;\n /**\n * The name of the model to use. Defaults to 'merritt'\n */\n model?: Phonic.ConfigPayload['model'] | string;\n /**\n * Phonic agent to use for the conversation. Options explicitly set here will override the agent settings.\n */\n phonicAgent?: string;\n /**\n * Voice ID for agent outputs\n */\n voice?: Voice;\n /**\n * Welcome message for the agent to say when the conversation starts. Ignored when generateWelcomeMessage is true\n */\n welcomeMessage?: string;\n /**\n * When true, the welcome message will be automatically generated and welcomeMessage will be ignored\n */\n generateWelcomeMessage?: boolean;\n /**\n * Project name to use for the conversation. Defaults to `main`\n */\n project?: string;\n /**\n * ISO 639-1 language codes the agent should recognize and speak\n */\n languages?: string[];\n /**\n * Audio playback speed\n */\n audioSpeed?: number;\n /**\n * Phonic tool names available to the assistant\n */\n phonicTools?: string[];\n /**\n * Keywords to boost in speech recognition\n */\n boostedKeywords?: string[];\n /**\n * Auto-generate poke text when user is silent\n */\n generateNoInputPokeText?: boolean;\n /**\n * Seconds of silence before sending poke message\n */\n noInputPokeSec?: number;\n /**\n * Poke message text (ignored when generateNoInputPokeText is true)\n */\n noInputPokeText?: string;\n /**\n * Seconds of silence before ending conversation\n */\n noInputEndConversationSec?: number;\n /**\n * Connection options for the API connection\n */\n connOptions?: APIConnectOptions;\n baseUrl?: string;\n } = {},\n ) {\n super({\n messageTruncation: false,\n turnDetection: true,\n userTranscription: true,\n // TODO @Phonic-Co: Implement tool support\n // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.\n autoToolReplyGeneration: true,\n audioOutput: true,\n });\n\n const apiKey = options.apiKey || process.env.PHONIC_API_KEY;\n if (!apiKey) {\n throw new Error('Phonic API key is required. Provide apiKey or set PHONIC_API_KEY.');\n }\n\n this._options = {\n apiKey,\n voice: options.voice,\n phonicAgent: options.phonicAgent,\n project: options.project,\n welcomeMessage: options.welcomeMessage,\n generateWelcomeMessage: options.generateWelcomeMessage,\n languages: options.languages,\n audioSpeed: options.audioSpeed,\n phonicTools: options.phonicTools,\n boostedKeywords: options.boostedKeywords,\n generateNoInputPokeText: options.generateNoInputPokeText,\n noInputPokeSec: options.noInputPokeSec,\n noInputPokeText: options.noInputPokeText,\n noInputEndConversationSec: options.noInputEndConversationSec,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n model: options.model ?? DEFAULT_MODEL,\n baseUrl: options.baseUrl,\n };\n }\n\n /**\n * Create a new realtime session\n */\n session(): RealtimeSession {\n return new RealtimeSession(this);\n }\n\n async close(): Promise<void> {}\n}\n\ninterface GenerationState {\n responseId: string;\n messageChannel: stream.StreamChannel<llm.MessageGeneration>;\n functionChannel: stream.StreamChannel<llm.FunctionCall>;\n textChannel: stream.StreamChannel<string>;\n audioChannel: stream.StreamChannel<AudioFrame>;\n outputText: string;\n}\n\n/**\n * Realtime session for Phonic (https://docs.phonic.co/)\n */\nexport class RealtimeSession extends llm.RealtimeSession {\n private _tools: llm.ToolContext = {};\n private _chatCtx = llm.ChatContext.empty();\n\n private options: RealtimeModelOptions;\n private bstream: AudioByteStream;\n private inputResampler?: AudioResampler;\n private inputResamplerInputRate?: number;\n\n private currentGeneration?: GenerationState;\n private conversationId?: string;\n\n private client: PhonicClient;\n private socket?: Awaited<ReturnType<PhonicClient['conversations']['connect']>>;\n private logger = log();\n private closed = false;\n private configSent = false;\n private instructionsReady: Promise<void>;\n private resolveInstructionsReady: () => void;\n private connectTask: Promise<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super(realtimeModel);\n this.options = realtimeModel._options;\n\n this.resolveInstructionsReady = () => {};\n this.instructionsReady = new Promise<void>((resolve) => {\n this.resolveInstructionsReady = resolve;\n });\n\n this.client = new PhonicClient({\n apiKey: this.options.apiKey,\n baseUrl: this.options.baseUrl,\n });\n this.bstream = new AudioByteStream(\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n (PHONIC_INPUT_SAMPLE_RATE * PHONIC_INPUT_FRAME_MS) / 1000,\n );\n this.connectTask = this.connect().catch((error: unknown) => {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n this.emitError(normalizedError, false);\n });\n }\n\n get chatCtx(): llm.ChatContext {\n return this._chatCtx.copy();\n }\n\n get tools(): llm.ToolContext {\n return { ...this._tools };\n }\n\n async updateInstructions(instructions: string): Promise<void> {\n if (this.configSent) {\n this.logger.warn(\n 'updateInstructions called after config was already sent. Phonic does not support updating instructions mid-session.',\n );\n return;\n }\n this.options.instructions = instructions;\n this.resolveInstructionsReady();\n }\n\n async updateChatCtx(_chatCtx: llm.ChatContext): Promise<void> {\n this.logger.warn('updateChatCtx is not supported by the Phonic realtime model.');\n }\n\n async updateTools(tools: llm.ToolContext): Promise<void> {\n if (Object.keys(tools).length > 0) {\n this.logger.warn('Tool use is not supported by the Phonic realtime model.');\n }\n }\n\n updateOptions(_options: { toolChoice?: llm.ToolChoice | null }): void {\n this.logger.warn('updateOptions is not supported by the Phonic realtime model.');\n }\n\n pushAudio(frame: AudioFrame): void {\n if (this.closed) {\n return;\n }\n\n for (const resampledFrame of this.resampleAudio(frame)) {\n for (const chunk of this.bstream.write(resampledFrame.data.buffer as ArrayBuffer)) {\n const bytes = Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength);\n const payload: Phonic.AudioChunkPayload = {\n type: 'audio_chunk',\n audio: bytes.toString('base64'),\n };\n\n if (!this.socket) {\n continue;\n }\n this.socket.sendAudioChunk(payload);\n }\n }\n }\n\n // TODO @Phonic-Co: Implement generateReply\n async generateReply(_instructions?: string): Promise<llm.GenerationCreatedEvent> {\n throw new Error(\n 'generateReply is not yet supported by the Phonic realtime model. Consider using `welcomeMessage` instead.',\n );\n }\n\n async commitAudio(): Promise<void> {\n this.logger.warn('commitAudio is not supported by the Phonic realtime model.');\n }\n async clearAudio(): Promise<void> {\n this.logger.warn('clearAudio is not supported by the Phonic realtime model.');\n }\n\n async interrupt(): Promise<void> {\n this.logger.warn('interrupt is not supported by the Phonic realtime model.');\n }\n\n async truncate(_options: { messageId: string; audioEndMs: number; audioTranscript?: string }) {\n this.logger.warn('truncate is not supported by the Phonic realtime model.');\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.resolveInstructionsReady();\n this.closeCurrentGeneration({ interrupted: false });\n this.socket?.close();\n await this.connectTask;\n await super.close();\n }\n\n private async connect(): Promise<void> {\n this.socket = await this.client.conversations.connect({\n reconnectAttempts: this.options.connOptions.maxRetry,\n });\n\n if (this.closed) {\n this.socket.close();\n return;\n }\n\n this.socket.on('message', (message: unknown) =>\n this.handleServerMessage(message as ServerEvent),\n );\n this.socket.on('error', (error: Error) => this.emitError(error, false));\n this.socket.on('close', (event: { code?: number }) => {\n this.closeCurrentGeneration({ interrupted: false });\n if (!this.closed && event.code !== WS_CLOSE_NORMAL) {\n this.emitError(new Error(`Phonic STS socket closed with code ${event.code ?? -1}`), false);\n }\n });\n\n await this.socket.waitForOpen();\n await this.instructionsReady;\n if (this.closed) return;\n this.configSent = true;\n this.socket.sendConfig({\n type: 'config',\n model: this.options.model as Phonic.ConfigPayload['model'],\n agent: this.options.phonicAgent,\n project: this.options.project,\n welcome_message: this.options.welcomeMessage,\n generate_welcome_message: this.options.generateWelcomeMessage,\n system_prompt: this.options.instructions,\n voice_id: this.options.voice,\n input_format: 'pcm_44100',\n output_format: 'pcm_44100',\n recognized_languages: this.options.languages,\n audio_speed: this.options.audioSpeed,\n tools: this.options.phonicTools,\n boosted_keywords: this.options.boostedKeywords,\n generate_no_input_poke_text: this.options.generateNoInputPokeText,\n no_input_poke_sec: this.options.noInputPokeSec,\n no_input_poke_text: this.options.noInputPokeText,\n no_input_end_conversation_sec: this.options.noInputEndConversationSec,\n });\n }\n\n private handleServerMessage(message: ServerEvent): void {\n if (this.closed) {\n return;\n }\n\n switch (message.type) {\n case 'assistant_started_speaking':\n this.startNewAssistantTurn();\n break;\n case 'assistant_finished_speaking':\n this.finishAssistantTurn();\n break;\n case 'audio_chunk':\n this.handleAudioChunk(message);\n break;\n case 'input_text':\n this.handleInputText(message);\n break;\n case 'user_started_speaking':\n this.handleInputSpeechStarted();\n break;\n case 'user_finished_speaking':\n this.handleInputSpeechStopped();\n break;\n case 'error':\n this.emitError(new Error(message.error.message), false);\n break;\n case 'tool_call':\n this.emitError(\n new Error(\n `WebSocket tool calls are not yet supported by the Phonic realtime model with LiveKit Agents.`,\n ),\n false,\n );\n break;\n case 'assistant_ended_conversation':\n this.emitError(\n new Error(\n 'assistant_ended_conversation is not supported by the Phonic realtime model with LiveKit Agents.',\n ),\n false,\n );\n break;\n case 'conversation_created':\n this.conversationId = message.conversation_id;\n this.logger.info(`Phonic Conversation began with ID: ${this.conversationId}`);\n break;\n case 'assistant_chose_not_to_respond':\n case 'ready_to_start_conversation':\n case 'input_cancelled':\n case 'tool_call_output_processed':\n case 'tool_call_interrupted':\n case 'dtmf':\n default:\n break;\n }\n }\n\n private handleAudioChunk(message: Phonic.AudioChunkResponsePayload): void {\n /**\n * Although Phonic sends audio chunks when the assistant is not speaking (i.e. containing silence or background noise),\n * we only process the chunks when the assistant is speaking to align with the generations model, whereby new streams are created for each turn.\n */\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (message.text) {\n gen.outputText += message.text;\n gen.textChannel.write(message.text);\n }\n\n if (message.audio) {\n const bytes = Buffer.from(message.audio, 'base64');\n const sampleCount = Math.floor(bytes.byteLength / Int16Array.BYTES_PER_ELEMENT);\n if (sampleCount > 0) {\n const pcm = new Int16Array(\n bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + sampleCount * Int16Array.BYTES_PER_ELEMENT,\n ),\n );\n const frame = new AudioFrame(\n pcm,\n PHONIC_OUTPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n sampleCount / PHONIC_NUM_CHANNELS,\n );\n gen.audioChannel.write(frame);\n }\n }\n }\n\n private handleInputText(message: Phonic.InputTextPayload): void {\n const itemId = shortuuid('PI_');\n this.emit('input_audio_transcription_completed', {\n itemId,\n transcript: message.text,\n isFinal: true,\n });\n\n this._chatCtx.addMessage({\n role: 'user',\n content: message.text,\n id: itemId,\n });\n }\n\n private handleInputSpeechStarted(): void {\n this.emit('input_speech_started', {});\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n private handleInputSpeechStopped(): void {\n this.emit('input_speech_stopped', {\n userTranscriptionEnabled: true,\n });\n }\n\n private startNewAssistantTurn(): void {\n if (this.currentGeneration) {\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n const responseId = shortuuid('PS_');\n\n const textChannel = stream.createStreamChannel<string>();\n const audioChannel = stream.createStreamChannel<AudioFrame>();\n const functionChannel = stream.createStreamChannel<llm.FunctionCall>();\n const messageChannel = stream.createStreamChannel<llm.MessageGeneration>();\n\n messageChannel.write({\n messageId: responseId,\n textStream: textChannel.stream(),\n audioStream: audioChannel.stream(),\n modalities: Promise.resolve(['audio', 'text']),\n });\n\n this.currentGeneration = {\n responseId,\n messageChannel,\n functionChannel,\n textChannel,\n audioChannel,\n outputText: '',\n };\n\n this.emit('generation_created', {\n messageStream: messageChannel.stream(),\n functionStream: functionChannel.stream(),\n userInitiated: false,\n responseId,\n });\n }\n\n private finishAssistantTurn(): void {\n this.closeCurrentGeneration({ interrupted: false });\n }\n\n private closeCurrentGeneration({ interrupted }: { interrupted: boolean }): void {\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (gen.outputText) {\n this._chatCtx.addMessage({\n role: 'assistant',\n content: gen.outputText,\n id: gen.responseId,\n interrupted,\n });\n }\n\n gen.textChannel.close();\n gen.audioChannel.close();\n gen.functionChannel.close();\n gen.messageChannel.close();\n this.currentGeneration = undefined;\n }\n\n private emitError(error: Error, recoverable: boolean): void {\n this.emit('error', {\n timestamp: Date.now(),\n label: 'phonic_realtime',\n type: 'realtime_model_error',\n error,\n recoverable,\n } satisfies llm.RealtimeModelError);\n }\n\n private *resampleAudio(frame: AudioFrame): Generator<AudioFrame> {\n if (this.inputResampler) {\n if (frame.sampleRate !== this.inputResamplerInputRate) {\n this.inputResampler = undefined;\n this.inputResamplerInputRate = undefined;\n }\n }\n\n if (\n this.inputResampler === undefined &&\n (frame.sampleRate !== PHONIC_INPUT_SAMPLE_RATE || frame.channels !== PHONIC_NUM_CHANNELS)\n ) {\n this.inputResampler = new AudioResampler(\n frame.sampleRate,\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n );\n this.inputResamplerInputRate = frame.sampleRate;\n }\n\n if (this.inputResampler) {\n for (const resampledFrame of this.inputResampler.push(frame)) {\n yield resampledFrame;\n }\n } else {\n yield frame;\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/realtime/realtime_model.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { APIConnectOptions } from '@livekit/agents';\nimport {\n AudioByteStream,\n DEFAULT_API_CONNECT_OPTIONS,\n llm,\n log,\n shortuuid,\n stream,\n} from '@livekit/agents';\nimport { AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { Phonic } from 'phonic';\nimport { PhonicClient } from 'phonic';\nimport type { ServerEvent, Voice } from './api_proto.js';\n\nconst PHONIC_INPUT_SAMPLE_RATE = 44100;\nconst PHONIC_OUTPUT_SAMPLE_RATE = 44100;\nconst PHONIC_NUM_CHANNELS = 1;\nconst PHONIC_INPUT_FRAME_MS = 20;\nconst DEFAULT_MODEL = 'merritt';\nconst WS_CLOSE_NORMAL = 1000;\n\nexport interface RealtimeModelOptions {\n apiKey: string;\n model: string;\n phonicAgent?: string;\n voice?: Voice | string;\n welcomeMessage?: string;\n generateWelcomeMessage?: boolean;\n project?: string;\n connOptions: APIConnectOptions;\n baseUrl?: string;\n languages?: string[];\n audioSpeed?: number;\n phonicTools?: string[];\n boostedKeywords?: string[];\n generateNoInputPokeText?: boolean;\n noInputPokeSec?: number;\n noInputPokeText?: string;\n noInputEndConversationSec?: number;\n /** Set by `updateInstructions` via `voice.Agent` rather than the RealtimeModel constructor */\n instructions?: string;\n}\n\nexport class RealtimeModel extends llm.RealtimeModel {\n /** @internal */\n _options: RealtimeModelOptions;\n\n get model(): string {\n return this._options.model;\n }\n\n constructor(\n options: {\n /**\n * Phonic API key. If not provided, will attempt to read from PHONIC_API_KEY environment variable\n */\n apiKey?: string;\n /**\n * The name of the model to use. Defaults to 'merritt'\n */\n model?: Phonic.ConfigPayload['model'] | string;\n /**\n * Phonic agent to use for the conversation. Options explicitly set here will override the agent settings.\n */\n phonicAgent?: string;\n /**\n * Voice ID for agent outputs\n */\n voice?: Voice;\n /**\n * Welcome message for the agent to say when the conversation starts. Ignored when generateWelcomeMessage is true\n */\n welcomeMessage?: string;\n /**\n * When true, the welcome message will be automatically generated and welcomeMessage will be ignored\n */\n generateWelcomeMessage?: boolean;\n /**\n * Project name to use for the conversation. Defaults to `main`\n */\n project?: string;\n /**\n * ISO 639-1 language codes the agent should recognize and speak\n */\n languages?: string[];\n /**\n * Audio playback speed\n */\n audioSpeed?: number;\n /**\n * Phonic tool names available to the assistant\n */\n phonicTools?: string[];\n /**\n * Keywords to boost in speech recognition\n */\n boostedKeywords?: string[];\n /**\n * Auto-generate poke text when user is silent\n */\n generateNoInputPokeText?: boolean;\n /**\n * Seconds of silence before sending poke message\n */\n noInputPokeSec?: number;\n /**\n * Poke message text (ignored when generateNoInputPokeText is true)\n */\n noInputPokeText?: string;\n /**\n * Seconds of silence before ending conversation\n */\n noInputEndConversationSec?: number;\n /**\n * Connection options for the API connection\n */\n connOptions?: APIConnectOptions;\n baseUrl?: string;\n } = {},\n ) {\n super({\n messageTruncation: false,\n turnDetection: true,\n userTranscription: true,\n // TODO @Phonic-Co: Implement tool support\n // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.\n autoToolReplyGeneration: true,\n manualFunctionCalls: false,\n audioOutput: true,\n });\n\n const apiKey = options.apiKey || process.env.PHONIC_API_KEY;\n if (!apiKey) {\n throw new Error('Phonic API key is required. Provide apiKey or set PHONIC_API_KEY.');\n }\n\n this._options = {\n apiKey,\n voice: options.voice,\n phonicAgent: options.phonicAgent,\n project: options.project,\n welcomeMessage: options.welcomeMessage,\n generateWelcomeMessage: options.generateWelcomeMessage,\n languages: options.languages,\n audioSpeed: options.audioSpeed,\n phonicTools: options.phonicTools,\n boostedKeywords: options.boostedKeywords,\n generateNoInputPokeText: options.generateNoInputPokeText,\n noInputPokeSec: options.noInputPokeSec,\n noInputPokeText: options.noInputPokeText,\n noInputEndConversationSec: options.noInputEndConversationSec,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n model: options.model ?? DEFAULT_MODEL,\n baseUrl: options.baseUrl,\n };\n }\n\n /**\n * Create a new realtime session\n */\n session(): RealtimeSession {\n return new RealtimeSession(this);\n }\n\n async close(): Promise<void> {}\n}\n\ninterface GenerationState {\n responseId: string;\n messageChannel: stream.StreamChannel<llm.MessageGeneration>;\n functionChannel: stream.StreamChannel<llm.FunctionCall>;\n textChannel: stream.StreamChannel<string>;\n audioChannel: stream.StreamChannel<AudioFrame>;\n outputText: string;\n}\n\n/**\n * Realtime session for Phonic (https://docs.phonic.co/)\n */\nexport class RealtimeSession extends llm.RealtimeSession {\n private _tools: llm.ToolContext = {};\n private _chatCtx = llm.ChatContext.empty();\n\n private options: RealtimeModelOptions;\n private bstream: AudioByteStream;\n private inputResampler?: AudioResampler;\n private inputResamplerInputRate?: number;\n\n private currentGeneration?: GenerationState;\n private conversationId?: string;\n\n private client: PhonicClient;\n private socket?: Awaited<ReturnType<PhonicClient['conversations']['connect']>>;\n private logger = log();\n private closed = false;\n private configSent = false;\n private instructionsReady: Promise<void>;\n private resolveInstructionsReady: () => void;\n private connectTask: Promise<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super(realtimeModel);\n this.options = realtimeModel._options;\n\n this.resolveInstructionsReady = () => {};\n this.instructionsReady = new Promise<void>((resolve) => {\n this.resolveInstructionsReady = resolve;\n });\n\n this.client = new PhonicClient({\n apiKey: this.options.apiKey,\n baseUrl: this.options.baseUrl,\n });\n this.bstream = new AudioByteStream(\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n (PHONIC_INPUT_SAMPLE_RATE * PHONIC_INPUT_FRAME_MS) / 1000,\n );\n this.connectTask = this.connect().catch((error: unknown) => {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n this.emitError(normalizedError, false);\n });\n }\n\n get chatCtx(): llm.ChatContext {\n return this._chatCtx.copy();\n }\n\n get tools(): llm.ToolContext {\n return { ...this._tools };\n }\n\n async updateInstructions(instructions: string): Promise<void> {\n if (this.configSent) {\n this.logger.warn(\n 'updateInstructions called after config was already sent. Phonic does not support updating instructions mid-session.',\n );\n return;\n }\n this.options.instructions = instructions;\n this.resolveInstructionsReady();\n }\n\n async updateChatCtx(_chatCtx: llm.ChatContext): Promise<void> {\n this.logger.warn('updateChatCtx is not supported by the Phonic realtime model.');\n }\n\n async updateTools(tools: llm.ToolContext): Promise<void> {\n if (Object.keys(tools).length > 0) {\n this.logger.warn('Tool use is not supported by the Phonic realtime model.');\n }\n }\n\n updateOptions(_options: { toolChoice?: llm.ToolChoice | null }): void {\n this.logger.warn('updateOptions is not supported by the Phonic realtime model.');\n }\n\n pushAudio(frame: AudioFrame): void {\n if (this.closed) {\n return;\n }\n\n for (const resampledFrame of this.resampleAudio(frame)) {\n for (const chunk of this.bstream.write(resampledFrame.data.buffer as ArrayBuffer)) {\n const bytes = Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength);\n const payload: Phonic.AudioChunkPayload = {\n type: 'audio_chunk',\n audio: bytes.toString('base64'),\n };\n\n if (!this.socket) {\n continue;\n }\n this.socket.sendAudioChunk(payload);\n }\n }\n }\n\n // TODO @Phonic-Co: Implement generateReply\n async generateReply(_instructions?: string): Promise<llm.GenerationCreatedEvent> {\n throw new Error(\n 'generateReply is not yet supported by the Phonic realtime model. Consider using `welcomeMessage` instead.',\n );\n }\n\n async commitAudio(): Promise<void> {\n this.logger.warn('commitAudio is not supported by the Phonic realtime model.');\n }\n async clearAudio(): Promise<void> {\n this.logger.warn('clearAudio is not supported by the Phonic realtime model.');\n }\n\n async interrupt(): Promise<void> {\n this.logger.warn('interrupt is not supported by the Phonic realtime model.');\n }\n\n async truncate(_options: { messageId: string; audioEndMs: number; audioTranscript?: string }) {\n this.logger.warn('truncate is not supported by the Phonic realtime model.');\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.resolveInstructionsReady();\n this.closeCurrentGeneration({ interrupted: false });\n this.socket?.close();\n await this.connectTask;\n await super.close();\n }\n\n private async connect(): Promise<void> {\n this.socket = await this.client.conversations.connect({\n reconnectAttempts: this.options.connOptions.maxRetry,\n });\n\n if (this.closed) {\n this.socket.close();\n return;\n }\n\n this.socket.on('message', (message: unknown) =>\n this.handleServerMessage(message as ServerEvent),\n );\n this.socket.on('error', (error: Error) => this.emitError(error, false));\n this.socket.on('close', (event: { code?: number }) => {\n this.closeCurrentGeneration({ interrupted: false });\n if (!this.closed && event.code !== WS_CLOSE_NORMAL) {\n this.emitError(new Error(`Phonic STS socket closed with code ${event.code ?? -1}`), false);\n }\n });\n\n await this.socket.waitForOpen();\n await this.instructionsReady;\n if (this.closed) return;\n this.configSent = true;\n this.socket.sendConfig({\n type: 'config',\n model: this.options.model as Phonic.ConfigPayload['model'],\n agent: this.options.phonicAgent,\n project: this.options.project,\n welcome_message: this.options.welcomeMessage,\n generate_welcome_message: this.options.generateWelcomeMessage,\n system_prompt: this.options.instructions,\n voice_id: this.options.voice,\n input_format: 'pcm_44100',\n output_format: 'pcm_44100',\n recognized_languages: this.options.languages,\n audio_speed: this.options.audioSpeed,\n tools: this.options.phonicTools,\n boosted_keywords: this.options.boostedKeywords,\n generate_no_input_poke_text: this.options.generateNoInputPokeText,\n no_input_poke_sec: this.options.noInputPokeSec,\n no_input_poke_text: this.options.noInputPokeText,\n no_input_end_conversation_sec: this.options.noInputEndConversationSec,\n });\n }\n\n private handleServerMessage(message: ServerEvent): void {\n if (this.closed) {\n return;\n }\n\n switch (message.type) {\n case 'assistant_started_speaking':\n this.startNewAssistantTurn();\n break;\n case 'assistant_finished_speaking':\n this.finishAssistantTurn();\n break;\n case 'audio_chunk':\n this.handleAudioChunk(message);\n break;\n case 'input_text':\n this.handleInputText(message);\n break;\n case 'user_started_speaking':\n this.handleInputSpeechStarted();\n break;\n case 'user_finished_speaking':\n this.handleInputSpeechStopped();\n break;\n case 'error':\n this.emitError(new Error(message.error.message), false);\n break;\n case 'tool_call':\n this.emitError(\n new Error(\n `WebSocket tool calls are not yet supported by the Phonic realtime model with LiveKit Agents.`,\n ),\n false,\n );\n break;\n case 'assistant_ended_conversation':\n this.emitError(\n new Error(\n 'assistant_ended_conversation is not supported by the Phonic realtime model with LiveKit Agents.',\n ),\n false,\n );\n break;\n case 'conversation_created':\n this.conversationId = message.conversation_id;\n this.logger.info(`Phonic Conversation began with ID: ${this.conversationId}`);\n break;\n case 'assistant_chose_not_to_respond':\n case 'ready_to_start_conversation':\n case 'input_cancelled':\n case 'tool_call_output_processed':\n case 'tool_call_interrupted':\n case 'dtmf':\n default:\n break;\n }\n }\n\n private handleAudioChunk(message: Phonic.AudioChunkResponsePayload): void {\n /**\n * Although Phonic sends audio chunks when the assistant is not speaking (i.e. containing silence or background noise),\n * we only process the chunks when the assistant is speaking to align with the generations model, whereby new streams are created for each turn.\n */\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (message.text) {\n gen.outputText += message.text;\n gen.textChannel.write(message.text);\n }\n\n if (message.audio) {\n const bytes = Buffer.from(message.audio, 'base64');\n const sampleCount = Math.floor(bytes.byteLength / Int16Array.BYTES_PER_ELEMENT);\n if (sampleCount > 0) {\n const pcm = new Int16Array(\n bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + sampleCount * Int16Array.BYTES_PER_ELEMENT,\n ),\n );\n const frame = new AudioFrame(\n pcm,\n PHONIC_OUTPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n sampleCount / PHONIC_NUM_CHANNELS,\n );\n gen.audioChannel.write(frame);\n }\n }\n }\n\n private handleInputText(message: Phonic.InputTextPayload): void {\n const itemId = shortuuid('PI_');\n this.emit('input_audio_transcription_completed', {\n itemId,\n transcript: message.text,\n isFinal: true,\n });\n\n this._chatCtx.addMessage({\n role: 'user',\n content: message.text,\n id: itemId,\n });\n }\n\n private handleInputSpeechStarted(): void {\n this.emit('input_speech_started', {});\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n private handleInputSpeechStopped(): void {\n this.emit('input_speech_stopped', {\n userTranscriptionEnabled: true,\n });\n }\n\n private startNewAssistantTurn(): void {\n if (this.currentGeneration) {\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n const responseId = shortuuid('PS_');\n\n const textChannel = stream.createStreamChannel<string>();\n const audioChannel = stream.createStreamChannel<AudioFrame>();\n const functionChannel = stream.createStreamChannel<llm.FunctionCall>();\n const messageChannel = stream.createStreamChannel<llm.MessageGeneration>();\n\n messageChannel.write({\n messageId: responseId,\n textStream: textChannel.stream(),\n audioStream: audioChannel.stream(),\n modalities: Promise.resolve(['audio', 'text']),\n });\n\n this.currentGeneration = {\n responseId,\n messageChannel,\n functionChannel,\n textChannel,\n audioChannel,\n outputText: '',\n };\n\n this.emit('generation_created', {\n messageStream: messageChannel.stream(),\n functionStream: functionChannel.stream(),\n userInitiated: false,\n responseId,\n });\n }\n\n private finishAssistantTurn(): void {\n this.closeCurrentGeneration({ interrupted: false });\n }\n\n private closeCurrentGeneration({ interrupted }: { interrupted: boolean }): void {\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (gen.outputText) {\n this._chatCtx.addMessage({\n role: 'assistant',\n content: gen.outputText,\n id: gen.responseId,\n interrupted,\n });\n }\n\n gen.textChannel.close();\n gen.audioChannel.close();\n gen.functionChannel.close();\n gen.messageChannel.close();\n this.currentGeneration = undefined;\n }\n\n private emitError(error: Error, recoverable: boolean): void {\n this.emit('error', {\n timestamp: Date.now(),\n label: 'phonic_realtime',\n type: 'realtime_model_error',\n error,\n recoverable,\n } satisfies llm.RealtimeModelError);\n }\n\n private *resampleAudio(frame: AudioFrame): Generator<AudioFrame> {\n if (this.inputResampler) {\n if (frame.sampleRate !== this.inputResamplerInputRate) {\n this.inputResampler = undefined;\n this.inputResamplerInputRate = undefined;\n }\n }\n\n if (\n this.inputResampler === undefined &&\n (frame.sampleRate !== PHONIC_INPUT_SAMPLE_RATE || frame.channels !== PHONIC_NUM_CHANNELS)\n ) {\n this.inputResampler = new AudioResampler(\n frame.sampleRate,\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n );\n this.inputResamplerInputRate = frame.sampleRate;\n }\n\n if (this.inputResampler) {\n for (const resampledFrame of this.inputResampler.push(frame)) {\n yield resampledFrame;\n }\n } else {\n yield frame;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"realtime_model.d.ts","sourceRoot":"","sources":["../../src/realtime/realtime_model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime_model.d.ts","sourceRoot":"","sources":["../../src/realtime/realtime_model.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,7 @@ class RealtimeModel extends llm.RealtimeModel {
28
28
  // TODO @Phonic-Co: Implement tool support
29
29
  // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.
30
30
  autoToolReplyGeneration: true,
31
+ manualFunctionCalls: false,
31
32
  audioOutput: true
32
33
  });
33
34
  const apiKey = options.apiKey || process.env.PHONIC_API_KEY;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/realtime/realtime_model.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { APIConnectOptions } from '@livekit/agents';\nimport {\n AudioByteStream,\n DEFAULT_API_CONNECT_OPTIONS,\n llm,\n log,\n shortuuid,\n stream,\n} from '@livekit/agents';\nimport { AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { Phonic } from 'phonic';\nimport { PhonicClient } from 'phonic';\nimport type { ServerEvent, Voice } from './api_proto.js';\n\nconst PHONIC_INPUT_SAMPLE_RATE = 44100;\nconst PHONIC_OUTPUT_SAMPLE_RATE = 44100;\nconst PHONIC_NUM_CHANNELS = 1;\nconst PHONIC_INPUT_FRAME_MS = 20;\nconst DEFAULT_MODEL = 'merritt';\nconst WS_CLOSE_NORMAL = 1000;\n\nexport interface RealtimeModelOptions {\n apiKey: string;\n model: string;\n phonicAgent?: string;\n voice?: Voice | string;\n welcomeMessage?: string;\n generateWelcomeMessage?: boolean;\n project?: string;\n connOptions: APIConnectOptions;\n baseUrl?: string;\n languages?: string[];\n audioSpeed?: number;\n phonicTools?: string[];\n boostedKeywords?: string[];\n generateNoInputPokeText?: boolean;\n noInputPokeSec?: number;\n noInputPokeText?: string;\n noInputEndConversationSec?: number;\n /** Set by `updateInstructions` via `voice.Agent` rather than the RealtimeModel constructor */\n instructions?: string;\n}\n\nexport class RealtimeModel extends llm.RealtimeModel {\n /** @internal */\n _options: RealtimeModelOptions;\n\n get model(): string {\n return this._options.model;\n }\n\n constructor(\n options: {\n /**\n * Phonic API key. If not provided, will attempt to read from PHONIC_API_KEY environment variable\n */\n apiKey?: string;\n /**\n * The name of the model to use. Defaults to 'merritt'\n */\n model?: Phonic.ConfigPayload['model'] | string;\n /**\n * Phonic agent to use for the conversation. Options explicitly set here will override the agent settings.\n */\n phonicAgent?: string;\n /**\n * Voice ID for agent outputs\n */\n voice?: Voice;\n /**\n * Welcome message for the agent to say when the conversation starts. Ignored when generateWelcomeMessage is true\n */\n welcomeMessage?: string;\n /**\n * When true, the welcome message will be automatically generated and welcomeMessage will be ignored\n */\n generateWelcomeMessage?: boolean;\n /**\n * Project name to use for the conversation. Defaults to `main`\n */\n project?: string;\n /**\n * ISO 639-1 language codes the agent should recognize and speak\n */\n languages?: string[];\n /**\n * Audio playback speed\n */\n audioSpeed?: number;\n /**\n * Phonic tool names available to the assistant\n */\n phonicTools?: string[];\n /**\n * Keywords to boost in speech recognition\n */\n boostedKeywords?: string[];\n /**\n * Auto-generate poke text when user is silent\n */\n generateNoInputPokeText?: boolean;\n /**\n * Seconds of silence before sending poke message\n */\n noInputPokeSec?: number;\n /**\n * Poke message text (ignored when generateNoInputPokeText is true)\n */\n noInputPokeText?: string;\n /**\n * Seconds of silence before ending conversation\n */\n noInputEndConversationSec?: number;\n /**\n * Connection options for the API connection\n */\n connOptions?: APIConnectOptions;\n baseUrl?: string;\n } = {},\n ) {\n super({\n messageTruncation: false,\n turnDetection: true,\n userTranscription: true,\n // TODO @Phonic-Co: Implement tool support\n // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.\n autoToolReplyGeneration: true,\n audioOutput: true,\n });\n\n const apiKey = options.apiKey || process.env.PHONIC_API_KEY;\n if (!apiKey) {\n throw new Error('Phonic API key is required. Provide apiKey or set PHONIC_API_KEY.');\n }\n\n this._options = {\n apiKey,\n voice: options.voice,\n phonicAgent: options.phonicAgent,\n project: options.project,\n welcomeMessage: options.welcomeMessage,\n generateWelcomeMessage: options.generateWelcomeMessage,\n languages: options.languages,\n audioSpeed: options.audioSpeed,\n phonicTools: options.phonicTools,\n boostedKeywords: options.boostedKeywords,\n generateNoInputPokeText: options.generateNoInputPokeText,\n noInputPokeSec: options.noInputPokeSec,\n noInputPokeText: options.noInputPokeText,\n noInputEndConversationSec: options.noInputEndConversationSec,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n model: options.model ?? DEFAULT_MODEL,\n baseUrl: options.baseUrl,\n };\n }\n\n /**\n * Create a new realtime session\n */\n session(): RealtimeSession {\n return new RealtimeSession(this);\n }\n\n async close(): Promise<void> {}\n}\n\ninterface GenerationState {\n responseId: string;\n messageChannel: stream.StreamChannel<llm.MessageGeneration>;\n functionChannel: stream.StreamChannel<llm.FunctionCall>;\n textChannel: stream.StreamChannel<string>;\n audioChannel: stream.StreamChannel<AudioFrame>;\n outputText: string;\n}\n\n/**\n * Realtime session for Phonic (https://docs.phonic.co/)\n */\nexport class RealtimeSession extends llm.RealtimeSession {\n private _tools: llm.ToolContext = {};\n private _chatCtx = llm.ChatContext.empty();\n\n private options: RealtimeModelOptions;\n private bstream: AudioByteStream;\n private inputResampler?: AudioResampler;\n private inputResamplerInputRate?: number;\n\n private currentGeneration?: GenerationState;\n private conversationId?: string;\n\n private client: PhonicClient;\n private socket?: Awaited<ReturnType<PhonicClient['conversations']['connect']>>;\n private logger = log();\n private closed = false;\n private configSent = false;\n private instructionsReady: Promise<void>;\n private resolveInstructionsReady: () => void;\n private connectTask: Promise<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super(realtimeModel);\n this.options = realtimeModel._options;\n\n this.resolveInstructionsReady = () => {};\n this.instructionsReady = new Promise<void>((resolve) => {\n this.resolveInstructionsReady = resolve;\n });\n\n this.client = new PhonicClient({\n apiKey: this.options.apiKey,\n baseUrl: this.options.baseUrl,\n });\n this.bstream = new AudioByteStream(\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n (PHONIC_INPUT_SAMPLE_RATE * PHONIC_INPUT_FRAME_MS) / 1000,\n );\n this.connectTask = this.connect().catch((error: unknown) => {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n this.emitError(normalizedError, false);\n });\n }\n\n get chatCtx(): llm.ChatContext {\n return this._chatCtx.copy();\n }\n\n get tools(): llm.ToolContext {\n return { ...this._tools };\n }\n\n async updateInstructions(instructions: string): Promise<void> {\n if (this.configSent) {\n this.logger.warn(\n 'updateInstructions called after config was already sent. Phonic does not support updating instructions mid-session.',\n );\n return;\n }\n this.options.instructions = instructions;\n this.resolveInstructionsReady();\n }\n\n async updateChatCtx(_chatCtx: llm.ChatContext): Promise<void> {\n this.logger.warn('updateChatCtx is not supported by the Phonic realtime model.');\n }\n\n async updateTools(tools: llm.ToolContext): Promise<void> {\n if (Object.keys(tools).length > 0) {\n this.logger.warn('Tool use is not supported by the Phonic realtime model.');\n }\n }\n\n updateOptions(_options: { toolChoice?: llm.ToolChoice | null }): void {\n this.logger.warn('updateOptions is not supported by the Phonic realtime model.');\n }\n\n pushAudio(frame: AudioFrame): void {\n if (this.closed) {\n return;\n }\n\n for (const resampledFrame of this.resampleAudio(frame)) {\n for (const chunk of this.bstream.write(resampledFrame.data.buffer as ArrayBuffer)) {\n const bytes = Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength);\n const payload: Phonic.AudioChunkPayload = {\n type: 'audio_chunk',\n audio: bytes.toString('base64'),\n };\n\n if (!this.socket) {\n continue;\n }\n this.socket.sendAudioChunk(payload);\n }\n }\n }\n\n // TODO @Phonic-Co: Implement generateReply\n async generateReply(_instructions?: string): Promise<llm.GenerationCreatedEvent> {\n throw new Error(\n 'generateReply is not yet supported by the Phonic realtime model. Consider using `welcomeMessage` instead.',\n );\n }\n\n async commitAudio(): Promise<void> {\n this.logger.warn('commitAudio is not supported by the Phonic realtime model.');\n }\n async clearAudio(): Promise<void> {\n this.logger.warn('clearAudio is not supported by the Phonic realtime model.');\n }\n\n async interrupt(): Promise<void> {\n this.logger.warn('interrupt is not supported by the Phonic realtime model.');\n }\n\n async truncate(_options: { messageId: string; audioEndMs: number; audioTranscript?: string }) {\n this.logger.warn('truncate is not supported by the Phonic realtime model.');\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.resolveInstructionsReady();\n this.closeCurrentGeneration({ interrupted: false });\n this.socket?.close();\n await this.connectTask;\n await super.close();\n }\n\n private async connect(): Promise<void> {\n this.socket = await this.client.conversations.connect({\n reconnectAttempts: this.options.connOptions.maxRetry,\n });\n\n if (this.closed) {\n this.socket.close();\n return;\n }\n\n this.socket.on('message', (message: unknown) =>\n this.handleServerMessage(message as ServerEvent),\n );\n this.socket.on('error', (error: Error) => this.emitError(error, false));\n this.socket.on('close', (event: { code?: number }) => {\n this.closeCurrentGeneration({ interrupted: false });\n if (!this.closed && event.code !== WS_CLOSE_NORMAL) {\n this.emitError(new Error(`Phonic STS socket closed with code ${event.code ?? -1}`), false);\n }\n });\n\n await this.socket.waitForOpen();\n await this.instructionsReady;\n if (this.closed) return;\n this.configSent = true;\n this.socket.sendConfig({\n type: 'config',\n model: this.options.model as Phonic.ConfigPayload['model'],\n agent: this.options.phonicAgent,\n project: this.options.project,\n welcome_message: this.options.welcomeMessage,\n generate_welcome_message: this.options.generateWelcomeMessage,\n system_prompt: this.options.instructions,\n voice_id: this.options.voice,\n input_format: 'pcm_44100',\n output_format: 'pcm_44100',\n recognized_languages: this.options.languages,\n audio_speed: this.options.audioSpeed,\n tools: this.options.phonicTools,\n boosted_keywords: this.options.boostedKeywords,\n generate_no_input_poke_text: this.options.generateNoInputPokeText,\n no_input_poke_sec: this.options.noInputPokeSec,\n no_input_poke_text: this.options.noInputPokeText,\n no_input_end_conversation_sec: this.options.noInputEndConversationSec,\n });\n }\n\n private handleServerMessage(message: ServerEvent): void {\n if (this.closed) {\n return;\n }\n\n switch (message.type) {\n case 'assistant_started_speaking':\n this.startNewAssistantTurn();\n break;\n case 'assistant_finished_speaking':\n this.finishAssistantTurn();\n break;\n case 'audio_chunk':\n this.handleAudioChunk(message);\n break;\n case 'input_text':\n this.handleInputText(message);\n break;\n case 'user_started_speaking':\n this.handleInputSpeechStarted();\n break;\n case 'user_finished_speaking':\n this.handleInputSpeechStopped();\n break;\n case 'error':\n this.emitError(new Error(message.error.message), false);\n break;\n case 'tool_call':\n this.emitError(\n new Error(\n `WebSocket tool calls are not yet supported by the Phonic realtime model with LiveKit Agents.`,\n ),\n false,\n );\n break;\n case 'assistant_ended_conversation':\n this.emitError(\n new Error(\n 'assistant_ended_conversation is not supported by the Phonic realtime model with LiveKit Agents.',\n ),\n false,\n );\n break;\n case 'conversation_created':\n this.conversationId = message.conversation_id;\n this.logger.info(`Phonic Conversation began with ID: ${this.conversationId}`);\n break;\n case 'assistant_chose_not_to_respond':\n case 'ready_to_start_conversation':\n case 'input_cancelled':\n case 'tool_call_output_processed':\n case 'tool_call_interrupted':\n case 'dtmf':\n default:\n break;\n }\n }\n\n private handleAudioChunk(message: Phonic.AudioChunkResponsePayload): void {\n /**\n * Although Phonic sends audio chunks when the assistant is not speaking (i.e. containing silence or background noise),\n * we only process the chunks when the assistant is speaking to align with the generations model, whereby new streams are created for each turn.\n */\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (message.text) {\n gen.outputText += message.text;\n gen.textChannel.write(message.text);\n }\n\n if (message.audio) {\n const bytes = Buffer.from(message.audio, 'base64');\n const sampleCount = Math.floor(bytes.byteLength / Int16Array.BYTES_PER_ELEMENT);\n if (sampleCount > 0) {\n const pcm = new Int16Array(\n bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + sampleCount * Int16Array.BYTES_PER_ELEMENT,\n ),\n );\n const frame = new AudioFrame(\n pcm,\n PHONIC_OUTPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n sampleCount / PHONIC_NUM_CHANNELS,\n );\n gen.audioChannel.write(frame);\n }\n }\n }\n\n private handleInputText(message: Phonic.InputTextPayload): void {\n const itemId = shortuuid('PI_');\n this.emit('input_audio_transcription_completed', {\n itemId,\n transcript: message.text,\n isFinal: true,\n });\n\n this._chatCtx.addMessage({\n role: 'user',\n content: message.text,\n id: itemId,\n });\n }\n\n private handleInputSpeechStarted(): void {\n this.emit('input_speech_started', {});\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n private handleInputSpeechStopped(): void {\n this.emit('input_speech_stopped', {\n userTranscriptionEnabled: true,\n });\n }\n\n private startNewAssistantTurn(): void {\n if (this.currentGeneration) {\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n const responseId = shortuuid('PS_');\n\n const textChannel = stream.createStreamChannel<string>();\n const audioChannel = stream.createStreamChannel<AudioFrame>();\n const functionChannel = stream.createStreamChannel<llm.FunctionCall>();\n const messageChannel = stream.createStreamChannel<llm.MessageGeneration>();\n\n messageChannel.write({\n messageId: responseId,\n textStream: textChannel.stream(),\n audioStream: audioChannel.stream(),\n modalities: Promise.resolve(['audio', 'text']),\n });\n\n this.currentGeneration = {\n responseId,\n messageChannel,\n functionChannel,\n textChannel,\n audioChannel,\n outputText: '',\n };\n\n this.emit('generation_created', {\n messageStream: messageChannel.stream(),\n functionStream: functionChannel.stream(),\n userInitiated: false,\n responseId,\n });\n }\n\n private finishAssistantTurn(): void {\n this.closeCurrentGeneration({ interrupted: false });\n }\n\n private closeCurrentGeneration({ interrupted }: { interrupted: boolean }): void {\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (gen.outputText) {\n this._chatCtx.addMessage({\n role: 'assistant',\n content: gen.outputText,\n id: gen.responseId,\n interrupted,\n });\n }\n\n gen.textChannel.close();\n gen.audioChannel.close();\n gen.functionChannel.close();\n gen.messageChannel.close();\n this.currentGeneration = undefined;\n }\n\n private emitError(error: Error, recoverable: boolean): void {\n this.emit('error', {\n timestamp: Date.now(),\n label: 'phonic_realtime',\n type: 'realtime_model_error',\n error,\n recoverable,\n } satisfies llm.RealtimeModelError);\n }\n\n private *resampleAudio(frame: AudioFrame): Generator<AudioFrame> {\n if (this.inputResampler) {\n if (frame.sampleRate !== this.inputResamplerInputRate) {\n this.inputResampler = undefined;\n this.inputResamplerInputRate = undefined;\n }\n }\n\n if (\n this.inputResampler === undefined &&\n (frame.sampleRate !== PHONIC_INPUT_SAMPLE_RATE || frame.channels !== PHONIC_NUM_CHANNELS)\n ) {\n this.inputResampler = new AudioResampler(\n frame.sampleRate,\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n );\n this.inputResamplerInputRate = frame.sampleRate;\n }\n\n if (this.inputResampler) {\n for (const resampledFrame of this.inputResampler.push(frame)) {\n yield resampledFrame;\n }\n } else {\n yield frame;\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/realtime/realtime_model.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { APIConnectOptions } from '@livekit/agents';\nimport {\n AudioByteStream,\n DEFAULT_API_CONNECT_OPTIONS,\n llm,\n log,\n shortuuid,\n stream,\n} from '@livekit/agents';\nimport { AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { Phonic } from 'phonic';\nimport { PhonicClient } from 'phonic';\nimport type { ServerEvent, Voice } from './api_proto.js';\n\nconst PHONIC_INPUT_SAMPLE_RATE = 44100;\nconst PHONIC_OUTPUT_SAMPLE_RATE = 44100;\nconst PHONIC_NUM_CHANNELS = 1;\nconst PHONIC_INPUT_FRAME_MS = 20;\nconst DEFAULT_MODEL = 'merritt';\nconst WS_CLOSE_NORMAL = 1000;\n\nexport interface RealtimeModelOptions {\n apiKey: string;\n model: string;\n phonicAgent?: string;\n voice?: Voice | string;\n welcomeMessage?: string;\n generateWelcomeMessage?: boolean;\n project?: string;\n connOptions: APIConnectOptions;\n baseUrl?: string;\n languages?: string[];\n audioSpeed?: number;\n phonicTools?: string[];\n boostedKeywords?: string[];\n generateNoInputPokeText?: boolean;\n noInputPokeSec?: number;\n noInputPokeText?: string;\n noInputEndConversationSec?: number;\n /** Set by `updateInstructions` via `voice.Agent` rather than the RealtimeModel constructor */\n instructions?: string;\n}\n\nexport class RealtimeModel extends llm.RealtimeModel {\n /** @internal */\n _options: RealtimeModelOptions;\n\n get model(): string {\n return this._options.model;\n }\n\n constructor(\n options: {\n /**\n * Phonic API key. If not provided, will attempt to read from PHONIC_API_KEY environment variable\n */\n apiKey?: string;\n /**\n * The name of the model to use. Defaults to 'merritt'\n */\n model?: Phonic.ConfigPayload['model'] | string;\n /**\n * Phonic agent to use for the conversation. Options explicitly set here will override the agent settings.\n */\n phonicAgent?: string;\n /**\n * Voice ID for agent outputs\n */\n voice?: Voice;\n /**\n * Welcome message for the agent to say when the conversation starts. Ignored when generateWelcomeMessage is true\n */\n welcomeMessage?: string;\n /**\n * When true, the welcome message will be automatically generated and welcomeMessage will be ignored\n */\n generateWelcomeMessage?: boolean;\n /**\n * Project name to use for the conversation. Defaults to `main`\n */\n project?: string;\n /**\n * ISO 639-1 language codes the agent should recognize and speak\n */\n languages?: string[];\n /**\n * Audio playback speed\n */\n audioSpeed?: number;\n /**\n * Phonic tool names available to the assistant\n */\n phonicTools?: string[];\n /**\n * Keywords to boost in speech recognition\n */\n boostedKeywords?: string[];\n /**\n * Auto-generate poke text when user is silent\n */\n generateNoInputPokeText?: boolean;\n /**\n * Seconds of silence before sending poke message\n */\n noInputPokeSec?: number;\n /**\n * Poke message text (ignored when generateNoInputPokeText is true)\n */\n noInputPokeText?: string;\n /**\n * Seconds of silence before ending conversation\n */\n noInputEndConversationSec?: number;\n /**\n * Connection options for the API connection\n */\n connOptions?: APIConnectOptions;\n baseUrl?: string;\n } = {},\n ) {\n super({\n messageTruncation: false,\n turnDetection: true,\n userTranscription: true,\n // TODO @Phonic-Co: Implement tool support\n // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.\n autoToolReplyGeneration: true,\n manualFunctionCalls: false,\n audioOutput: true,\n });\n\n const apiKey = options.apiKey || process.env.PHONIC_API_KEY;\n if (!apiKey) {\n throw new Error('Phonic API key is required. Provide apiKey or set PHONIC_API_KEY.');\n }\n\n this._options = {\n apiKey,\n voice: options.voice,\n phonicAgent: options.phonicAgent,\n project: options.project,\n welcomeMessage: options.welcomeMessage,\n generateWelcomeMessage: options.generateWelcomeMessage,\n languages: options.languages,\n audioSpeed: options.audioSpeed,\n phonicTools: options.phonicTools,\n boostedKeywords: options.boostedKeywords,\n generateNoInputPokeText: options.generateNoInputPokeText,\n noInputPokeSec: options.noInputPokeSec,\n noInputPokeText: options.noInputPokeText,\n noInputEndConversationSec: options.noInputEndConversationSec,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n model: options.model ?? DEFAULT_MODEL,\n baseUrl: options.baseUrl,\n };\n }\n\n /**\n * Create a new realtime session\n */\n session(): RealtimeSession {\n return new RealtimeSession(this);\n }\n\n async close(): Promise<void> {}\n}\n\ninterface GenerationState {\n responseId: string;\n messageChannel: stream.StreamChannel<llm.MessageGeneration>;\n functionChannel: stream.StreamChannel<llm.FunctionCall>;\n textChannel: stream.StreamChannel<string>;\n audioChannel: stream.StreamChannel<AudioFrame>;\n outputText: string;\n}\n\n/**\n * Realtime session for Phonic (https://docs.phonic.co/)\n */\nexport class RealtimeSession extends llm.RealtimeSession {\n private _tools: llm.ToolContext = {};\n private _chatCtx = llm.ChatContext.empty();\n\n private options: RealtimeModelOptions;\n private bstream: AudioByteStream;\n private inputResampler?: AudioResampler;\n private inputResamplerInputRate?: number;\n\n private currentGeneration?: GenerationState;\n private conversationId?: string;\n\n private client: PhonicClient;\n private socket?: Awaited<ReturnType<PhonicClient['conversations']['connect']>>;\n private logger = log();\n private closed = false;\n private configSent = false;\n private instructionsReady: Promise<void>;\n private resolveInstructionsReady: () => void;\n private connectTask: Promise<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super(realtimeModel);\n this.options = realtimeModel._options;\n\n this.resolveInstructionsReady = () => {};\n this.instructionsReady = new Promise<void>((resolve) => {\n this.resolveInstructionsReady = resolve;\n });\n\n this.client = new PhonicClient({\n apiKey: this.options.apiKey,\n baseUrl: this.options.baseUrl,\n });\n this.bstream = new AudioByteStream(\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n (PHONIC_INPUT_SAMPLE_RATE * PHONIC_INPUT_FRAME_MS) / 1000,\n );\n this.connectTask = this.connect().catch((error: unknown) => {\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n this.emitError(normalizedError, false);\n });\n }\n\n get chatCtx(): llm.ChatContext {\n return this._chatCtx.copy();\n }\n\n get tools(): llm.ToolContext {\n return { ...this._tools };\n }\n\n async updateInstructions(instructions: string): Promise<void> {\n if (this.configSent) {\n this.logger.warn(\n 'updateInstructions called after config was already sent. Phonic does not support updating instructions mid-session.',\n );\n return;\n }\n this.options.instructions = instructions;\n this.resolveInstructionsReady();\n }\n\n async updateChatCtx(_chatCtx: llm.ChatContext): Promise<void> {\n this.logger.warn('updateChatCtx is not supported by the Phonic realtime model.');\n }\n\n async updateTools(tools: llm.ToolContext): Promise<void> {\n if (Object.keys(tools).length > 0) {\n this.logger.warn('Tool use is not supported by the Phonic realtime model.');\n }\n }\n\n updateOptions(_options: { toolChoice?: llm.ToolChoice | null }): void {\n this.logger.warn('updateOptions is not supported by the Phonic realtime model.');\n }\n\n pushAudio(frame: AudioFrame): void {\n if (this.closed) {\n return;\n }\n\n for (const resampledFrame of this.resampleAudio(frame)) {\n for (const chunk of this.bstream.write(resampledFrame.data.buffer as ArrayBuffer)) {\n const bytes = Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength);\n const payload: Phonic.AudioChunkPayload = {\n type: 'audio_chunk',\n audio: bytes.toString('base64'),\n };\n\n if (!this.socket) {\n continue;\n }\n this.socket.sendAudioChunk(payload);\n }\n }\n }\n\n // TODO @Phonic-Co: Implement generateReply\n async generateReply(_instructions?: string): Promise<llm.GenerationCreatedEvent> {\n throw new Error(\n 'generateReply is not yet supported by the Phonic realtime model. Consider using `welcomeMessage` instead.',\n );\n }\n\n async commitAudio(): Promise<void> {\n this.logger.warn('commitAudio is not supported by the Phonic realtime model.');\n }\n async clearAudio(): Promise<void> {\n this.logger.warn('clearAudio is not supported by the Phonic realtime model.');\n }\n\n async interrupt(): Promise<void> {\n this.logger.warn('interrupt is not supported by the Phonic realtime model.');\n }\n\n async truncate(_options: { messageId: string; audioEndMs: number; audioTranscript?: string }) {\n this.logger.warn('truncate is not supported by the Phonic realtime model.');\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.resolveInstructionsReady();\n this.closeCurrentGeneration({ interrupted: false });\n this.socket?.close();\n await this.connectTask;\n await super.close();\n }\n\n private async connect(): Promise<void> {\n this.socket = await this.client.conversations.connect({\n reconnectAttempts: this.options.connOptions.maxRetry,\n });\n\n if (this.closed) {\n this.socket.close();\n return;\n }\n\n this.socket.on('message', (message: unknown) =>\n this.handleServerMessage(message as ServerEvent),\n );\n this.socket.on('error', (error: Error) => this.emitError(error, false));\n this.socket.on('close', (event: { code?: number }) => {\n this.closeCurrentGeneration({ interrupted: false });\n if (!this.closed && event.code !== WS_CLOSE_NORMAL) {\n this.emitError(new Error(`Phonic STS socket closed with code ${event.code ?? -1}`), false);\n }\n });\n\n await this.socket.waitForOpen();\n await this.instructionsReady;\n if (this.closed) return;\n this.configSent = true;\n this.socket.sendConfig({\n type: 'config',\n model: this.options.model as Phonic.ConfigPayload['model'],\n agent: this.options.phonicAgent,\n project: this.options.project,\n welcome_message: this.options.welcomeMessage,\n generate_welcome_message: this.options.generateWelcomeMessage,\n system_prompt: this.options.instructions,\n voice_id: this.options.voice,\n input_format: 'pcm_44100',\n output_format: 'pcm_44100',\n recognized_languages: this.options.languages,\n audio_speed: this.options.audioSpeed,\n tools: this.options.phonicTools,\n boosted_keywords: this.options.boostedKeywords,\n generate_no_input_poke_text: this.options.generateNoInputPokeText,\n no_input_poke_sec: this.options.noInputPokeSec,\n no_input_poke_text: this.options.noInputPokeText,\n no_input_end_conversation_sec: this.options.noInputEndConversationSec,\n });\n }\n\n private handleServerMessage(message: ServerEvent): void {\n if (this.closed) {\n return;\n }\n\n switch (message.type) {\n case 'assistant_started_speaking':\n this.startNewAssistantTurn();\n break;\n case 'assistant_finished_speaking':\n this.finishAssistantTurn();\n break;\n case 'audio_chunk':\n this.handleAudioChunk(message);\n break;\n case 'input_text':\n this.handleInputText(message);\n break;\n case 'user_started_speaking':\n this.handleInputSpeechStarted();\n break;\n case 'user_finished_speaking':\n this.handleInputSpeechStopped();\n break;\n case 'error':\n this.emitError(new Error(message.error.message), false);\n break;\n case 'tool_call':\n this.emitError(\n new Error(\n `WebSocket tool calls are not yet supported by the Phonic realtime model with LiveKit Agents.`,\n ),\n false,\n );\n break;\n case 'assistant_ended_conversation':\n this.emitError(\n new Error(\n 'assistant_ended_conversation is not supported by the Phonic realtime model with LiveKit Agents.',\n ),\n false,\n );\n break;\n case 'conversation_created':\n this.conversationId = message.conversation_id;\n this.logger.info(`Phonic Conversation began with ID: ${this.conversationId}`);\n break;\n case 'assistant_chose_not_to_respond':\n case 'ready_to_start_conversation':\n case 'input_cancelled':\n case 'tool_call_output_processed':\n case 'tool_call_interrupted':\n case 'dtmf':\n default:\n break;\n }\n }\n\n private handleAudioChunk(message: Phonic.AudioChunkResponsePayload): void {\n /**\n * Although Phonic sends audio chunks when the assistant is not speaking (i.e. containing silence or background noise),\n * we only process the chunks when the assistant is speaking to align with the generations model, whereby new streams are created for each turn.\n */\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (message.text) {\n gen.outputText += message.text;\n gen.textChannel.write(message.text);\n }\n\n if (message.audio) {\n const bytes = Buffer.from(message.audio, 'base64');\n const sampleCount = Math.floor(bytes.byteLength / Int16Array.BYTES_PER_ELEMENT);\n if (sampleCount > 0) {\n const pcm = new Int16Array(\n bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + sampleCount * Int16Array.BYTES_PER_ELEMENT,\n ),\n );\n const frame = new AudioFrame(\n pcm,\n PHONIC_OUTPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n sampleCount / PHONIC_NUM_CHANNELS,\n );\n gen.audioChannel.write(frame);\n }\n }\n }\n\n private handleInputText(message: Phonic.InputTextPayload): void {\n const itemId = shortuuid('PI_');\n this.emit('input_audio_transcription_completed', {\n itemId,\n transcript: message.text,\n isFinal: true,\n });\n\n this._chatCtx.addMessage({\n role: 'user',\n content: message.text,\n id: itemId,\n });\n }\n\n private handleInputSpeechStarted(): void {\n this.emit('input_speech_started', {});\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n private handleInputSpeechStopped(): void {\n this.emit('input_speech_stopped', {\n userTranscriptionEnabled: true,\n });\n }\n\n private startNewAssistantTurn(): void {\n if (this.currentGeneration) {\n this.closeCurrentGeneration({ interrupted: true });\n }\n\n const responseId = shortuuid('PS_');\n\n const textChannel = stream.createStreamChannel<string>();\n const audioChannel = stream.createStreamChannel<AudioFrame>();\n const functionChannel = stream.createStreamChannel<llm.FunctionCall>();\n const messageChannel = stream.createStreamChannel<llm.MessageGeneration>();\n\n messageChannel.write({\n messageId: responseId,\n textStream: textChannel.stream(),\n audioStream: audioChannel.stream(),\n modalities: Promise.resolve(['audio', 'text']),\n });\n\n this.currentGeneration = {\n responseId,\n messageChannel,\n functionChannel,\n textChannel,\n audioChannel,\n outputText: '',\n };\n\n this.emit('generation_created', {\n messageStream: messageChannel.stream(),\n functionStream: functionChannel.stream(),\n userInitiated: false,\n responseId,\n });\n }\n\n private finishAssistantTurn(): void {\n this.closeCurrentGeneration({ interrupted: false });\n }\n\n private closeCurrentGeneration({ interrupted }: { interrupted: boolean }): void {\n const gen = this.currentGeneration;\n if (!gen) return;\n\n if (gen.outputText) {\n this._chatCtx.addMessage({\n role: 'assistant',\n content: gen.outputText,\n id: gen.responseId,\n interrupted,\n });\n }\n\n gen.textChannel.close();\n gen.audioChannel.close();\n gen.functionChannel.close();\n gen.messageChannel.close();\n this.currentGeneration = undefined;\n }\n\n private emitError(error: Error, recoverable: boolean): void {\n this.emit('error', {\n timestamp: Date.now(),\n label: 'phonic_realtime',\n type: 'realtime_model_error',\n error,\n recoverable,\n } satisfies llm.RealtimeModelError);\n }\n\n private *resampleAudio(frame: AudioFrame): Generator<AudioFrame> {\n if (this.inputResampler) {\n if (frame.sampleRate !== this.inputResamplerInputRate) {\n this.inputResampler = undefined;\n this.inputResamplerInputRate = undefined;\n }\n }\n\n if (\n this.inputResampler === undefined &&\n (frame.sampleRate !== PHONIC_INPUT_SAMPLE_RATE || frame.channels !== PHONIC_NUM_CHANNELS)\n ) {\n this.inputResampler = new AudioResampler(\n frame.sampleRate,\n PHONIC_INPUT_SAMPLE_RATE,\n PHONIC_NUM_CHANNELS,\n );\n this.inputResamplerInputRate = frame.sampleRate;\n }\n\n if (this.inputResampler) {\n for (const resampledFrame of this.inputResampler.push(frame)) {\n yield resampledFrame;\n }\n } else {\n yield frame;\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/agents-plugin-phonic",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
4
4
  "description": "Phonic STS plugin for LiveKit Node Agents",
5
5
  "main": "dist/index.js",
6
6
  "require": "dist/index.cjs",
@@ -29,14 +29,14 @@
29
29
  "@microsoft/api-extractor": "^7.35.0",
30
30
  "tsup": "^8.3.5",
31
31
  "typescript": "^5.0.0",
32
- "@livekit/agents": "1.0.46"
32
+ "@livekit/agents": "1.0.47"
33
33
  },
34
34
  "dependencies": {
35
35
  "phonic": "^0.30.37"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@livekit/rtc-node": "^0.13.24",
39
- "@livekit/agents": "1.0.46"
39
+ "@livekit/agents": "1.0.47"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsup --onSuccess \"pnpm build:types\"",
@@ -128,6 +128,7 @@ export class RealtimeModel extends llm.RealtimeModel {
128
128
  // TODO @Phonic-Co: Implement tool support
129
129
  // Phonic has automatic tool reply generation, but tools are not supported with LiveKit Agents yet.
130
130
  autoToolReplyGeneration: true,
131
+ manualFunctionCalls: false,
131
132
  audioOutput: true,
132
133
  });
133
134