1use std::collections::BTreeMap;
5
6use runx_contracts::{ExecutionSemantics, JsonObject, JsonValue};
7use runx_core::policy::{CwdPolicy, SandboxProfile};
8use serde::{Deserialize, Serialize};
9
10use crate::graph::MintAuthorityDirective;
11
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct RawSkillIr {
15 pub frontmatter: JsonObject,
16 pub raw_frontmatter: String,
17 pub body: String,
18}
19
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct SkillInput {
23 #[serde(rename = "type")]
24 pub input_type: String,
25 pub required: bool,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub description: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub default: Option<JsonValue>,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct SkillRetryPolicy {
35 pub max_attempts: u64,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct SkillIdempotencyPolicy {
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub key: Option<String>,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "kebab-case")]
52pub enum SourceKind {
53 CliTool,
54 Mcp,
55 Catalog,
56 A2a,
57 Agent,
58 #[serde(rename = "agent-task")]
59 AgentStep,
60 HarnessHook,
61 Graph,
62 Http,
63 ExternalAdapter,
64 ThreadOutboxProvider,
65}
66
67impl SourceKind {
68 pub fn as_str(&self) -> &'static str {
69 match self {
70 SourceKind::CliTool => "cli-tool",
71 SourceKind::Mcp => "mcp",
72 SourceKind::Catalog => "catalog",
73 SourceKind::A2a => "a2a",
74 SourceKind::Agent => "agent",
75 SourceKind::AgentStep => "agent-task",
76 SourceKind::HarnessHook => "harness-hook",
77 SourceKind::Graph => "graph",
78 SourceKind::Http => "http",
79 SourceKind::ExternalAdapter => "external-adapter",
80 SourceKind::ThreadOutboxProvider => "thread-outbox-provider",
81 }
82 }
83}
84
85impl std::fmt::Display for SourceKind {
86 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 formatter.write_str(self.as_str())
88 }
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum InputMode {
94 Args,
95 Stdin,
96 None,
97}
98
99impl InputMode {
100 pub fn as_str(&self) -> &'static str {
101 match self {
102 InputMode::Args => "args",
103 InputMode::Stdin => "stdin",
104 InputMode::None => "none",
105 }
106 }
107}
108
109#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct SkillSource {
112 #[serde(rename = "type")]
113 pub source_type: SourceKind,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub command: Option<String>,
116 pub args: Vec<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub cwd: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub timeout_seconds: Option<u64>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub input_mode: Option<InputMode>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub sandbox: Option<SkillSandbox>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub server: Option<SkillMcpServer>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub catalog_ref: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub tool: Option<String>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub arguments: Option<JsonObject>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub agent_card_url: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub agent_identity: Option<String>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub agent: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub task: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub hook: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub outputs: Option<JsonObject>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub graph: Option<crate::ExecutionGraph>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub http: Option<SkillHttpSource>,
149 #[serde(skip_serializing_if = "Option::is_none")]
152 pub act: Option<ActDeclaration>,
153 pub raw: JsonObject,
154}
155
156#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(deny_unknown_fields)]
164pub struct ActDeclaration {
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub form: Option<String>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub form_from: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub purpose: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub purpose_from: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub legitimacy: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub legitimacy_from: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub reason_from: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub target_from: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub decision_from: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub effect_from: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub effect_field_from: Option<String>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub effect_from_input: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub effect_type: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub effect_prefix: Option<String>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub effect_prefix_from: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub actor_from: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub authority_from: Option<String>,
199 #[serde(skip_serializing_if = "Option::is_none")]
202 pub authority_term_from: Option<String>,
203 #[serde(skip_serializing_if = "Option::is_none")]
206 pub authority_parent_from: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
210 pub authority_subset_proof_from: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
217 pub mint_authority: Option<MintAuthorityDirective>,
218 #[serde(skip_serializing_if = "Option::is_none")]
221 pub requested_scope_from: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub previous_from: Option<String>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub reason_step: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub effect_step: Option<String>,
230}
231
232#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct SkillHttpSource {
240 pub url: String,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub method: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub headers: Option<BTreeMap<String, String>>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub allow_private_network: Option<bool>,
247}
248
249#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct SkillMcpServer {
252 pub command: String,
253 pub args: Vec<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub cwd: Option<String>,
256}
257
258#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct SkillSandbox {
261 pub profile: SandboxProfile,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub cwd_policy: Option<CwdPolicy>,
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub env_allowlist: Option<Vec<String>>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub network: Option<bool>,
268 pub writable_paths: Vec<String>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub require_enforcement: Option<bool>,
271 #[serde(skip)]
272 pub approved_escalation: Option<bool>,
273 pub raw: JsonObject,
274}
275
276#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct SkillArtifactContract {
279 #[serde(skip_serializing_if = "Option::is_none")]
280 pub emits: Option<Vec<String>>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub named_emits: Option<BTreeMap<String, String>>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub wrap_as: Option<String>,
285}
286
287#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct SkillQualityProfile {
290 pub heading: String,
291 pub content: String,
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
295pub enum ValidateSkillMode {
296 Strict,
297 Lenient,
298}
299
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
301pub struct ValidateSkillOptions {
302 pub mode: ValidateSkillMode,
303}
304
305impl Default for ValidateSkillOptions {
306 fn default() -> Self {
307 Self {
308 mode: ValidateSkillMode::Strict,
309 }
310 }
311}
312
313impl ValidateSkillOptions {
314 #[must_use]
315 pub const fn strict() -> Self {
316 Self {
317 mode: ValidateSkillMode::Strict,
318 }
319 }
320
321 #[must_use]
322 pub const fn lenient() -> Self {
323 Self {
324 mode: ValidateSkillMode::Lenient,
325 }
326 }
327}
328
329#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct ValidatedSkill {
332 pub name: String,
333 #[serde(skip_serializing_if = "Option::is_none")]
334 pub description: Option<String>,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub category: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub runx_category: Option<String>,
339 pub body: String,
340 pub source: SkillSource,
341 pub inputs: BTreeMap<String, SkillInput>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub auth: Option<JsonValue>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub risk: Option<JsonValue>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub runtime: Option<JsonValue>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub retry: Option<SkillRetryPolicy>,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 pub idempotency: Option<SkillIdempotencyPolicy>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub mutating: Option<bool>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub artifacts: Option<SkillArtifactContract>,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub quality_profile: Option<SkillQualityProfile>,
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub allowed_tools: Option<Vec<String>>,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub execution: Option<ExecutionSemantics>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub runx: Option<JsonObject>,
364 pub raw: RawSkillIr,
365}
366
367#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct SkillRunnerDefinition {
370 pub name: String,
371 pub default: bool,
372 pub source: SkillSource,
373 pub inputs: BTreeMap<String, SkillInput>,
374 #[serde(skip_serializing_if = "Option::is_none")]
375 pub auth: Option<JsonValue>,
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub risk: Option<JsonValue>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub runtime: Option<JsonValue>,
380 #[serde(skip_serializing_if = "Option::is_none")]
381 pub retry: Option<SkillRetryPolicy>,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub idempotency: Option<SkillIdempotencyPolicy>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 pub mutating: Option<bool>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub artifacts: Option<SkillArtifactContract>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub allowed_tools: Option<Vec<String>>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub execution: Option<ExecutionSemantics>,
392 #[serde(skip_serializing_if = "Option::is_none")]
393 pub runx: Option<JsonObject>,
394 pub raw: JsonObject,
395}