fix(ts-config,proxybridge,voicebox): align voicebox config types and add missing proxy bridge command definitions

This commit is contained in:
2026-04-11 19:02:52 +00:00
parent 6fcdf4291a
commit a9fdfe5733
10 changed files with 212 additions and 54 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: 'siprouter',
version: '1.20.2',
version: '1.20.3',
description: 'undefined'
}

View File

@@ -8,6 +8,7 @@
import fs from 'node:fs';
import path from 'node:path';
import type { IVoiceboxConfig } from './voicebox.js';
// ---------------------------------------------------------------------------
// Shared types (previously in ts/sip/types.ts, now inlined)
@@ -160,24 +161,13 @@ export interface IContact {
// Voicebox configuration
// ---------------------------------------------------------------------------
export interface IVoiceboxConfig {
/** Unique ID — typically matches device ID or extension. */
id: string;
/** Whether this voicebox is active. */
enabled: boolean;
/** Custom TTS greeting text. */
greetingText?: string;
/** TTS voice ID (default 'af_bella'). */
greetingVoice?: string;
/** Path to uploaded WAV greeting (overrides TTS). */
greetingWavPath?: string;
/** Seconds to wait before routing to voicemail (default 25). */
noAnswerTimeoutSec?: number;
/** Maximum recording duration in seconds (default 120). */
maxRecordingSec?: number;
/** Maximum stored messages per box (default 50). */
maxMessages?: number;
}
// Canonical definition lives in voicebox.ts (imported at the top of this
// file) — re-exported here so consumers can import everything from a
// single config module without pulling in the voicebox implementation.
// This used to be a duplicated interface and caused
// "number | undefined is not assignable to number" type errors when
// passing config.voiceboxes into VoiceboxManager.init().
export type { IVoiceboxConfig };
// ---------------------------------------------------------------------------
// IVR configuration

View File

@@ -41,6 +41,14 @@ type TProxyCommands = {
params: { call_id: string };
result: { file_path: string; duration_ms: number };
};
add_leg: {
params: { call_id: string; number: string; provider_id?: string };
result: { leg_id: string };
};
remove_leg: {
params: { call_id: string; leg_id: string };
result: Record<string, never>;
};
add_device_leg: {
params: { call_id: string; device_id: string };
result: { leg_id: string };
@@ -83,6 +91,34 @@ type TProxyCommands = {
params: { model: string; voices: string; voice: string; text: string; output: string };
result: { output: string };
};
// WebRTC signaling — bridged from the browser via the TS control plane.
webrtc_offer: {
params: { session_id: string; sdp: string };
result: { sdp: string };
};
webrtc_ice: {
params: {
session_id: string;
candidate: string;
sdp_mid?: string;
sdp_mline_index?: number;
};
result: Record<string, never>;
};
webrtc_link: {
params: {
session_id: string;
call_id: string;
provider_media_addr: string;
provider_media_port: number;
sip_pt?: number;
};
result: Record<string, never>;
};
webrtc_close: {
params: { session_id: string };
result: Record<string, never>;
};
};
// ---------------------------------------------------------------------------
@@ -522,7 +558,7 @@ export async function sendProxyCommand<K extends keyof TProxyCommands>(
params: TProxyCommands[K]['params'],
): Promise<TProxyCommands[K]['result']> {
if (!bridge || !initialized) throw new Error('proxy engine not initialized');
return bridge.sendCommand(method as string, params as any) as any;
return bridge.sendCommand(method, params) as Promise<TProxyCommands[K]['result']>;
}
/** Shut down the proxy engine. */

View File

@@ -501,7 +501,7 @@ async function startProxyEngine(): Promise<void> {
onProxyEvent('recording_done', (data: any) => {
log(`[voicemail] recording done: ${data.file_path} (${data.duration_ms}ms) caller=${data.caller_number}`);
// Save voicemail metadata via VoiceboxManager.
voiceboxManager.addMessage?.('default', {
voiceboxManager.addMessage('default', {
callerNumber: data.caller_number || 'Unknown',
callerName: null,
fileName: data.file_path,

View File

@@ -29,12 +29,14 @@ export interface IVoiceboxConfig {
greetingVoice?: string;
/** Path to uploaded WAV greeting (overrides TTS). */
greetingWavPath?: string;
/** Seconds to wait before routing to voicemail (default 25). */
noAnswerTimeoutSec: number;
/** Maximum recording duration in seconds (default 120). */
maxRecordingSec: number;
/** Maximum stored messages per box (default 50). */
maxMessages: number;
/** Seconds to wait before routing to voicemail. Defaults to 25 when
* absent — both the config loader and `VoiceboxManager.init` apply
* the default via `??=`. */
noAnswerTimeoutSec?: number;
/** Maximum recording duration in seconds. Defaults to 120. */
maxRecordingSec?: number;
/** Maximum stored messages per box. Defaults to 50. */
maxMessages?: number;
}
export interface IVoicemailMessage {
@@ -148,6 +150,35 @@ export class VoiceboxManager {
// Message CRUD
// -------------------------------------------------------------------------
/**
* Convenience wrapper around `saveMessage` — used by the `recording_done`
* event handler, which has a raw recording path + caller info and needs
* to persist metadata. Generates `id`, sets `timestamp = now`, defaults
* `heard = false`, and normalizes `fileName` to a basename (the WAV is
* expected to already live in the box's directory).
*/
addMessage(
boxId: string,
info: {
callerNumber: string;
callerName?: string | null;
fileName: string;
durationMs: number;
},
): void {
const msg: IVoicemailMessage = {
id: crypto.randomUUID(),
boxId,
callerNumber: info.callerNumber,
callerName: info.callerName ?? undefined,
timestamp: Date.now(),
durationMs: info.durationMs,
fileName: path.basename(info.fileName),
heard: false,
};
this.saveMessage(msg);
}
/**
* Save a new voicemail message.
* The WAV file should already exist at the expected path.