1use serde::{Deserialize, Serialize};
3
4use crate::schema::{NonEmptyString, RunxSchema};
5use crate::{AgentContextEnvelope, JsonObject, JsonValue};
6
7#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
9pub enum ExecutionEvent {
10 SkillLoaded {
11 message: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 data: Option<JsonValue>,
14 },
15 InputsResolved {
16 message: String,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 data: Option<JsonValue>,
19 },
20 AuthResolved {
21 message: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 data: Option<JsonValue>,
24 },
25 ResolutionRequested {
26 message: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 data: Option<JsonValue>,
29 },
30 ResolutionResolved {
31 message: String,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 data: Option<JsonValue>,
34 },
35 Admitted {
36 message: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 data: Option<JsonValue>,
39 },
40 Executing {
41 message: String,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 data: Option<JsonValue>,
44 },
45 StepStarted {
46 message: String,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 data: Option<JsonValue>,
49 },
50 StepWaitingResolution {
51 message: String,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 data: Option<JsonValue>,
54 },
55 StepCompleted {
56 message: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 data: Option<JsonValue>,
59 },
60 Warning {
61 message: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 data: Option<JsonValue>,
64 },
65 Completed {
66 message: String,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 data: Option<JsonValue>,
69 },
70}
71
72#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
73#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
74#[runx_schema(spec_id = "https://runx.ai/spec/resolution-request.schema.json")]
75pub enum ResolutionRequest {
76 Input {
77 id: NonEmptyString,
78 questions: Vec<Question>,
79 },
80 Approval {
81 id: NonEmptyString,
82 gate: ApprovalGate,
83 },
84 AgentAct {
85 id: NonEmptyString,
86 invocation: Box<AgentActInvocation>,
87 },
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
91#[serde(deny_unknown_fields)]
92#[runx_schema(spec_id = "https://runx.ai/spec/question.schema.json")]
93pub struct Question {
94 pub id: NonEmptyString,
95 pub prompt: NonEmptyString,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub description: Option<String>,
98 pub required: bool,
99 #[serde(rename = "type")]
100 pub question_type: NonEmptyString,
101}
102
103#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
110#[serde(deny_unknown_fields)]
111#[runx_schema(spec_id = "https://runx.ai/spec/approval-gate.schema.json")]
112pub struct ApprovalGate {
113 pub id: NonEmptyString,
114 pub reason: NonEmptyString,
115 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
116 pub gate_type: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
120 pub summary: Option<JsonObject>,
121}
122
123#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
124#[serde(deny_unknown_fields)]
125#[runx_schema(spec_id = "https://runx.ai/spec/agent-act-invocation.schema.json")]
126pub struct AgentActInvocation {
127 pub id: NonEmptyString,
128 pub source_type: AgentActSourceType,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub agent: Option<NonEmptyString>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub task: Option<NonEmptyString>,
133 pub envelope: AgentContextEnvelope,
134}
135
136#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
137#[serde(rename_all = "kebab-case")]
138pub enum AgentActSourceType {
139 Agent,
140 #[serde(rename = "agent-task")]
141 AgentStep,
142}
143
144#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
145#[serde(deny_unknown_fields)]
146#[runx_schema(spec_id = "https://runx.ai/spec/resolution-response.schema.json")]
147pub struct ResolutionResponse {
148 pub actor: ResolutionResponseActor,
149 pub payload: JsonValue,
152}
153
154#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
155#[serde(rename_all = "snake_case")]
156pub enum ResolutionResponseActor {
157 Human,
158 Agent,
159}
160
161#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
162#[serde(deny_unknown_fields)]
163pub struct ApprovalDecision {
164 pub gate: ApprovalGate,
165 pub approved: bool,
166}
167
168#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
169#[serde(
170 tag = "status",
171 rename_all = "snake_case",
172 rename_all_fields = "camelCase",
173 deny_unknown_fields
174)]
175pub enum HostRunResult {
176 NeedsAgent {
177 skill_name: String,
178 run_id: String,
179 requests: Vec<ResolutionRequest>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 step_ids: Option<Vec<String>>,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 step_labels: Option<Vec<String>>,
184 events: Vec<ExecutionEvent>,
185 },
186 Completed {
187 skill_name: String,
188 receipt_id: String,
189 output: String,
190 events: Vec<ExecutionEvent>,
191 },
192 Failed {
193 skill_name: String,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 receipt_id: Option<String>,
196 error: String,
197 events: Vec<ExecutionEvent>,
198 },
199 Escalated {
200 skill_name: String,
201 receipt_id: String,
202 error: String,
203 events: Vec<ExecutionEvent>,
204 },
205 Denied {
206 skill_name: String,
207 reasons: Vec<String>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 receipt_id: Option<String>,
210 events: Vec<ExecutionEvent>,
211 },
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
215#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
216pub enum HostRunState {
217 NeedsAgent(HostNeedsAgentState),
218 Completed(HostTerminalState),
219 Failed(HostTerminalState),
220 Escalated(HostTerminalState),
221 Denied(HostTerminalState),
222}
223
224#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase", deny_unknown_fields)]
226pub struct HostNeedsAgentState {
227 pub skill_name: String,
228 pub run_id: String,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub requested_path: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub resolved_path: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub selected_runner: Option<String>,
235 pub requests: Vec<ResolutionRequest>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub step_ids: Option<Vec<String>>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub step_labels: Option<Vec<String>>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub lineage: Option<HostRunLineage>,
242}
243
244#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase", deny_unknown_fields)]
246pub struct HostTerminalState {
247 pub kind: HostRunKind,
248 pub skill_name: String,
249 pub run_id: String,
250 pub receipt_id: String,
251 pub verification: HostRunVerification,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 pub source_type: Option<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub started_at: Option<String>,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub completed_at: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub disposition: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub outcome_state: Option<String>,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub actors: Option<Vec<String>>,
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub artifact_types: Option<Vec<String>>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub runner_provider: Option<String>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub approval: Option<HostRunApproval>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub lineage: Option<HostRunLineage>,
272}
273
274#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(rename_all = "snake_case")]
276pub enum HostRunKind {
277 Harness,
278}
279
280#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
281#[serde(deny_unknown_fields)]
282pub struct HostRunVerification {
283 pub status: HostRunVerificationStatus,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub reason: Option<String>,
286}
287
288#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
289#[serde(rename_all = "snake_case")]
290pub enum HostRunVerificationStatus {
291 Verified,
292 Unverified,
293 Invalid,
294}
295
296#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase", deny_unknown_fields)]
298pub struct HostRunLineage {
299 pub kind: HostRunLineageKind,
300 pub source_run_id: String,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub source_receipt_id: Option<String>,
303}
304
305#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "snake_case")]
307pub enum HostRunLineageKind {
308 Rerun,
309}
310
311#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase", deny_unknown_fields)]
313pub struct HostRunApproval {
314 #[serde(skip_serializing_if = "Option::is_none")]
315 pub gate_id: Option<String>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub gate_type: Option<String>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub decision: Option<HostRunApprovalDecision>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub reason: Option<String>,
322}
323
324#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
325#[serde(rename_all = "snake_case")]
326pub enum HostRunApprovalDecision {
327 Approved,
328 Denied,
329}