Sessions and modes
A session is a server-issued realtime context (sessionId) that scopes rate limiting, conversation history, and audio routing. Every action — readAloud, sendBrowserAction, voice call, listener subscription — runs inside a session.
Modes
| Mode | Used for | Audio routing |
|---|---|---|
text | Pure text chat | None |
audio_playback | TTS / read-aloud / non-call voice replies | Streamed over the channel WebSocket |
voice_conversation | Full two-way WebRTC voice | SIP leg via the WebRTC gateway |
listen | Interpretation listener — subscribe to a TelPhi stream | Streamed over the channel WebSocket |
browser_actions | Pure BOA dispatch with no AI conversation | n/a |
Find-or-create per endpointId + mode
The SDK keeps one session per (endpointId, mode) pair. Subsequent calls with the same pair reuse the existing WebSocket and the same endpoint can run multiple modes side by side (for example, a voice_conversation speaker and a listen subscriber on the same endpoint during interpretation).
// First call opens the session
await delphi.readAloud('Hello', {endpointId});
// Same (endpointId, mode='audio_playback') — reuses the WebSocket
await delphi.readAloud('Are you there?', {endpointId});
// Different mode — opens a separate session for the same endpoint
await delphi.startCall({endpointId, autoDial: true});
Migrating between text and voice
When a flow exposes both web_chat and web_voice entry points, TelAPI can move a live session between text and voice_conversation without losing the same sessionId or conversation context. Check runtime.migration from capability discovery before showing handoff controls:
const caps = await delphi.getCapabilities(endpointId);
const canUpgrade = caps.runtime.migration.includes('text_to_voice');
const canDowngrade = caps.runtime.migration.includes('voice_to_text');
Upgrade — text → voice
Requires an open text session first:
delphi.setRemoteAudioElement(remoteAudioEl);
await delphi.upgradeToVoice({
endpointId,
autoDial: true,
browserContext: {identifier: 'user-123'}, // optional
});
TelAPI serialises the text FlowEngine session, re-issues a voice_conversation token for the same sessionId, and returns WebRTC gateway metadata. The SDK closes the text WebSocket, opens voice, re-imports chat history for the UI, and connects WebRTC.
REST equivalent: POST /api/v1/sessions/upgrade — see Browser SDK → Text chat.
Downgrade — voice → text
Requires an active voice_conversation session:
const session = await delphi.downgradeToText({
endpointId,
endCallFirst: true, // hang up WebRTC before reopening text (default)
});
session.sendTextChat('Can you recap in writing?');
The SDK sends prepare_voice_to_text_handoff on the channel, calls POST /api/v1/sessions/downgrade, tears down WebRTC when endCallFirst is true, then reopens mode: 'text'.
REST equivalent: Browser SDK → Text chat → Downgrade.
When migration is unavailable
| Condition | Effect |
|---|---|
FEATURE_WEBRTC=false | Voice upgrade returns 503; text-only still works if interactionModes.text is true. |
FEATURE_API_ACCESS=false | Session and capability routes are not registered. |
Flow missing web_chat or web_voice | runtime.migration omits the corresponding direction; open the supported mode only. |
Full walkthrough: SDK quick start → Text chat.
Text overlay on voice (not migration)
During an active voice call you can enable typed input without changing session mode: session.enableTextChat(), sendTextChat(), setResponseMode(). That is separate from upgrade/downgrade — see Text chat quick start.
Closing sessions
// Close just the audio-playback session
await delphi.endSession(endpointId, 'audio_playback');
// Close every mode for an endpoint
await delphi.endSession(endpointId);
// Close everything the client has open
await delphi.endAllSessions();
getSession(endpointId) returns an arbitrary match when multiple modes are open. Pass getSession(endpointId, mode) whenever your app may have more than one mode active for the same endpoint.
Idle timeout
Non-voice sessions auto-close after sessionIdleTimeoutMs (default 300_000 = 5 minutes). The clock resets on every outbound send and every inbound message. Set the value to 0 to disable.
Voice sessions ignore the timeout — they live until you explicitly hang up via endCall().
Session state shape
Each SessionClient exposes a small state object suitable for useSyncExternalStore:
const state = session.getState();
// { sessionId, mode, connected, lastActivityAt, lastMessage, ... }
session.subscribe(() => {
// re-render on state change
});
The orchestrator-level state on DelphiClient aggregates this for all open sessions:
const state = delphi.getState();
state.sessions; // [{ endpointId, sessionId, mode, connected, lastActivityAt }]
state.voiceCall; // { inCall, calling, registered, telproDomain, ... }
Next step
Configuration — the DelphiClient constructor options that shape session and call behavior.