Skip to main content

gemini_live_harness/
adapter.rs

1//! Shared host-fed tool surface definitions owned by the harness layer.
2//!
3//! The host is the source of truth for which downstream tools exist. The
4//! harness consumes host-provided tool metadata and executors, then applies
5//! orchestration policy such as inline budgets, background continuation, and
6//! durable notification delivery.
7
8use futures_util::future::BoxFuture;
9use gemini_live::types::{FunctionCallRequest, FunctionResponse, Tool};
10
11/// Failures while resolving or executing a host-defined tool call.
12#[derive(Debug, thiserror::Error)]
13pub enum ToolExecutionError {
14    #[error("unsupported function `{name}`")]
15    UnsupportedFunction { name: String },
16    #[error("tool execution failed: {message}")]
17    Failed { message: String },
18    #[error("tool call `{call_id}` was cancelled")]
19    Cancelled { call_id: String },
20}
21
22impl ToolExecutionError {
23    pub fn unsupported_function(name: impl Into<String>) -> Self {
24        Self::UnsupportedFunction { name: name.into() }
25    }
26
27    pub fn failed(message: impl Into<String>) -> Self {
28        Self::Failed {
29            message: message.into(),
30        }
31    }
32
33    pub fn cancelled(call_id: impl Into<String>) -> Self {
34        Self::Cancelled {
35            call_id: call_id.into(),
36        }
37    }
38}
39
40/// Metadata about a host-defined tool exposed to users or debuggers.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct ToolDescriptor {
43    pub key: String,
44    pub summary: String,
45    pub kind: ToolKind,
46}
47
48/// High-level tool categories for host-side presentation.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ToolKind {
51    BuiltIn,
52    Local,
53    Remote,
54}
55
56/// Per-function capability declared by the host and consumed by the harness.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub struct ToolCapability {
59    pub can_continue_async_after_timeout: bool,
60}
61
62impl ToolCapability {
63    pub const INLINE_ONLY: Self = Self {
64        can_continue_async_after_timeout: false,
65    };
66
67    pub const BACKGROUND_CONTINUABLE: Self = Self {
68        can_continue_async_after_timeout: true,
69    };
70}
71
72/// One model-callable function declared by a host-fed provider.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct ToolSpecification {
75    pub function_name: String,
76    pub capability: ToolCapability,
77}
78
79impl ToolSpecification {
80    pub fn new(function_name: impl Into<String>, capability: ToolCapability) -> Self {
81        Self {
82            function_name: function_name.into(),
83            capability,
84        }
85    }
86}
87
88/// Host-fed metadata surface describing which tools exist.
89pub trait ToolProvider: Send + Sync + 'static {
90    /// Full `setup.tools` payload advertised for the next session connect.
91    fn advertised_tools(&self) -> Option<Vec<Tool>> {
92        None
93    }
94
95    /// User-facing catalog metadata for this provider's tools.
96    fn descriptors(&self) -> Vec<ToolDescriptor> {
97        Vec::new()
98    }
99
100    /// Function-level capabilities for locally executable tools.
101    fn specifications(&self) -> Vec<ToolSpecification> {
102        Vec::new()
103    }
104}
105
106/// Host-fed blocking execution surface for locally executable tools.
107pub trait ToolExecutor: Send + Sync + 'static {
108    /// Execute one server-requested function call.
109    fn execute<'a>(
110        &'a self,
111        call: FunctionCallRequest,
112    ) -> BoxFuture<'a, Result<FunctionResponse, ToolExecutionError>>;
113
114    /// Attempt to cancel an in-flight tool call by server `id`.
115    fn cancel(&self, _call_id: &str) -> bool {
116        false
117    }
118}
119
120/// Empty downstream tool source used by hosts that do not expose local tools.
121#[derive(Debug, Default, Clone, Copy)]
122pub struct NoopToolSource;
123
124impl ToolProvider for NoopToolSource {}
125
126impl ToolExecutor for NoopToolSource {
127    fn execute<'a>(
128        &'a self,
129        call: FunctionCallRequest,
130    ) -> BoxFuture<'a, Result<FunctionResponse, ToolExecutionError>> {
131        Box::pin(async move { Err(ToolExecutionError::unsupported_function(call.name)) })
132    }
133}