pub struct RealtimeRunner { /* private fields */ }realtime only.Expand description
A runner that manages a realtime session with tool execution.
RealtimeRunner provides a high-level interface for:
- Connecting to realtime providers
- Automatically executing tool calls
- Routing events to handlers
- Managing the session lifecycle
§Example
use adk_realtime::{RealtimeRunner, RealtimeConfig, ToolDefinition};
use adk_realtime::openai::OpenAIRealtimeModel;
#[tokio::main]
async fn main() -> Result<()> {
let model = OpenAIRealtimeModel::new(api_key, "gpt-realtime");
let runner = RealtimeRunner::builder()
.model(Box::new(model))
.instruction("You are a helpful voice assistant.")
.voice("alloy")
.tool_fn(
ToolDefinition::new("get_weather")
.with_description("Get weather for a location"),
|call| {
Ok(serde_json::json!({"temperature": 72, "condition": "sunny"}))
}
)
.build()?;
runner.connect().await?;
runner.run().await?;
Ok(())
}Implementations§
Source§impl RealtimeRunner
impl RealtimeRunner
Sourcepub fn builder() -> RealtimeRunnerBuilder
pub fn builder() -> RealtimeRunnerBuilder
Create a new builder.
Sourcepub async fn connect(&self) -> Result<(), RealtimeError>
pub async fn connect(&self) -> Result<(), RealtimeError>
Connect to the realtime provider.
Sourcepub async fn is_connected(&self) -> bool
pub async fn is_connected(&self) -> bool
Check if currently connected.
Sourcepub async fn session_id(&self) -> Option<String>
pub async fn session_id(&self) -> Option<String>
Get the session ID if connected.
Sourcepub async fn send_client_event(
&self,
event: ClientEvent,
) -> Result<(), RealtimeError>
pub async fn send_client_event( &self, event: ClientEvent, ) -> Result<(), RealtimeError>
Send a client event directly to the session.
This method intercepts internal control-plane events (like UpdateSession) to route
them through the provider-agnostic orchestration layer instead of forwarding raw JSON
to the underlying WebSocket transport. This guarantees that adk-realtime never leaks
invalid event payloads to providers (e.g., OpenAI or Gemini) and universally bridges
the Cognitive Handoff mechanics transparently.
Sourcepub async fn update_session(
&self,
config: SessionUpdateConfig,
) -> Result<(), RealtimeError>
pub async fn update_session( &self, config: SessionUpdateConfig, ) -> Result<(), RealtimeError>
Update the session configuration.
Delegates to Self::update_session_with_bridge with no bridge message.
§Example
use adk_realtime::config::{SessionUpdateConfig, RealtimeConfig};
async fn example(runner: &adk_realtime::RealtimeRunner) {
let update = SessionUpdateConfig(
RealtimeConfig::default().with_instruction("You are now a pirate.")
);
runner.update_session(update).await.unwrap();
}Sourcepub async fn update_session_with_bridge(
&self,
config: SessionUpdateConfig,
bridge_message: Option<String>,
) -> Result<(), RealtimeError>
pub async fn update_session_with_bridge( &self, config: SessionUpdateConfig, bridge_message: Option<String>, ) -> Result<(), RealtimeError>
Update the session configuration, optionally injecting a bridge message if a transport resumption (Phantom Reconnect) occurs.
The RealtimeRunner will attempt to mutate the session natively if the underlying API supports it (e.g., OpenAI). If it does not (e.g., Gemini), the Runner will queue a transport resumption, executing it only when the session is in a resumable state (Idle) to prevent data corruption.
The runner keeps only one pending resumption. If a new session update arrives while a resumption is already pending, the previous pending resumption is replaced. This is intentional: pending session updates represent desired end state, not an ordered command queue. The policy is last write wins.
Sourcepub async fn send_audio_chunk(
&self,
audio: &AudioChunk,
) -> Result<(), RealtimeError>
pub async fn send_audio_chunk( &self, audio: &AudioChunk, ) -> Result<(), RealtimeError>
Send a typed raw-audio chunk to the session.
This preserves the audio format at the provider boundary and lets the provider choose its native encoding path. Prefer this method when the caller already owns raw audio bytes.
Sourcepub async fn send_audio(&self, audio_base64: &str) -> Result<(), RealtimeError>
pub async fn send_audio(&self, audio_base64: &str) -> Result<(), RealtimeError>
Send base64-encoded audio to the session.
This compatibility entry point is useful when the caller already has a
base64 payload. Raw-audio callers should use
send_audio_chunk to avoid forcing an encoding
decision at the provider-neutral runner boundary.
Sourcepub async fn send_text(&self, text: &str) -> Result<(), RealtimeError>
pub async fn send_text(&self, text: &str) -> Result<(), RealtimeError>
Send text to the session.
Sourcepub async fn send_video_frame(
&self,
mime_type: &str,
data_base64: &str,
) -> Result<(), RealtimeError>
pub async fn send_video_frame( &self, mime_type: &str, data_base64: &str, ) -> Result<(), RealtimeError>
Send a base64-encoded video/image frame (e.g. image/jpeg) for
multimodal input, where the provider supports it (Gemini Live; OpenAI as
an image-in-context item).
Sourcepub async fn commit_audio(&self) -> Result<(), RealtimeError>
pub async fn commit_audio(&self) -> Result<(), RealtimeError>
Commit the audio buffer (for manual VAD mode).
Sourcepub async fn create_response(&self) -> Result<(), RealtimeError>
pub async fn create_response(&self) -> Result<(), RealtimeError>
Trigger a response from the model.
Sourcepub async fn interrupt(&self) -> Result<(), RealtimeError>
pub async fn interrupt(&self) -> Result<(), RealtimeError>
Interrupt the current response.
Sourcepub async fn disconnect_reason(&self) -> Option<DisconnectReason>
pub async fn disconnect_reason(&self) -> Option<DisconnectReason>
Get the next raw event from the session.
§Example
use adk_realtime::events::ServerEvent;
use tracing::{info, error};
async fn process_events(runner: &adk_realtime::RealtimeRunner) {
while let Some(event) = runner.next_event().await {
match event {
Ok(ServerEvent::SpeechStarted { .. }) => info!("User is speaking"),
Ok(_) => info!("Received other event"),
Err(e) => error!("Error: {e}"),
}
}
}Why the provider ended the stream, once Self::next_event has returned
None.
Callers that poll next_event never see the runner’s on_disconnect
dispatch, so without this a provider that deliberately closed an idle
session is indistinguishable from a dropped socket — and both get
recorded as the same generic stream failure.
pub async fn next_event(&self) -> Option<Result<ServerEvent, RealtimeError>>
Sourcepub async fn send_tool_response(
&self,
response: ToolResponse,
) -> Result<(), RealtimeError>
pub async fn send_tool_response( &self, response: ToolResponse, ) -> Result<(), RealtimeError>
Send a tool response to the session.
§Example
use adk_realtime::events::ToolResponse;
use serde_json::json;
async fn example(runner: &adk_realtime::RealtimeRunner) {
let response = ToolResponse {
call_id: "call_123".to_string(),
output: json!({"temperature": 72}),
};
runner.send_tool_response(response).await.unwrap();
}Sourcepub async fn dispatch_tool_call(
&self,
call_id: &str,
name: &str,
arguments: &str,
) -> Result<(), RealtimeError>
pub async fn dispatch_tool_call( &self, call_id: &str, name: &str, arguments: &str, ) -> Result<(), RealtimeError>
Execute a tool call against the registered handlers, sending the result
back to the model when auto_respond_tools is enabled.
This is the same dispatch the run loop performs for a
response.function_call_arguments.done event, exposed so that callers
driving the session manually via next_event — such
as IntegratedRealtimeRunner (available with the integration feature) —
can execute tools without re-implementing the lookup/respond logic.
Sourcepub async fn send_tool_result(
&self,
call_id: &str,
output: Value,
) -> Result<(), RealtimeError>
pub async fn send_tool_result( &self, call_id: &str, output: Value, ) -> Result<(), RealtimeError>
Sends a tool result the caller produced, honouring auto_respond_tools.
Used by the integration layer, which runs ADK tools through its own policy pipeline and
then needs the result delivered exactly as execute_tool_call would deliver it: the
output is sent now, and the single follow-up create_response is deferred until the
dispatching response closes, so several parallel calls produce one response.
§Example
let result = my_policy_pipeline.run(&call).await?;
runner.send_tool_result(&call.call_id, result).await?;Sourcepub async fn run(&self) -> Result<(), RealtimeError>
pub async fn run(&self) -> Result<(), RealtimeError>
Run the event loop, processing events until disconnected.
Sourcepub async fn instruction(&self) -> Option<String>
pub async fn instruction(&self) -> Option<String>
The system instruction the next connection will use.
Exposed so callers and tests can confirm what context a session was actually created with, rather than inferring it from log lines.
Sourcepub async fn prepend_instruction_context(&self, block: &str)
pub async fn prepend_instruction_context(&self, block: &str)
Prepends a context block to the system instruction before connecting.
The integration layer uses this to carry prior conversation history and recalled memory
into the provider session. Call it before RealtimeRunner::connect: providers read
the instruction at session creation, so a later change needs update_session.
§Example
runner.prepend_instruction_context("Previously discussed: the refund policy.").await;
runner.connect().await?;Sourcepub async fn respond_after_tools(&self) -> Result<(), RealtimeError>
pub async fn respond_after_tools(&self) -> Result<(), RealtimeError>
Trigger the single follow-up response owed after a tool-dispatching turn.
Call this when a response finishes (ResponseDone). If tool output(s)
were sent back during that response (auto_respond_tools), the model now
needs one create_response to speak its answer — issued here, after the
dispatch response is closed and every parallel tool output is in, rather
than once per tool call. Gemini’s create_response is a no-op, so this is
safely uniform across providers. No-op when nothing is pending.
Sourcepub async fn close(&self) -> Result<(), RealtimeError>
pub async fn close(&self) -> Result<(), RealtimeError>
Close the session.
Auto Trait Implementations§
impl !Freeze for RealtimeRunner
impl !RefUnwindSafe for RealtimeRunner
impl !UnwindSafe for RealtimeRunner
impl Send for RealtimeRunner
impl Sync for RealtimeRunner
impl Unpin for RealtimeRunner
impl UnsafeUnpin for RealtimeRunner
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> MaybeSend for Twhere
T: Send,
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.