Cosmo Realtime SDK
Examples

Browser voice page

Run the smallest complete browser app — RealtimeProvider, a live transcript, mic control, and one client tool — from the realtime-page example.

realtime-page is a Vite + React 19 page that puts every core React-SDK piece on one screen: a manually constructed RealtimeClient wrapped in RealtimeProvider, RealtimeAudio for playback, MicToggle for mute control, useTranscript() rendering turn-by-turn speech, and useToolCalls() showing the agent's tool activity. It declares one client tool — get_local_time — so you can watch a tool call round-trip without writing a backend.

Full source: examples/typescript/realtime-page in the examples repo. To build this app from an empty directory instead of running it, follow Build a voice React app, which uses this example as its base code.

Prerequisites

  • Node 18+
  • A workspace API key with the realtime:start scope (API keys)
  • A browser that can grant microphone access

Run it

  1. Clone the examples repo and install:

    git clone https://github.com/socratic-ai/cosmo-ai
    cd cosmo-ai/examples/typescript/realtime-page
    npm install

    The cosmo-ai dependency is the published npm package, so a fresh clone needs nothing beyond npm install.

  2. Start the dev server:

    npm run dev
  3. Open http://localhost:5173, paste your API key, and select Start Session. Allow microphone access when the browser asks.

Pasting a key into a local page is fine for development. An app you deploy must never contain a workspace key — mint short-lived end-user tokens on a backend instead. See End-user credentials.

How it works

The app is one file, src/App.tsx. Session state lives entirely in the SDK — the component tree just renders hooks:

function SessionView() {
  const session = useRealtimeSessionContext();
  const transport = useTransportState();
  const transcript = useTranscript();
  const toolCalls = useToolCalls();
  // render transport badge, MicToggle, RealtimeAudio, transcript bubbles…
}

The client tool is declared with the typed tool() helper and a zod schema, and runs in the page when the agent calls it:

import { clientTool } from 'cosmo-ai/tool';
import { zodInput } from 'cosmo-ai/tool/zod';
import { z } from 'zod/v4';

const getLocalTime = clientTool({
  name: 'get_local_time',
  description: 'Returns the local wall-clock time.',
  input: zodInput(
    z.object({
      locale: z.string().describe('BCP 47 locale tag, e.g. "en-US"').optional(),
    }),
  ),
  handler: async ({ locale }) => ({
    time: new Date().toLocaleTimeString(locale),
  }),
});

await client.agent({ tools: [getLocalTime] }).start();

One browser-specific detail worth copying: on Node the SDK reads COSMO_BASE_URL, but a browser has no environment, so the page names its backend through a <meta name="cosmo-base-url"> tag it writes before constructing the client. The default is the production origin; the form's Base URL field only matters when you point the page at a non-production backend.

What you should see

The form is replaced by a status badge that moves through the transport states to ready, and the agent greets you out loud. Speak, and your words appear as right-aligned transcript bubbles — dimmed while partial, solid when the turn is final — with the agent's replies on the left. Ask "what time is it?" and a Tool Calls panel appears showing get_local_time with its status, while the agent speaks the answer.

Troubleshooting

Issue: the status badge never reaches ready and no greeting plays. Cause: the key was rejected, or it belongs to a different backend than the Base URL field names. Solution: check the browser console for a 401, and confirm the key's workspace matches the backend you're pointing at.

Next steps

On this page