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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ console.log('Speech echo WebSocket app listening on port 3000');

Both `WebhookResponse` and `Session` support the same chainable verb methods:

`.say(opts)` `.play(opts)` `.gather(opts)` `.dial(opts)` `.llm(opts)` `.s2s(opts)` `.openai_s2s(opts)` `.google_s2s(opts)` `.elevenlabs_s2s(opts)` `.deepgram_s2s(opts)` `.ultravox_s2s(opts)` `.dialogflow(opts)` `.room(opts)` `.enqueue(opts)` `.dequeue(opts)` `.hangup()` `.pause(opts)` `.redirect(opts)` `.config(opts)` `.tag(opts)` `.dtmf(opts)` `.stream(opts)` `.transcribe(opts)` `.message(opts)` `.agent(opts)` `.dub(opts)` `.alert(opts)` `.answer(opts)` `.leave()` `.sipDecline(opts)` `.sipRefer(opts)` `.sipRequest(opts)`
`.say(opts)` `.play(opts)` `.gather(opts)` `.dial(opts)` `.llm(opts)` `.s2s(opts)` `.openai_s2s(opts)` `.google_s2s(opts)` `.elevenlabs_s2s(opts)` `.deepgram_s2s(opts)` `.ultravox_s2s(opts)` `.dialogflow(opts)` `.room(opts)` `.enqueue(opts)` `.dequeue(opts)` `.hangup()` `.pause(opts)` `.redirect(opts)` `.config(opts)` `.tag(opts)` `.dtmf(opts)` `.stream(opts)` `.transcribe(opts)` `.message(opts)` `.agent(opts)` `.dub(opts)` `.alert(opts)` `.answer(opts)` `.leave()` `.sipDecline(opts)` `.sipRefer(opts)` `.sipRequest(opts)` `.transfer(opts)`

Prefer `.room(opts)` and `.stream(opts)`. `.conference(opts)` and `.listen(opts)` remain as backward-compatible synonyms (same options), but new apps should use `room`/`stream`.

Expand Down
8 changes: 4 additions & 4 deletions typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"postpublish": "npm run clean-docs"
},
"dependencies": {
"@jambonz/schema": "^0.3.15",
"@jambonz/schema": "^0.3.16",
"ajv": "^8.17.1",
"ws": "^8.18.0"
},
Expand Down
5 changes: 5 additions & 0 deletions typescript/src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ export class CallsResource {
return this.update(callSid, { agent_update: data });
}

/** Redirect the in-progress call to a packaged transfer (blind or warm). */
async transfer(callSid: string, opts: NonNullable<UpdateCallRequest['transfer']>): Promise<void> {
return this.update(callSid, { transfer: opts });
}

