Current section

Files

Jump to
codex_sdk lib codex realtime runner.ex
Raw

lib/codex/realtime/runner.ex

defmodule Codex.Realtime.Runner do
@moduledoc """
Orchestrates realtime agent sessions.
A `Runner` is the equivalent of `Codex.AgentRunner` for realtime agents. It
automatically handles multiple turns by maintaining a persistent connection
with the underlying model layer.
The session manages the local history copy, executes tools, runs guardrails
and facilitates handoffs between agents.
Since this code runs on your server, it uses WebSockets by default. You can
optionally create your own custom model layer by implementing the
`Codex.Realtime.Model` behaviour.
## Example
# Create an agent
agent = Codex.Realtime.Agent.new(
name: "VoiceAssistant",
instructions: "You are a helpful voice assistant."
)
# Create a runner
runner = Codex.Realtime.Runner.new(agent,
config: %Codex.Realtime.Config.RunConfig{
tracing_disabled: false
}
)
# Start a session
{:ok, session} = Codex.Realtime.Runner.run(runner)
# Use the session
Codex.Realtime.Session.send_message(session, "Hello!")
# Subscribe to events
Codex.Realtime.Session.subscribe(session, self())
receive do
{:session_event, event} -> handle_event(event)
end
## Options
When creating a runner with `new/2`:
* `:config` - A `%Codex.Realtime.Config.RunConfig{}` for runtime settings
* `:model` - Optional custom model module implementing the Model behaviour
When starting a session with `run/2`:
* `:context` - Context map passed to the session
* `:model_config` - A `%Codex.Realtime.Config.ModelConfig{}` for API settings
* `:websocket_pid` - For testing with a mock WebSocket
* `:websocket_module` - Override the WebSocket module (default: WebSockex)
"""
alias Codex.Realtime.Agent
alias Codex.Realtime.Config.ModelConfig
alias Codex.Realtime.Config.RunConfig
alias Codex.Realtime.Session
defstruct [:starting_agent, :config, :model]
@type t :: %__MODULE__{
starting_agent: Agent.t() | nil,
config: RunConfig.t() | nil,
model: module() | nil
}
@doc """
Create a new realtime runner.
## Arguments
* `starting_agent` - The agent to start the session with.
## Options
* `:config` - Override parameters to use for the entire run.
* `:model` - The model to use. If not provided, will use the default
OpenAI realtime model.
## Example
agent = Codex.Realtime.Agent.new(name: "Assistant")
runner = Codex.Realtime.Runner.new(agent,
config: %Codex.Realtime.Config.RunConfig{tracing_disabled: true}
)
"""
@spec new(Agent.t(), keyword()) :: t()
def new(starting_agent, opts \\ []) do
%__MODULE__{
starting_agent: starting_agent,
config: Keyword.get(opts, :config),
model: Keyword.get(opts, :model)
}
end
@doc """
Start a realtime session.
Returns a session that can be used for bidirectional communication.
## Options
* `:context` - Context to pass to the session (default: `%{}`)
* `:model_config` - Model connection configuration (`%ModelConfig{}`)
* `:websocket_pid` - For testing with a mock WebSocket
* `:websocket_module` - Override the WebSocket module
## Returns
* `{:ok, pid}` - Session started successfully
* `{:error, reason}` - Failed to start session
## Example
runner = Runner.new(agent)
{:ok, session} = Runner.run(runner)
# Send messages
Session.send_message(session, "Hello!")
# Stream events
Session.subscribe(session, self())
receive do
{:session_event, event} -> handle_event(event)
end
# Close when done
Session.close(session)
"""
@spec run(t(), keyword()) :: {:ok, pid()} | {:error, term()}
def run(%__MODULE__{} = runner, opts \\ []) do
context = Keyword.get(opts, :context, %{})
model_config = Keyword.get(opts, :model_config, %ModelConfig{})
# Pass through testing options
websocket_pid = Keyword.get(opts, :websocket_pid)
websocket_module = Keyword.get(opts, :websocket_module)
session_opts =
[
agent: runner.starting_agent,
config: model_config,
run_config: runner.config || %RunConfig{},
context: context
]
|> maybe_add_opt(:websocket_pid, websocket_pid)
|> maybe_add_opt(:websocket_module, websocket_module)
Session.start_link(session_opts)
end
# Helpers
defp maybe_add_opt(opts, _key, nil), do: opts
defp maybe_add_opt(opts, key, value), do: Keyword.put(opts, key, value)
end