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
11pub 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}