/** Enable or disable server-side noise isolation. */
async noiseIsolation(
callSid: string,
Expand Down
5 changes: 5 additions & 0 deletions typescript/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type {
DubVerb,
EnqueueVerb,
GatherVerb,
Handoff,
HangupVerb,
JambonzApp,
LeaveVerb,
Expand All @@ -54,6 +55,10 @@ export type {
StreamVerb,
TagVerb,
TranscribeVerb,
TransferConfirm,
TransferDisposition,
TransferOptions,
TransferVerb,
Verb,
VerbName,
} from './verbs.js';
Expand Down
2 changes: 1 addition & 1 deletion typescript/src/types/llm-vendors.generated.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// AUTO-GENERATED — DO NOT EDIT BY HAND.
// Source of truth: @jambonz/schema@0.3.15 verbs/agent.schema.json (llm.vendor.enum)
// Source of truth: @jambonz/schema@0.3.16 verbs/agent.schema.json (llm.vendor.enum)
// Regenerate with: npm run gen:types
//
// This file derives the LLM vendor list from the JSON schema so the SDK's
Expand Down
4 changes: 3 additions & 1 deletion typescript/src/types/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import type { ActionHook } from './components.js';
import type { SayVerb, Verb } from './verbs.js';
import type { SayVerb, TransferVerb, Verb } from './verbs.js';

export interface CreateCallRequest {
/** Application SID to handle the call. */
Expand Down Expand Up @@ -114,6 +114,8 @@ export interface UpdateCallRequest {
};
/** Send DTMF digits. */
dtmf?: { digit: string; duration?: number };
/** Redirect the in-progress call to a packaged transfer (blind or warm). */
transfer?: Omit<TransferVerb, 'verb'>;
/** Tag metadata. */
tag?: Record<string, unknown>;
/** Mid-conversation agent update. */
Expand Down
92 changes: 91 additions & 1 deletion typescript/src/types/verbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ export interface LlmBaseOptions {
toolHook?: ActionHook;
/** Event types to receive via eventHook. */
events?: string[];
/** Declarative transfer-to-human: injects a transfer tool and runs the packaged transfer when the model calls it. */
handoff?: Handoff;
}

export interface LlmVerb extends LlmBaseOptions {
Expand Down Expand Up @@ -312,6 +314,8 @@ export interface AgentVerb {
eventHook?: ActionHook;
/** Webhook when the LLM requests a tool/function call. */
toolHook?: ActionHook;
/** Declarative transfer-to-human: injects a transfer tool and runs the packaged transfer when the model calls it. */
handoff?: Handoff;
/** Configuration for playing filler audio while tool calls are in progress. Prevents silence during long-running tool executions. */
toolFiller?: false | {
/** Filler mode. 'audio' plays a looping audio file. 'backchannel' uses TTS to speak short phrases. */
Expand Down Expand Up @@ -652,6 +656,91 @@ export interface SipReferVerb {
eventHook?: ActionHook;
}

/** Per-outcome fallback actions when a transfer does not complete normally. */
export interface TransferDisposition {
/** Action when the destination does not answer within timeout. Default: 'return'. */
onNoAnswer?: 'return' | 'voicemail' | 'hangup';
/** Action when the destination is busy. Default: 'return'. */
onBusy?: 'return' | 'voicemail' | 'hangup';
/** Action when the destination declines (confirm gate failed, AMD machine, or rejection). Default: 'return'. */
onDecline?: 'return' | 'voicemail' | 'hangup';
/** Action when a protocol-level error occurs on the destination leg. Default: 'return'. */
onFailure?: 'return' | 'voicemail' | 'hangup';
/** SIP URI or HTTP URL for voicemail. Required when any disposition value is 'voicemail'. */
voicemailUrl?: string;
}

/** Human-side acceptance gate: the destination must press a digit to accept. */
export interface TransferConfirm {
/** Text or URL of the prompt played to the destination. */
prompt: string;
/** Single DTMF digit the destination must press to accept. */
digit: string;
}

/**
* Shared configuration for blind and warm transfer choreography. Reused by the
* `transfer` verb and the `handoff` block on conversational verbs.
*/
export interface TransferOptions {
/** 'blind' hands off immediately; 'warm' lets the agent brief the destination first. */
mode: 'blind' | 'warm';
/** blind only: 'refer' sends a SIP REFER; 'dial' places a bridged outbound call. Default: 'refer'. */
blindMethod?: 'refer' | 'dial';
/** warm only: true = caller joins a three-way conference and hears the brief; false = caller is parked. Default: false. */
callerPresent?: boolean;
/** One or more destinations. All are rung simultaneously; first to answer wins. */
target: Target[];
/** Caller ID presented to the destination. */
callerId?: string;
/** warm/parked only: verbs for the parked caller while the agent briefs the destination. */
onHoldHook?: ActionHook;
/** Seconds to wait for the destination to answer before applying onNoAnswer. Default: 30. */
timeout?: number;
/** Optional human-side acceptance gate. */
confirm?: TransferConfirm;
/** warm only: optional answering-machine detection on the destination leg. */
amd?: Amd;
/** Per-outcome fallback actions. */
disposition?: TransferDisposition;
/** Fires when the transfer resolves (bridged, returned, voicemail, or failed). */
actionHook?: ActionHook;
/** Optional hook for in-progress transfer events (ringing, brief started). */
eventHook?: ActionHook;
}

/**
* Declarative transfer-to-human config for conversational verbs (`agent`, `llm`/`s2s`).
* The runtime injects a `transfer_to_human` tool and runs the packaged transfer
* choreography when the model calls it.
*/
export interface Handoff extends TransferOptions {
/** 'auto' = LLM writes the summary; 'none' = no spoken brief; {template} = guidance for the summary. Default: 'auto'. */
brief?: 'auto' | 'none' | { template: string };
/** Optional voice/vendor for the spoken brief. Defaults to the session synthesizer. */
briefSynthesizer?: Synthesizer;
/** Override the injected tool name. Default: 'transfer_to_human'. */
toolName?: string;
/** Override the injected tool description shown to the LLM. */
toolDescription?: string;
}

/**
* Packaged blind and warm (parked / three-way) transfer with built-in failure
* handling. Composes {@link TransferOptions} for the shared choreography.
*/
export interface TransferVerb extends TransferOptions {
verb: 'transfer';
id?: string;
/** warm only: the spoken summary delivered to the destination (human). */
brief?: {
/** The spoken briefing text. */
text: string;
/** Optional voice/vendor for the brief. Defaults to the session synthesizer. */
synthesizer?: Synthesizer;
};
}

// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -835,7 +924,8 @@ export type Verb =
| TagVerb
| SipDeclineVerb
| SipRequestVerb
| SipReferVerb;
| SipReferVerb
| TransferVerb;

/** A jambonz application — an array of verbs executed sequentially. */
export type JambonzApp = Verb[];
Expand Down
6 changes: 6 additions & 0 deletions typescript/src/verb-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
StreamVerb,
TagVerb,
TranscribeVerb,
TransferVerb,
Verb,
} from './types/verbs.js';
import { JambonzValidator } from './validator.js';
Expand Down Expand Up @@ -207,6 +208,11 @@ export class VerbBuilder {
return this.addVerb({ verb: 'sip:refer', ...opts });
}

/** Transfer the caller to a destination (blind, or warm parked/three-way) with built-in failure handling. */
transfer(opts: Omit<TransferVerb, 'verb'>): this {
return this.addVerb({ verb: 'transfer', ...opts });
}

// --- Utility ---

/** Set session-level defaults (TTS vendor/voice, STT vendor, VAD, etc.). */
Expand Down
2 changes: 2 additions & 0 deletions typescript/src/websocket/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ export class Session extends EventEmitter {
sipRequest(opts: Parameters<VerbBuilder['sipRequest']>[0]): this { this.builder.sipRequest(opts); return this; }
/** Transfer the call via SIP REFER. */
sipRefer(opts: Parameters<VerbBuilder['sipRefer']>[0]): this { this.builder.sipRefer(opts); return this; }
/** Transfer the caller to a destination (blind, or warm parked/three-way) with built-in failure handling. */
transfer(opts: Parameters<VerbBuilder['transfer']>[0]): this { this.builder.transfer(opts); return this; }
/** Set session-level defaults (TTS vendor/voice, STT vendor, VAD, etc.). */
config(opts: Parameters<VerbBuilder['config']>[0]): this { this.builder.config(opts); return this; }
/** Explicitly answer the call (sends a 200 OK). */
Expand Down