1use serde::{Deserialize, Serialize};
11
12pub const CONTRACT_VERSION_V1: &str = "provider-adapter.v1";
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17pub enum ContractVersion {
18 #[default]
20 #[serde(rename = "provider-adapter.v1")]
21 V1,
22}
23
24impl ContractVersion {
25 pub const fn as_str(self) -> &'static str {
27 match self {
28 Self::V1 => CONTRACT_VERSION_V1,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
35#[serde(rename_all = "kebab-case")]
36pub enum ProviderMaturity {
37 #[default]
39 Stable,
40 Stub,
42}
43
44impl ProviderMaturity {
45 pub const fn as_str(self) -> &'static str {
46 match self {
47 Self::Stable => "stable",
48 Self::Stub => "stub",
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct ProviderRef {
56 pub id: String,
57}
58
59impl ProviderRef {
60 pub fn new(id: impl Into<String>) -> Self {
61 Self { id: id.into() }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct ProviderMetadata {
68 pub id: String,
69 #[serde(default)]
70 pub contract_version: ContractVersion,
71 #[serde(default)]
72 pub maturity: ProviderMaturity,
73}
74
75impl ProviderMetadata {
76 pub fn new(id: impl Into<String>) -> Self {
77 Self {
78 id: id.into(),
79 contract_version: ContractVersion::V1,
80 maturity: ProviderMaturity::Stable,
81 }
82 }
83
84 pub fn with_maturity(mut self, maturity: ProviderMaturity) -> Self {
85 self.maturity = maturity;
86 self
87 }
88
89 pub fn provider_ref(&self) -> ProviderRef {
90 ProviderRef::new(self.id.clone())
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "kebab-case")]
97pub enum ProviderOperation {
98 Capabilities,
99 Healthcheck,
100 Execute,
101 Limits,
102 AuthState,
103}
104
105pub type ProviderResult<T> = std::result::Result<T, Box<ProviderError>>;
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct ProviderEnvelope<T> {
114 #[serde(default)]
115 pub contract_version: ContractVersion,
116 pub provider: ProviderRef,
117 pub operation: ProviderOperation,
118 #[serde(flatten)]
119 pub outcome: ProviderOutcome<T>,
120}
121
122impl<T> ProviderEnvelope<T> {
123 pub fn from_result(
124 provider: ProviderRef,
125 operation: ProviderOperation,
126 result: ProviderResult<T>,
127 ) -> Self {
128 match result {
129 Ok(payload) => Self::ok(provider, operation, payload),
130 Err(error) => Self::error(provider, operation, *error),
131 }
132 }
133
134 pub fn ok(provider: ProviderRef, operation: ProviderOperation, result: T) -> Self {
135 Self {
136 contract_version: ContractVersion::V1,
137 provider,
138 operation,
139 outcome: ProviderOutcome::Ok { result },
140 }
141 }
142
143 pub fn error(
144 provider: ProviderRef,
145 operation: ProviderOperation,
146 error: ProviderError,
147 ) -> Self {
148 Self {
149 contract_version: ContractVersion::V1,
150 provider,
151 operation,
152 outcome: ProviderOutcome::Error { error },
153 }
154 }
155
156 pub fn into_result(self) -> ProviderResult<T> {
157 match self.outcome {
158 ProviderOutcome::Ok { result } => Ok(result),
159 ProviderOutcome::Error { error } => Err(Box::new(error)),
160 }
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166#[serde(tag = "status", rename_all = "kebab-case")]
167pub enum ProviderOutcome<T> {
168 Ok { result: T },
169 Error { error: ProviderError },
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "kebab-case")]
175pub enum ProviderErrorCategory {
176 Auth,
177 RateLimit,
178 Network,
179 Timeout,
180 Validation,
181 Dependency,
182 Unavailable,
183 Internal,
184 Unknown,
185}
186
187impl ProviderErrorCategory {
188 pub const fn is_retryable(self) -> bool {
190 matches!(
191 self,
192 Self::RateLimit | Self::Network | Self::Timeout | Self::Unavailable
193 )
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199pub struct ProviderError {
200 pub category: ProviderErrorCategory,
201 pub code: String,
202 pub message: String,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub retryable: Option<bool>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub details: Option<serde_json::Value>,
207}
208
209impl ProviderError {
210 pub fn new(
211 category: ProviderErrorCategory,
212 code: impl Into<String>,
213 message: impl Into<String>,
214 ) -> Self {
215 Self {
216 category,
217 code: code.into(),
218 message: message.into(),
219 retryable: None,
220 details: None,
221 }
222 }
223
224 pub fn with_retryable(mut self, retryable: bool) -> Self {
225 self.retryable = Some(retryable);
226 self
227 }
228
229 pub fn with_details(mut self, details: serde_json::Value) -> Self {
230 self.details = Some(details);
231 self
232 }
233
234 pub fn is_retryable(&self) -> bool {
235 self.retryable
236 .unwrap_or_else(|| self.category.is_retryable())
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
242pub struct CapabilitiesRequest {
243 #[serde(default)]
244 pub include_experimental: bool,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
249pub struct CapabilitiesResponse {
250 #[serde(default, skip_serializing_if = "Vec::is_empty")]
251 pub capabilities: Vec<Capability>,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
256pub struct Capability {
257 pub name: String,
258 #[serde(default = "default_true")]
259 pub available: bool,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub description: Option<String>,
262}
263
264impl Capability {
265 pub fn available(name: impl Into<String>) -> Self {
266 Self {
267 name: name.into(),
268 available: true,
269 description: None,
270 }
271 }
272}
273
274fn default_true() -> bool {
275 true
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
280pub struct HealthcheckRequest {
281 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub timeout_ms: Option<u64>,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
287#[serde(rename_all = "kebab-case")]
288pub enum HealthStatus {
289 Healthy,
290 Degraded,
291 Unhealthy,
292 #[default]
293 Unknown,
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
298pub struct HealthcheckResponse {
299 #[serde(default)]
300 pub status: HealthStatus,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub summary: Option<String>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub details: Option<serde_json::Value>,
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
309pub struct ExecuteRequest {
310 pub task: String,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub input: Option<String>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub timeout_ms: Option<u64>,
315}
316
317impl ExecuteRequest {
318 pub fn new(task: impl Into<String>) -> Self {
319 Self {
320 task: task.into(),
321 input: None,
322 timeout_ms: None,
323 }
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
329pub struct ExecuteResponse {
330 #[serde(default)]
331 pub exit_code: i32,
332 #[serde(default)]
333 pub stdout: String,
334 #[serde(default)]
335 pub stderr: String,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub duration_ms: Option<u64>,
338}
339
340#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
342pub struct LimitsRequest {}
343
344#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
346pub struct LimitsResponse {
347 #[serde(default, skip_serializing_if = "Option::is_none")]
348 pub max_concurrency: Option<u32>,
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub max_timeout_ms: Option<u64>,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub max_input_bytes: Option<u64>,
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
357pub struct AuthStateRequest {}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
361#[serde(rename_all = "kebab-case")]
362pub enum AuthStateStatus {
363 Authenticated,
364 Unauthenticated,
365 Expired,
366 #[default]
367 Unknown,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
372pub struct AuthStateResponse {
373 #[serde(default)]
374 pub state: AuthStateStatus,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub subject: Option<String>,
377 #[serde(default, skip_serializing_if = "Vec::is_empty")]
378 pub scopes: Vec<String>,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub expires_at: Option<String>,
381}