Skip to main content

soma_provider_core/
call.rs

1use std::sync::{Arc, Mutex};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::ProviderSurface;
7
8/// Authenticated, traceable context carried into an isolated provider runtime.
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
10pub struct ProviderInvocationContext {
11    pub request_id: String,
12    pub actor_id: Option<String>,
13    pub actor_scopes: Vec<String>,
14    pub traceparent: Option<String>,
15    pub tracestate: Option<String>,
16    /// Host-owned channel used to return capability progress to the surface
17    /// that initiated this invocation.
18    pub progress: ProviderProgressReporter,
19}
20
21/// One bounded progress notification emitted by an isolated provider.
22#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
23pub struct ProviderProgressEvent {
24    pub current: u64,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub total: Option<u64>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub message: Option<String>,
29}
30
31/// Cloneable, invocation-scoped progress collector.
32///
33/// The reporter deliberately has no global registry: a provider can only
34/// write to the collector that the authenticated host call supplied.
35#[derive(Debug, Clone, Default)]
36pub struct ProviderProgressReporter(Arc<Mutex<Vec<ProviderProgressEvent>>>);
37
38impl ProviderProgressReporter {
39    const MAX_EVENTS: usize = 256;
40    const MAX_MESSAGE_CHARS: usize = 1_024;
41
42    pub fn report(&self, current: u64, total: Option<u64>, message: Option<&str>) {
43        let mut events = self
44            .0
45            .lock()
46            .expect("provider progress lock should not be poisoned");
47        if events.len() == Self::MAX_EVENTS {
48            events.remove(0);
49        }
50        events.push(ProviderProgressEvent {
51            current,
52            total,
53            message: message.map(|value| value.chars().take(Self::MAX_MESSAGE_CHARS).collect()),
54        });
55    }
56
57    #[must_use]
58    pub fn events(&self) -> Vec<ProviderProgressEvent> {
59        self.0
60            .lock()
61            .expect("provider progress lock should not be poisoned")
62            .clone()
63    }
64}
65
66impl PartialEq for ProviderProgressReporter {
67    fn eq(&self, other: &Self) -> bool {
68        self.events() == other.events()
69    }
70}
71
72impl Eq for ProviderProgressReporter {}
73
74#[derive(Debug, Clone)]
75pub struct ProviderCall {
76    pub provider: String,
77    pub action: String,
78    pub params: Value,
79    pub surface: ProviderSurface,
80    pub snapshot_id: String,
81    pub context: ProviderInvocationContext,
82}
83
84impl ProviderCall {
85    pub fn new(action: impl Into<String>, arguments: Value) -> Self {
86        Self {
87            provider: String::new(),
88            action: action.into(),
89            params: arguments,
90            surface: ProviderSurface::Internal,
91            snapshot_id: String::new(),
92            context: ProviderInvocationContext::default(),
93        }
94    }
95
96    #[must_use]
97    pub fn with_surface(mut self, surface: ProviderSurface) -> Self {
98        self.surface = surface;
99        self
100    }
101
102    pub fn tool(&self) -> &str {
103        &self.action
104    }
105
106    pub fn arguments(&self) -> &Value {
107        &self.params
108    }
109
110    #[must_use]
111    pub fn with_context(mut self, context: ProviderInvocationContext) -> Self {
112        self.context = context;
113        self
114    }
115}