Cosmo Realtime SDK
Capabilities

Tools

The three tool kinds — client, background client, and server — plus schemas, validation, and the dispatch lifecycle.

Tools are how the agent acts on the world. The taxonomy is small and explicit:

KindRuns whereYou writeTypical use
Client toolyour app, in-processa handlerupdate UI, read device state, hit your own APIs
Background client toolyour app, asynca handler that acks then finishes laterexports, long computations — anything slower than a beat of conversation
Server toolCosmo's backendnothing — a typed, zero-config opt-inweb search, vision, call control

All of these are declared in the agent's tools list and merged into one function-calling surface for the model. (A fourth kind, catalog — workspace-defined tools resolved by the server — is reserved on the wire but not executable yet; sending one is rejected at session start.)

Client tools

Declare a name, description, and JSON-Schema parameters; attach a handler. The SDK validates the model's arguments, runs your handler, and returns the result — with hooks able to deny or rewrite the call first.

Python — the @tool decorator derives the schema from a Pydantic model:

from typing import Any
from pydantic import BaseModel, Field
from cosmo_ai import CosmoRealtime, tool

class WeatherInput(BaseModel):
    city: str = Field(description="City name")

@tool
async def get_weather(input: WeatherInput) -> dict[str, Any]:
    """Current weather for a city."""
    return {"temp_c": 21.5}

agent = client.agent(instructions="…", tools=[get_weather])

TypeScript — the tool() builder, optionally with Zod:

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

const getWeather = tool({
  name: 'get_weather',
  description: 'Current weather for a city',
  input: zodInput(z.object({ city: z.string().describe('City name') })),
  handler: async ({ city }) => ({ tempC: 21.5 }),
});

SwiftSessionConfig.Tool.define with a typed Decodable argument struct:

struct WeatherArgs: Decodable, Sendable { let city: String }

let getWeather = try SessionConfig.Tool.define(
    name: "get_weather",
    description: "Current weather for a city",
    input: .object(properties: ["city": .string(description: "City name")], required: ["city"])
) { (args: WeatherArgs) in
    ["temp_c": .double(21.5)]
}

Schemas and validation

Tool schemas use a restricted JSON-Schema dialect (no pattern/format, no oneOf, no recursive models; 8 KiB serialized cap). Violations throw ToolSchemaError at construction time — at import in Python, at tool() in TypeScript, at define in Swift — never mid-call.

When the model sends malformed arguments, the SDK rejects them before your handler runs and returns a sanitized INVALID_INPUT error to the model (paths and constraints, never the submitted values). Your handler only ever sees validated, typed input.

Background client tools

A regular client tool blocks the conversation until it returns. If the work takes longer than a beat, make it a background tool: the handler acks immediately (the agent can say "working on it…" and keep talking) and delivers the result later through a job handle.

@tool(background=True)
async def export_report(input: ExportInput, job: ClientToolJob) -> None:
    """Export the quarterly report."""
    await job.ack(note="Starting the export")
    url = await run_export(input)          # takes a minute
    await job.complete(result={"url": url})

TypeScript uses tool({ background: true, handler: async (args, job) => … }); Swift uses SessionConfig.Tool.defineBackground. On failure, call job.fail(error=…) — the outcome is injected into the conversation whenever it lands.

Server tools

Server tools execute on Cosmo's backend; each built-in is its own typed, zero-config spec — the server owns the model-facing declaration, and you write no handler:

from cosmo_ai import WebSearchTool

agent = client.agent(instructions="…", tools=[WebSearchTool()])
const agent = client.agent({ tools: [{ kind: 'web_search' }] });
var config = SessionConfig()
config.tools = [.webSearch]
KindWhat it does
web_searchLive web search
examine_imageExamine the freshest published video frame at full resolution (image input)
detect_objectsLocate a named object in the frame, returning a box per matching instance
point_at_objectLocate a named object in the frame, returning points

A few first-party tools have no typed kinds yet — cosmo.end_call (telephony) and cosmo.view_state / cosmo.set_state (session state). In TypeScript they remain reachable through the deprecated generic kind: "server" name reference until they graduate to typed kinds or retire. The Python SDK no longer ships the generic reference, so on Python these tools are unavailable until their typed kinds land.

An opt-in the deployment cannot run does not fail the session — it is dropped, and the model never calls it. The signal is rejected_tools on the ready event, with a reason per entry; check it first when a tool "does nothing".

Watching dispatch

Every tool invocation — client or server — emits an observability sequence sharing one tool_call_id:

  1. tool-call — the model decided to call the tool
  2. tool-dispatch-started — the handler began executing
  3. tool-result — finished: ok plus a short summary

Client-tool executions additionally arrive as tool-invocation (the actual dispatch to your handler). Render a live tool timeline from these events — see Handle tool calls.

Guarding execution

Pre-tool-use hooks run before every client-tool handler and can deny the call or rewrite its arguments — the right place for confirmation gates, argument clamps, and "never touch production" rules that must hold no matter what the model decides.

On this page