Skip to content

Choose a model

Every adapter returns a RealtimeModel, so switching models changes one line and no app code.

import { boson } from '@vrealtime/boson';
import { openai } from '@vrealtime/openai';
import { gemini } from '@vrealtime/gemini';
boson({ voice: 'default', turnDetection: 'semantic' }); // BOSON_API_KEY
openai({ voice: 'marin' }); // OPENAI_API_KEY
gemini({ voice: 'Kore' }); // GEMINI_API_KEY

API keys default to the environment variable shown; pass apiKey to set one explicitly.

Boson Higgs RealtimeOpenAI RealtimeGemini Live
Package@vrealtime/boson@vrealtime/openai@vrealtime/gemini
Turn detectionserver or semanticserver or semanticserver
Streams tool argumentsyesyesno
Change tools mid-sessionyesyesno
Truncates to what was heardnativelynativelyby the runtime, locally
Input audio without transcodingPCM 8–48 kHz, G.711 µ-law, OpusPCM 24 kHz, G.711 µ-lawPCM 8–48 kHz

Boson and OpenAI share one OpenAI-compatible implementation and differ only in endpoints, authentication and extension events. When the app’s audio format is one the model accepts, audio passes through without transcoding.

Boson can search the web on its own servers. Turn it on with webSearch; the model decides when to search (tool_choice: "auto"):

boson({ webSearch: true });
boson({ webSearch: { allowedDomains: ['weather.gov', 'noaa.gov'], country: 'US' } }); // or excludedDomains

Boson runs these calls itself (executor: "server"), so they never reach the app as tool calls and the runtime never answers them: the model speaks a short preamble, the response ends, and Boson starts a new response with the answer once the search returns. Each call surfaces as an extension event, server_tool.call with its arguments and server_tool.result once Boson has the result. The result’s content is not passed on; Boson treats its format as unstable.

web_search is reserved: connecting with an app tool of that name fails, and a later tool update leaves that tool out and reports a recoverable invalid_request error. Domains are bare hostnames, at most five, and allowedDomains and excludedDomains exclude each other; options Boson would reject throw when the model is created. See Boson’s tool guide.

cascade() combines a speech-to-text model, any text LLM and a text-to-speech model into one realtime model. With @vrealtime/ai-sdk, the LLM can be anything the AI SDK supports: Claude, OpenAI, or a self-hosted OpenAI-compatible server.

import { createAnthropic } from '@ai-sdk/anthropic';
import { aiSdk } from '@vrealtime/ai-sdk';
import { cascade } from '@vrealtime/cascade';
import { openaiStt, openaiTts } from '@vrealtime/openai';
import { energyVad } from '@vrealtime/vad';
const model = cascade({
stt: openaiStt(), // speech to text
llm: aiSdk(createAnthropic()('claude-sonnet-5-5')), // any AI SDK model
tts: openaiTts(), // text to speech
vad: energyVad(), // turn detection
});

openaiStt() and openaiTts() work with any OpenAI-compatible speech endpoint through baseURL. Without tts the agent answers in text only; without stt it takes text input only.

Bias speech recognition toward names and terms, for example words from the open document, through the session config. It applies on every model that supports it:

serve(agent, {
configure: (principal, { context }) => ({
inputTranscription: { language: 'en', vocabulary: context.documentTerms },
}),
});

Every adapter reports failures as a RealtimeError with a provider-neutral code, so one handler works for every model. The provider’s own code stays in providerCode, and its raw payload in cause.

import { isRealtimeError } from '@vrealtime/core';
defineAgent({
// ...
hooks: {
onError: (error, recoverable) => {
if (!isRealtimeError(error)) return logger.error(error);
if (error.code === 'quota') return alertBilling(error.provider, error.message);
if (error.retryable && !recoverable) return reconnectLater();
logger.warn({ code: error.code, provider: error.provider, providerCode: error.providerCode });
},
},
});
codeMeaningretryable
authCredentials missing, invalid or expiredno
quotaBilling refused: credits exhausted, spending cap, contract endedno
rate_limitedToo many requestsyes
overloadedThe provider is at capacity, for example concurrent sessionsyes
invalid_requestThe provider rejected a request or the session configno
session_limitAn idle timeout or maximum duration ended the sessionyes
connectionThe connection dropped unexpectedlyyes
transcriptionTranscribing the user’s speech failed; the conversation goes onno
unsupportedInput the model cannot take, such as an audio format it cannot convertno
providerAnything else the provider reportedyes

recoverable says whether the session goes on; retryable says whether trying again unchanged, such as reconnecting, can succeed. Boson’s close codes map to overloaded (1013), auth (3000) and quota (4429). Gemini’s close codes are not mapped yet and report connection.

response.end carries usage with inputTokens, outputTokens and, where the provider reports it, cachedInputTokens: the part of the input read from the prompt cache. The runtime adds up every response of a turn into metrics.usage for onTurnEnd.