Skip to main content

runx_runtime/
adapter.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use runx_contracts::{ContextEntry, JsonObject};
5use runx_parser::SkillSource;
6use serde::{Deserialize, Serialize};
7
8use crate::RuntimeError;
9use crate::credentials::CredentialDelivery;
10
11/// Metadata key under which a skill's non-secret credential-delivery
12/// observations are recorded on [`SkillOutput::metadata`].
13pub const CREDENTIAL_DELIVERY_OBSERVATIONS_METADATA: &str = "credential_delivery_observations";
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum InvocationStatus {
17    Success,
18    Failure,
19}
20
21#[derive(Clone, Debug)]
22pub struct SkillInvocation {
23    pub skill_name: String,
24    pub source: SkillSource,
25    pub inputs: JsonObject,
26    pub resolved_inputs: JsonObject,
27    pub current_context: Vec<ContextEntry>,
28    pub skill_directory: PathBuf,
29    pub env: BTreeMap<String, String>,
30    pub credential_delivery: CredentialDelivery,
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
34pub struct SkillOutput {
35    pub status: InvocationStatus,
36    pub stdout: String,
37    pub stderr: String,
38    pub exit_code: Option<i32>,
39    pub duration_ms: u64,
40    pub metadata: JsonObject,
41}
42
43impl SkillOutput {
44    #[must_use]
45    pub fn succeeded(&self) -> bool {
46        self.status == InvocationStatus::Success
47    }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum FanoutExecutionMode {
52    Serial,
53    IsolatedParallel,
54}
55
56pub trait SkillAdapter {
57    fn adapter_type(&self) -> &'static str;
58    fn invoke(&self, request: SkillInvocation) -> Result<SkillOutput, RuntimeError>;
59
60    fn fanout_execution_mode(&self, source: &SkillSource) -> FanoutExecutionMode {
61        let _ = source;
62        FanoutExecutionMode::Serial
63    }
64
65    fn clone_for_fanout(&self) -> Option<Box<dyn SkillAdapter + Send + Sync>> {
66        None
67    }
68}
69
70pub(crate) struct BorrowedSkillAdapter<'a, A>
71where
72    A: SkillAdapter + ?Sized,
73{
74    adapter: &'a A,
75}
76
77impl<'a, A> BorrowedSkillAdapter<'a, A>
78where
79    A: SkillAdapter + ?Sized,
80{
81    pub(crate) fn new(adapter: &'a A) -> Self {
82        Self { adapter }
83    }
84}
85
86impl<A> SkillAdapter for BorrowedSkillAdapter<'_, A>
87where
88    A: SkillAdapter + ?Sized,
89{
90    fn adapter_type(&self) -> &'static str {
91        self.adapter.adapter_type()
92    }
93
94    fn invoke(&self, request: SkillInvocation) -> Result<SkillOutput, RuntimeError> {
95        self.adapter.invoke(request)
96    }
97
98    fn fanout_execution_mode(&self, source: &SkillSource) -> FanoutExecutionMode {
99        self.adapter.fanout_execution_mode(source)
100    }
101
102    fn clone_for_fanout(&self) -> Option<Box<dyn SkillAdapter + Send + Sync>> {
103        self.adapter.clone_for_fanout()
104    }
105}
106
107impl<A> SkillAdapter for Box<A>
108where
109    A: SkillAdapter + ?Sized,
110{
111    fn adapter_type(&self) -> &'static str {
112        self.as_ref().adapter_type()
113    }
114
115    fn invoke(&self, request: SkillInvocation) -> Result<SkillOutput, RuntimeError> {
116        self.as_ref().invoke(request)
117    }
118
119    fn fanout_execution_mode(&self, source: &SkillSource) -> FanoutExecutionMode {
120        self.as_ref().fanout_execution_mode(source)
121    }
122
123    fn clone_for_fanout(&self) -> Option<Box<dyn SkillAdapter + Send + Sync>> {
124        self.as_ref().clone_for_fanout()
125    }
126}