gemini_adk_fluent_rs/live/
contract.rs

1use gemini_adk_rs::live::{
2    ControlContract, ExtractorContract, PhaseContract, PreparationContract, PromotionContract,
3    RuntimeContract, ToolContract, TransitionContract,
4};
5use gemini_genai_rs::prelude::Tool;
6
7use super::Live;
8
9impl Live {
10    /// Describe the configured runtime contract before the session connects.
11    ///
12    /// The contract is intended for DevTools, replay validation, and generated
13    /// docs. It is metadata only; predicates and callbacks are represented by
14    /// stable names or boolean capabilities rather than executable closures.
15    pub fn describe_contract(&self) -> RuntimeContract {
16        let mut tools = describe_tools(&self.config.tools);
17        if let Some(dispatcher) = &self.dispatcher {
18            tools.extend(describe_tools(&dispatcher.to_tool_declarations()));
19        }
20        tools.extend(self.deferred_agent_tools.iter().map(|tool| ToolContract {
21            name: tool.name.clone(),
22            description: tool.description.clone(),
23            behavior: Some("AgentTool".into()),
24        }));
25
26        RuntimeContract {
27            version: 1,
28            model: self.config.model.to_string(),
29            tools,
30            phases: self.phases.iter().map(describe_phase).collect(),
31            initial_phase: self.initial_phase.clone(),
32            extractors: self
33                .extractors
34                .iter()
35                .map(|extractor| ExtractorContract {
36                    name: extractor.name().to_string(),
37                    window_size: extractor.window_size(),
38                    trigger: format!("{:?}", extractor.trigger()),
39                    promotions: extractor
40                        .promotion_rules()
41                        .iter()
42                        .map(|rule| PromotionContract {
43                            field: rule.field.clone(),
44                            state_key: rule.state_key.clone(),
45                            merge: format!("{:?}", rule.merge),
46                            has_predicate: rule.accept.is_some(),
47                        })
48                        .collect(),
49                })
50                .collect(),
51            computed: self.computed.describe(),
52            watchers: self.watchers.describe(),
53            controls: ControlContract {
54                soft_turn_timeout_ms: self
55                    .soft_turn_timeout
56                    .map(|timeout| timeout.as_millis() as u64),
57                steering_mode: format!("{:?}", self.steering_mode),
58                context_delivery: format!("{:?}", self.context_delivery),
59                tool_advisory: self.tool_advisory,
60                telemetry_interval_ms: self
61                    .telemetry_interval
62                    .map(|interval| interval.as_millis() as u64),
63                repair_enabled: self.repair_config.is_some(),
64                persistence_enabled: self.persistence.is_some(),
65            },
66        }
67    }
68}
69
70fn describe_phase(phase: &gemini_adk_rs::live::Phase) -> PhaseContract {
71    PhaseContract {
72        name: phase.name.clone(),
73        terminal: phase.terminal,
74        tools_enabled: phase.tools_enabled.clone(),
75        needs: phase.needs.clone(),
76        requires: phase.requires.clone(),
77        preparations: phase
78            .preparations
79            .iter()
80            .map(|prep| PreparationContract {
81                name: prep.name.clone(),
82                produces: prep.produces.clone(),
83            })
84            .collect(),
85        presents: phase.presents.clone(),
86        clear_on_enter: phase.clear_on_enter.clone(),
87        transitions: phase
88            .transitions
89            .iter()
90            .map(|transition| TransitionContract {
91                target: transition.target.clone(),
92                description: transition.description.clone(),
93                has_guard: true,
94            })
95            .collect(),
96        has_guard: phase.guard.is_some(),
97        prompt_on_enter: phase.prompt_on_enter,
98    }
99}
100
101fn describe_tools(tools: &[Tool]) -> Vec<ToolContract> {
102    let mut described = Vec::new();
103    for tool in tools {
104        if let Some(functions) = &tool.function_declarations {
105            described.extend(functions.iter().map(|function| {
106                ToolContract {
107                    name: function.name.clone(),
108                    description: function.description.clone(),
109                    behavior: function
110                        .behavior
111                        .as_ref()
112                        .map(|behavior| format!("{behavior:?}")),
113                }
114            }));
115        }
116        if tool.google_search.is_some() {
117            described.push(ToolContract {
118                name: "google_search".into(),
119                description: "Google Search grounding".into(),
120                behavior: None,
121            });
122        }
123        if tool.code_execution.is_some() {
124            described.push(ToolContract {
125                name: "code_execution".into(),
126                description: "Code execution".into(),
127                behavior: None,
128            });
129        }
130        if tool.url_context.is_some() {
131            described.push(ToolContract {
132                name: "url_context".into(),
133                description: "URL context retrieval".into(),
134                behavior: None,
135            });
136        }
137        if tool.google_search_retrieval.is_some() {
138            described.push(ToolContract {
139                name: "google_search_retrieval".into(),
140                description: "Google Search retrieval".into(),
141                behavior: None,
142            });
143        }
144    }
145    described
146}