Skip to main content

soma_codemode/
host.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use serde_json::Value;
6
7use crate::error::ToolError;
8use crate::types::{CodeModeCaller, CodeModeSurface, ToolDescriptor, ToolScope, UiLink};
9
10pub type HostFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
11
12#[derive(Debug, Clone)]
13pub struct ToolsRender {
14    pub fingerprint: String,
15    pub entries: Arc<[ToolDescriptor]>,
16    pub catalog_json: Arc<str>,
17    pub serialized_size: usize,
18}
19
20impl ToolsRender {
21    #[must_use]
22    pub fn empty() -> Self {
23        Self {
24            fingerprint: String::new(),
25            entries: Arc::from([]),
26            catalog_json: Arc::from("[]"),
27            serialized_size: 2,
28        }
29    }
30}
31
32#[derive(Debug, Clone)]
33pub struct ResolvedSnippet {
34    pub name: String,
35    pub code: String,
36    pub input: Value,
37}
38
39#[derive(Debug, Clone)]
40pub struct ToolCallOutcome {
41    pub value: Value,
42    pub ui: Option<UiLink>,
43}
44
45#[derive(Debug, Clone)]
46pub struct ExecCtx {
47    pub seq: u64,
48    pub execution_id: Option<Arc<str>>,
49    pub step_ordinal: Option<u64>,
50}
51
52impl ExecCtx {
53    #[must_use]
54    pub fn none() -> Self {
55        Self {
56            seq: 0,
57            execution_id: None,
58            step_ordinal: None,
59        }
60    }
61}
62
63#[derive(Debug, Clone, PartialEq)]
64pub enum StepDecision {
65    Replay(Value),
66    Execute,
67    Error { kind: String, message: String },
68}
69
70pub trait CodeModeHost: Send + Sync {
71    fn list_tools<'a>(
72        &'a self,
73        caller: &'a CodeModeCaller,
74        surface: CodeModeSurface,
75        scope: &'a ToolScope,
76        include_snippets: bool,
77        use_cache: bool,
78    ) -> HostFuture<'a, Result<ToolsRender, ToolError>>;
79
80    fn call_tool<'a>(
81        &'a self,
82        id: &'a str,
83        params: Value,
84        caller: &'a CodeModeCaller,
85        surface: CodeModeSurface,
86        scope: &'a ToolScope,
87        ctx: ExecCtx,
88    ) -> HostFuture<'a, Result<ToolCallOutcome, ToolError>>;
89
90    fn resolve_snippet<'a>(
91        &'a self,
92        name: &'a str,
93        input: Value,
94    ) -> HostFuture<'a, Result<ResolvedSnippet, ToolError>>;
95
96    fn semantic_search<'a>(
97        &'a self,
98        query: &'a str,
99        caller: &'a CodeModeCaller,
100        surface: CodeModeSurface,
101        scope: &'a ToolScope,
102        top_k: usize,
103    ) -> HostFuture<'a, Result<Vec<(String, f32)>, ToolError>> {
104        let _ = (query, caller, surface, scope, top_k);
105        Box::pin(async { Ok(Vec::new()) })
106    }
107
108    fn decide_step<'a>(&'a self, ctx: ExecCtx, name: &'a str) -> HostFuture<'a, StepDecision> {
109        let _ = (ctx, name);
110        Box::pin(async { StepDecision::Execute })
111    }
112
113    fn record_step<'a>(
114        &'a self,
115        ctx: ExecCtx,
116        name: &'a str,
117        value: &'a Value,
118    ) -> HostFuture<'a, Result<(), ToolError>> {
119        let _ = (ctx, name, value);
120        Box::pin(async { Ok(()) })
121    }
122
123    #[cfg(feature = "openapi")]
124    fn openapi_registry(&self) -> Option<soma_openapi::OpenApiRegistry> {
125        None
126    }
127
128    #[cfg(feature = "openapi")]
129    fn openapi_http_client(&self) -> Option<reqwest::Client> {
130        None
131    }
132}
133
134#[derive(Debug, Default)]
135pub struct NoopHost;
136
137impl CodeModeHost for NoopHost {
138    fn list_tools<'a>(
139        &'a self,
140        caller: &'a CodeModeCaller,
141        surface: CodeModeSurface,
142        scope: &'a ToolScope,
143        include_snippets: bool,
144        use_cache: bool,
145    ) -> HostFuture<'a, Result<ToolsRender, ToolError>> {
146        let _ = (caller, surface, scope, include_snippets, use_cache);
147        Box::pin(async { Ok(ToolsRender::empty()) })
148    }
149
150    fn call_tool<'a>(
151        &'a self,
152        id: &'a str,
153        params: Value,
154        caller: &'a CodeModeCaller,
155        surface: CodeModeSurface,
156        scope: &'a ToolScope,
157        ctx: ExecCtx,
158    ) -> HostFuture<'a, Result<ToolCallOutcome, ToolError>> {
159        let _ = (params, caller, surface, scope, ctx);
160        Box::pin(async move {
161            Err(ToolError::UnknownAction {
162                message: format!("unknown Code Mode tool `{id}`"),
163                valid: Vec::new(),
164                hint: None,
165            })
166        })
167    }
168
169    fn resolve_snippet<'a>(
170        &'a self,
171        name: &'a str,
172        input: Value,
173    ) -> HostFuture<'a, Result<ResolvedSnippet, ToolError>> {
174        let _ = input;
175        Box::pin(async move {
176            Err(ToolError::UnknownInstance {
177                message: format!("unknown Code Mode snippet `{name}`"),
178                valid: Vec::new(),
179            })
180        })
181    }
182}