prodigy/cook/execution/mapreduce/
types.rs1use crate::cook::execution::interpolation::InterpolationContext;
7use crate::cook::execution::variable_capture::CaptureConfig;
8use crate::cook::execution::variables::{Variable, VariableContext};
9use crate::cook::orchestrator::ExecutionEnvironment;
10use serde::{Deserialize, Serialize};
11use serde_json::{json, Value};
12use std::collections::HashMap;
13use std::path::PathBuf;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct MapReduceConfig {
18 #[serde(default)]
20 pub input: String,
21 #[serde(default)]
23 pub json_path: String,
24 #[serde(default = "default_max_parallel")]
26 pub max_parallel: usize,
27 pub agent_timeout_secs: Option<u64>,
29 pub continue_on_failure: bool,
31 pub batch_size: Option<usize>,
33 pub enable_checkpoints: bool,
35 pub max_items: Option<usize>,
37 pub offset: Option<usize>,
39}
40
41fn default_max_parallel() -> usize {
42 10
43}
44
45impl Default for MapReduceConfig {
46 fn default() -> Self {
47 Self {
48 input: String::new(),
49 json_path: String::new(),
50 max_parallel: default_max_parallel(),
51 agent_timeout_secs: Some(300),
52 continue_on_failure: false,
53 batch_size: None,
54 enable_checkpoints: true,
55 max_items: None,
56 offset: None,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct SetupPhase {
64 pub commands: Vec<crate::cook::workflow::WorkflowStep>,
66 pub timeout: Option<u64>,
69 #[serde(default)]
71 pub capture_outputs: HashMap<String, CaptureConfig>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct MapPhase {
77 #[serde(flatten)]
79 pub config: MapReduceConfig,
80 pub json_path: Option<String>,
82 pub agent_template: Vec<crate::cook::workflow::WorkflowStep>,
84 pub filter: Option<String>,
86 pub sort_by: Option<String>,
88 pub max_items: Option<usize>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub distinct: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub timeout_config: Option<crate::cook::execution::mapreduce::timeout::TimeoutConfig>,
96 #[serde(skip)]
98 pub workflow_env: std::collections::HashMap<String, String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ReducePhase {
104 pub commands: Vec<crate::cook::workflow::WorkflowStep>,
106 pub timeout_secs: Option<u64>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, Default)]
112pub struct ResumeOptions {
113 pub reprocess_failed: bool,
115 pub max_parallel: Option<usize>,
117 pub skip_validation: bool,
119 pub agent_timeout_secs: Option<u64>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ResumeResult {
126 pub job_id: String,
128 pub resumed_from_version: u32,
130 pub total_items: usize,
132 pub already_completed: usize,
134 pub remaining_items: usize,
136 pub final_results: Vec<crate::cook::execution::mapreduce::AgentResult>,
138}
139
140#[derive(Clone)]
142pub struct AgentContext {
143 pub item_id: String,
145 pub worktree_path: PathBuf,
147 pub worktree_name: String,
149 pub variables: HashMap<String, String>,
151 pub shell_output: Option<String>,
153 pub environment: ExecutionEnvironment,
155 pub retry_count: u32,
157 pub captured_outputs: HashMap<String, String>,
159 pub iteration_vars: HashMap<String, String>,
161 pub variable_store: crate::cook::workflow::variables::VariableStore,
163}
164
165impl AgentContext {
166 pub fn new(
168 item_id: String,
169 worktree_path: PathBuf,
170 worktree_name: String,
171 environment: ExecutionEnvironment,
172 ) -> Self {
173 Self {
174 item_id,
175 worktree_path,
176 worktree_name,
177 variables: HashMap::new(),
178 shell_output: None,
179 environment,
180 retry_count: 0,
181 captured_outputs: HashMap::new(),
182 iteration_vars: HashMap::new(),
183 variable_store: crate::cook::workflow::variables::VariableStore::new(),
184 }
185 }
186
187 pub fn update_with_output(&mut self, output: Option<String>) {
189 if let Some(out) = output {
190 self.variables
191 .insert("shell.output".to_string(), out.clone());
192 self.variables
193 .insert("shell.last_output".to_string(), out.clone());
194 self.shell_output = Some(out);
195 }
196 }
197
198 pub fn to_interpolation_context(&self) -> InterpolationContext {
200 let mut context = InterpolationContext::new();
201
202 for (key, value) in &self.variables {
203 context.set(key.clone(), Value::String(value.as_str().into()));
204 }
205
206 if let Some(ref output) = self.shell_output {
207 context.set(
208 "shell",
209 json!({
210 "output": output,
211 "last_output": output
212 }),
213 );
214 }
215
216 for (key, value) in &self.captured_outputs {
217 context.set(key.clone(), Value::String(value.as_str().into()));
218 }
219
220 for (key, value) in &self.iteration_vars {
221 context.set(key.clone(), Value::String(value.as_str().into()));
222 }
223
224 context
225 }
226
227 pub async fn to_variable_context(&self) -> VariableContext {
229 let mut context = VariableContext::new();
230
231 for (key, value) in &self.variables {
232 if key.starts_with("map.") {
233 if let Ok(num) = value.parse::<f64>() {
234 context.set_phase(
235 key.clone(),
236 Variable::Static(Value::Number(
237 serde_json::Number::from_f64(num).unwrap_or(0.into()),
238 )),
239 );
240 } else {
241 context.set_phase(key.clone(), Variable::Static(Value::String(value.clone())));
242 }
243 } else {
244 context.set_phase(key.clone(), Variable::Static(Value::String(value.clone())));
245 }
246 }
247
248 let store_vars = self.variable_store.get_all().await;
249 for (key, captured_value) in store_vars {
250 let value = captured_value.to_json();
251
252 if key.starts_with("map.") {
253 context.set_phase(key.clone(), Variable::Static(value));
254 } else {
255 context.set_local(key.clone(), Variable::Static(value));
256 }
257 }
258
259 if let Some(ref output) = self.shell_output {
260 context.set_phase(
261 "shell",
262 Variable::Static(json!({
263 "output": output,
264 "last_output": output
265 })),
266 );
267 }
268
269 for (key, value) in &self.captured_outputs {
270 context.set_local(key.clone(), Variable::Static(Value::String(value.clone())));
271 }
272
273 for (key, value) in &self.iteration_vars {
274 context.set_local(key.clone(), Variable::Static(Value::String(value.clone())));
275 }
276
277 context.set_local(
278 "workflow",
279 Variable::Static(json!({
280 "id": self.item_id.clone(),
281 "worktree": Value::String(self.worktree_name.clone()),
282 "path": self.worktree_path.to_string_lossy()
283 })),
284 );
285
286 context
287 }
288}
289
290pub trait Configurable {
292 type Config;
293
294 fn configure(config: Self::Config) -> Self;
296
297 fn configuration(&self) -> &Self::Config;
299}
300
301pub trait Resettable {
303 fn reset(&mut self);
305
306 fn needs_reset(&self) -> bool;
308}