feat(proxy-engine): add live TTS streaming interactions and incoming number range support

This commit is contained in:
2026-04-14 18:52:13 +00:00
parent adfc4726fd
commit feb3514de4
13 changed files with 1476 additions and 358 deletions

View File

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

View File

@@ -48,6 +48,24 @@ export interface IDeviceConfig {
extension: string;
}
export type TIncomingNumberMode = 'single' | 'range' | 'regex';
export interface IIncomingNumberConfig {
id: string;
label: string;
providerId?: string;
mode: TIncomingNumberMode;
countryCode?: string;
areaCode?: string;
localNumber?: string;
rangeEnd?: string;
pattern?: string;
// Legacy persisted fields kept for migration compatibility.
number?: string;
rangeStart?: string;
}
// ---------------------------------------------------------------------------
// Match/Action routing model
// ---------------------------------------------------------------------------
@@ -66,7 +84,7 @@ export interface ISipRouteMatch {
*
* Inbound: matches the provider-delivered DID / Request-URI user part.
* Outbound: matches the normalized dialed digits.
* Supports: exact string, prefix with trailing '*' (e.g. "+4930*"), or regex ("/^\\+49/").
* Supports: exact string, numeric range `start..end`, prefix with trailing '*' (e.g. "+4930*"), or regex ("/^\\+49/").
*/
numberPattern?: string;
@@ -234,6 +252,7 @@ export interface IAppConfig {
proxy: IProxyConfig;
providers: IProviderConfig[];
devices: IDeviceConfig[];
incomingNumbers?: IIncomingNumberConfig[];
routing: IRoutingConfig;
contacts: IContact[];
voiceboxes?: IVoiceboxConfig[];
@@ -288,6 +307,14 @@ export function loadConfig(): IAppConfig {
d.extension ??= '100';
}
cfg.incomingNumbers ??= [];
for (const incoming of cfg.incomingNumbers) {
if (!incoming.id) incoming.id = `incoming-${Date.now()}`;
incoming.label ??= incoming.id;
incoming.mode ??= incoming.pattern ? 'regex' : incoming.rangeStart || incoming.rangeEnd ? 'range' : 'single';
incoming.countryCode ??= incoming.mode === 'regex' ? undefined : '+49';
}
cfg.routing ??= { routes: [] };
cfg.routing.routes ??= [];

View File

@@ -266,6 +266,7 @@ async function handleRequest(
if (existing && ud.displayName !== undefined) existing.displayName = ud.displayName;
}
}
if (updates.incomingNumbers !== undefined) cfg.incomingNumbers = updates.incomingNumbers;
if (updates.routing) {
if (updates.routing.routes) {
cfg.routing.routes = updates.routing.routes;

View File

@@ -82,6 +82,19 @@ type TProxyCommands = {
};
result: { result: 'digit' | 'timeout' | 'cancelled'; digit?: string };
};
start_tts_interaction: {
params: {
call_id: string;
leg_id: string;
text: string;
voice?: string;
model?: string;
voices?: string;
expected_digits: string;
timeout_ms: number;
};
result: { result: 'digit' | 'timeout' | 'cancelled'; digit?: string };
};
add_tool_leg: {
params: {
call_id: string;
@@ -446,6 +459,40 @@ export async function startInteraction(
}
}
/**
* Start a live TTS interaction on a specific leg. The first chunk is rendered
* up front and the rest streams into the mixer while playback is already live.
*/
export async function startTtsInteraction(
callId: string,
legId: string,
text: string,
expectedDigits: string,
timeoutMs: number,
options?: {
voice?: string;
model?: string;
voices?: string;
},
): Promise<{ result: 'digit' | 'timeout' | 'cancelled'; digit?: string } | null> {
if (!bridge || !initialized) return null;
try {
return await sendProxyCommand('start_tts_interaction', {
call_id: callId,
leg_id: legId,
text,
expected_digits: expectedDigits,
timeout_ms: timeoutMs,
voice: options?.voice,
model: options?.model,
voices: options?.voices,
});
} catch (error: unknown) {
logFn?.(`[proxy-engine] start_tts_interaction error: ${errorMessage(error)}`);
return null;
}
}
/**
* Add a tool leg (recording or transcription) to a call.
* Tool legs receive per-source unmerged audio from all participants.