fleetforge_runtime/gateway/
mod.rs

1//! Gateway traits that abstract external language models and tools.
2
3pub mod anthropic;
4pub mod openai;
5pub mod speech;
6
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use serde_json::Value;
11
12use self::speech::{NoopStt, NoopTts, SpeechToText, TextToSpeech};
13use fleetforge_prompt::{ChatMessage, ChatRole, ModelResponse, ModelUsage, ToolSpec};
14use fleetforge_telemetry::context::TraceContext;
15use fleetforge_trust::Trust;
16
17/// Minimal LLM contract; concrete providers live in adapter modules.
18#[async_trait]
19pub trait LanguageModel: Send + Sync {
20    async fn chat(
21        &self,
22        messages: &[ChatMessage],
23        tools: Option<&[ToolSpec]>,
24        response_schema: Option<&Value>,
25        strict: bool,
26        params: &Value,
27        trace: &TraceContext,
28    ) -> anyhow::Result<ModelResponse>;
29
30    async fn complete(
31        &self,
32        prompt: &str,
33        params: &Value,
34        trace: &TraceContext,
35    ) -> anyhow::Result<String> {
36        let messages = [ChatMessage {
37            role: ChatRole::User,
38            content: Value::String(prompt.to_owned()),
39            name: None,
40            tool_call_id: None,
41            metadata: None,
42            trust: Some(Trust::Untrusted),
43            trust_origin: None,
44        }];
45        let response = self
46            .chat(&messages, None, None, false, params, trace)
47            .await?;
48        Ok(response
49            .messages
50            .last()
51            .and_then(|m| m.content.as_str().map(str::to_owned))
52            .unwrap_or_default())
53    }
54}
55
56/// Tool invocation surface so executors can call arbitrary side-effecting endpoints.
57#[async_trait]
58pub trait ToolAdapter: Send + Sync {
59    async fn invoke(&self, name: &str, payload: &Value) -> anyhow::Result<Value>;
60}
61
62/// Helper enum to enumerate gateway kinds without leaking provider details.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum GatewayKind {
65    LanguageModel,
66    Tool,
67}
68
69/// Registry that collects available gateway implementations.
70#[derive(Clone)]
71pub struct GatewayRegistry {
72    pub language_model: Option<Arc<dyn LanguageModel>>,
73    pub tool: Option<Arc<dyn ToolAdapter>>,
74    pub stt: Arc<dyn SpeechToText>,
75    pub tts: Arc<dyn TextToSpeech>,
76}
77
78impl GatewayRegistry {
79    /// Construct a registry populated with the default no-op adapters.
80    pub fn with_defaults() -> Self {
81        Self {
82            language_model: None,
83            tool: None,
84            stt: Arc::new(NoopStt),
85            tts: Arc::new(NoopTts),
86        }
87    }
88
89    pub fn with_language_model(mut self, model: Arc<dyn LanguageModel>) -> Self {
90        self.language_model = Some(model);
91        self
92    }
93
94    pub fn with_tool(mut self, tool: Arc<dyn ToolAdapter>) -> Self {
95        self.tool = Some(tool);
96        self
97    }
98
99    pub fn with_stt<T>(mut self, adapter: T) -> Self
100    where
101        T: SpeechToText + 'static,
102    {
103        self.stt = Arc::new(adapter);
104        self
105    }
106
107    pub fn with_tts<T>(mut self, adapter: T) -> Self
108    where
109        T: TextToSpeech + 'static,
110    {
111        self.tts = Arc::new(adapter);
112        self
113    }
114}
115
116impl Default for GatewayRegistry {
117    fn default() -> Self {
118        Self::with_defaults()
119    }
120}