Speko Docs

realtime

Provider-direct speech-to-speech with OpenAI Realtime, Gemini Live, and xAI Grok Voice.

Open a speech-to-speech (S2S) session. speko.realtime.connect() reserves a Speko entitlement and mints a short-lived provider credential via POST /v1/sessions, then connects directly to OpenAI Realtime, Gemini Live, or xAI Grok Voice. Audio never traverses a Speko media proxy.

Use this low-level resource only in a trusted runtime because it creates the session with your Speko API key. Browser apps should mint on their backend and pass the response to @spekoai/client. OpenAI uses WebRTC; Gemini Live and xAI use WebSockets.

import { Speko } from '@spekoai/sdk';

const speko = new Speko({ apiKey: process.env.SPEKO_API_KEY! });

const session = await speko.realtime.connect({
  provider: 'openai',
  model: 'gpt-realtime-2.1',
  agentId: 'agent_123',
  webhookTags: { environment: 'production' },
});

session.on((frame) => {
  if (frame.type === 'audio') play(frame.pcm);
  else if (frame.type === 'transcript') console.log(frame.role, frame.text);
});

session.sendAudio(pcm16Chunk);
// ... end of user turn
session.commit();

speko.realtime.connect(params)

Signature

speko.realtime.connect(
  params: RealtimeConnectParams,
): Promise<RealtimeSessionHandle>

RealtimeConnectParams

FieldTypeDescription
provider'openai' | 'google' | 'xai'S2S provider.
modelstringProvider-specific model id, such as gpt-realtime-2.1, gemini-3.1-flash-live-preview, or grok-voice-latest.
voicestring?Voice id override — interpreted per provider.
systemPromptstring?Initial system instruction.
temperaturenumber?
inputSampleRate16000 | 24000?PCM rate you'll be sending.
outputSampleRate16000 | 24000?PCM rate you want back.
toolsRealtimeToolSpec[]?Tool definitions the assistant may call.
agentIdstring?Agent used for workspace webhook routing.
webhookTagsRecord<string, string>?Case-sensitive workspace webhook routing tags; requires agentId.
metadataRecord<string, unknown>?Free-form metadata attached to the session record.
ttlSecondsnumber?Requested maximum session duration; the returned entitlement may impose a lower cap.
idempotencyKeystring?Stable bootstrap key to reuse after an ambiguous timeout. Generated when omitted.

RealtimeSessionHandle

PropertyTypeDescription
sessionIdstringServer-assigned session id.
expiresAtstringISO-8601 expiry of the delegated credential.
inputSampleRate16000 | 24000PCM rate the session accepts.
outputSampleRate16000 | 24000PCM rate the session returns.

Methods

MethodDescription
sendAudio(pcm: Uint8Array): voidSend a PCM16 audio chunk directly to the selected provider.
commit(): voidSignal an end-of-user-turn to the provider.
interrupt(): voidCancel the assistant's in-flight response.
sendToolResult(callId, output): voidReturn the result of a previously-issued tool_call.
on(handler): () => voidSubscribe to frames. Returns an unsubscribe callback.
close(code?, reason?): voidClose the socket. Idempotent.

RealtimeFrame variants

type RealtimeFrame =
  | { type: 'ready'; inputSampleRate: 16000 | 24000; outputSampleRate: 16000 | 24000 }
  | { type: 'audio'; pcm: Uint8Array; sampleRate: number }
  | { type: 'transcript'; role: 'user' | 'assistant'; text: string; final: boolean }
  | { type: 'tool_call'; callId: string; name: string; arguments: string }
  | { type: 'usage'; inputAudioTokens: number; outputAudioTokens: number }
  | { type: 'interruption'; at: 'user' | 'assistant' }
  | { type: 'server_tool_call'; id: string; name: string; status: 'started' | 'completed' | 'failed' }
  | { type: 'error'; code: string; message: string }
  | { type: 'close'; code: number; reason: string };

Example — tool calls

const session = await speko.realtime.connect({
  provider: 'openai',
  model: 'gpt-realtime-2.1',
  tools: [
    {
      name: 'get_weather',
      description: 'Current weather for a city.',
      parameters: {
        type: 'object',
        properties: { city: { type: 'string' } },
        required: ['city'],
      },
    },
  ],
});

session.on(async (frame) => {
  if (frame.type === 'tool_call' && frame.name === 'get_weather') {
    const { city } = JSON.parse(frame.arguments);
    const result = await fetchWeather(city);
    session.sendToolResult(frame.callId, result);
  }
});

Transport notes

  • Provider authentication. OpenAI uses its ephemeral bearer credential during WebRTC setup; xAI uses its delegated credential as a WebSocket subprotocol; Gemini Live uses its scoped token on the provider URL.
  • Runtime support. OpenAI requires WebRTC and Web Audio globals. Gemini Live and xAI require a WebSocket global; Node 22 can provide one with globalThis.WebSocket = (await import('ws')).WebSocket;.
  • WebSocket audio. For Gemini Live and xAI, the SDK forces binaryType = 'arraybuffer'; normalized inbound audio is emitted as Uint8Array.
  • Missing PCM. Until you call sendAudio, the provider sees no user input. Browser applications should normally use @spekoai/client, which owns microphone capture and playback.

On this page