1use serde::{Deserialize, Serialize};
4use tokio::sync::oneshot;
5
6#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct AgentOpts {
9 #[serde(default)]
11 pub prompt: String,
12 #[serde(default)]
14 pub label: Option<String>,
15 #[serde(default)]
17 pub model: Option<String>,
18 #[serde(default)]
20 pub capability_mode: Option<String>,
21 #[serde(default)]
23 pub output_schema: Option<serde_json::Value>,
24 #[serde(default)]
26 pub phase: Option<String>,
27 #[serde(default)]
29 pub agent_type: Option<String>,
30 #[serde(default)]
32 pub fork_context: bool,
33 #[serde(default)]
35 pub resume_from: Option<String>,
36 #[serde(default)]
38 pub max_output_tokens: Option<u64>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AgentResult {
44 pub agent_id: String,
46 pub success: bool,
48 pub output: serde_json::Value,
50 pub cancelled: bool,
52 pub tokens_used: u64,
54 pub duration_ms: u64,
56}
57
58#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
60pub struct BudgetState {
61 pub total: Option<u64>,
63 pub spent: u64,
65 pub reserved: u64,
67 pub remaining: Option<u64>,
69}
70
71#[derive(Debug, Clone, thiserror::Error)]
73pub enum HostError {
74 #[error("workflow agent-call quota exceeded: requested {requested}, maximum {maximum}")]
76 AgentCallQuotaExceeded {
77 requested: u64,
79 maximum: u64,
81 },
82 #[error("workflow token/agent budget exceeded")]
84 BudgetExceeded,
85 #[error("workflow cancelled")]
87 Cancelled,
88 #[error("unsupported in this context: {0}")]
90 Unsupported(String),
91 #[error("host failure: {0}")]
93 Failed(String),
94}
95
96#[derive(Debug)]
98pub enum WorkflowHostRequest {
99 ReserveAgentCalls {
101 count: u64,
103 reply: oneshot::Sender<Result<(), HostError>>,
105 },
106 ReleaseAgentCalls {
108 count: u64,
110 reply: oneshot::Sender<Result<(), HostError>>,
112 },
113 SpawnAgent {
115 opts: AgentOpts,
117 reply: oneshot::Sender<Result<AgentResult, HostError>>,
119 },
120 Phase {
122 title: String,
124 replayed: bool,
126 },
127 Log {
129 message: String,
131 replayed: bool,
133 },
134 Telemetry {
136 name: String,
138 fields: serde_json::Value,
140 replayed: bool,
142 },
143 BudgetQuery {
145 reply: oneshot::Sender<Result<BudgetState, HostError>>,
147 },
148 RenderTemplate {
150 name: String,
152 vars: serde_json::Value,
154 reply: oneshot::Sender<Result<String, HostError>>,
156 },
157 WriteScratchFile {
159 name: String,
161 content: String,
163 reply: oneshot::Sender<Result<String, HostError>>,
165 },
166 ReadScratchFile {
168 name: String,
170 reply: oneshot::Sender<Result<String, HostError>>,
172 },
173 GitDiffSince {
175 commit: String,
177 reply: oneshot::Sender<Result<String, HostError>>,
179 },
180}
181
182impl WorkflowHostRequest {
183 #[must_use]
185 pub const fn kind(&self) -> &'static str {
186 match self {
187 Self::ReserveAgentCalls { .. } => "reserve_agent_calls",
188 Self::ReleaseAgentCalls { .. } => "release_agent_calls",
189 Self::SpawnAgent { .. } => "spawn_agent",
190 Self::Phase { .. } => "phase",
191 Self::Log { .. } => "log",
192 Self::Telemetry { .. } => "telemetry",
193 Self::BudgetQuery { .. } => "budget",
194 Self::RenderTemplate { .. } => "render_template",
195 Self::WriteScratchFile { .. } => "write_scratch_file",
196 Self::ReadScratchFile { .. } => "read_scratch_file",
197 Self::GitDiffSince { .. } => "git_diff_since",
198 }
199 }
200}