ralph_workflow/phases/commit/runner/
chain.rs1pub fn generate_commit_message(
29 diff: &str,
30 registry: &AgentRegistry,
31 runtime: &mut PipelineRuntime<'_>,
32 commit_agent: &str,
33 template_context: &TemplateContext,
34 workspace: &dyn Workspace,
35) -> anyhow::Result<CommitMessageResult> {
36 let model_budget = model_budget_bytes_for_agent_name(commit_agent);
39 let (model_safe_diff, truncated) = truncate_diff_to_model_budget(diff, model_budget);
40 if truncated {
41 runtime.logger.warn(&format!(
42 "Diff size ({} KB) exceeds agent limit ({} KB). Truncated to {} KB.",
43 diff.len() / 1024,
44 model_budget / 1024,
45 model_safe_diff.len() / 1024
46 ));
47 }
48
49 let (prompt, substitution_log) =
50 build_commit_prompt(template_context, &model_safe_diff, workspace);
51 if !substitution_log.is_complete() {
52 return Err(anyhow::anyhow!(
53 "Commit prompt has unresolved placeholders: {:?}",
54 substitution_log.unsubstituted
55 ));
56 }
57
58 let agent_config = registry
59 .resolve_config(commit_agent)
60 .ok_or_else(|| anyhow::anyhow!("Agent not found: {commit_agent}"))?;
61 let cmd_str = agent_config.build_cmd_with_model(true, true, true, None);
62
63 let log_prefix = ".agent/logs/commit_generation/commit_generation";
64 let model_index = 0usize;
65 let attempt = 1u32;
66 let agent_for_log = commit_agent.to_lowercase();
67 let logfile = crate::pipeline::logfile::build_logfile_path_with_attempt(
68 log_prefix,
69 &agent_for_log,
70 model_index,
71 attempt,
72 );
73 let prompt_cmd = PromptCommand {
74 label: commit_agent,
75 display_name: commit_agent,
76 cmd_str: &cmd_str,
77 prompt: &prompt,
78 log_prefix,
79 model_index: Some(model_index),
80 attempt: Some(attempt),
81 logfile: &logfile,
82 parser_type: agent_config.json_parser,
83 env_vars: &agent_config.env_vars,
84 };
85
86 let result = run_with_prompt(&prompt_cmd, runtime)?;
87 let had_error = result.exit_code != 0;
88 let auth_failure = had_error && stderr_contains_auth_error(&result.stderr);
89 if auth_failure {
90 anyhow::bail!("Authentication error detected");
91 }
92
93 let extraction = extract_commit_message_from_file_with_workspace(workspace);
94 let result = match extraction {
95 CommitExtractionOutcome::Valid {
96 extracted: result,
97 files: _,
98 ..
99 } => result,
100 CommitExtractionOutcome::InvalidXml(detail)
101 | CommitExtractionOutcome::MissingFile(detail) => anyhow::bail!(detail),
102 CommitExtractionOutcome::Skipped(reason) => {
103 archive_xml_file_with_workspace(workspace, Path::new(xml_paths::COMMIT_MESSAGE_XML));
104 return Ok(CommitMessageResult {
105 outcome: CommitMessageOutcome::Skipped { reason },
106 });
107 }
108 };
109
110 archive_xml_file_with_workspace(workspace, Path::new(xml_paths::COMMIT_MESSAGE_XML));
111
112 Ok(CommitMessageResult {
113 outcome: CommitMessageOutcome::Message(result.into_message()),
114 })
115}
116
117pub fn generate_commit_message_with_chain(
138 diff: &str,
139 registry: &AgentRegistry,
140 runtime: &mut PipelineRuntime<'_>,
141 agents: &[String],
142 template_context: &TemplateContext,
143 workspace: &dyn Workspace,
144) -> anyhow::Result<CommitMessageResult> {
145 if agents.is_empty() {
146 anyhow::bail!("No agents provided in commit chain");
147 }
148
149 let model_budget = effective_model_budget_bytes(agents);
151 let (model_safe_diff, truncated) = truncate_diff_to_model_budget(diff, model_budget);
152 if truncated {
153 runtime.logger.warn(&format!(
154 "Diff size ({} KB) exceeds chain limit ({} KB). Truncated to {} KB.",
155 diff.len() / 1024,
156 model_budget / 1024,
157 model_safe_diff.len() / 1024
158 ));
159 }
160
161 let mut last_error: Option<anyhow::Error> = None;
162
163 for (agent_index, commit_agent) in agents.iter().enumerate() {
164 let (prompt, substitution_log) =
165 build_commit_prompt(template_context, &model_safe_diff, workspace);
166 if !substitution_log.is_complete() {
167 return Err(anyhow::anyhow!(
168 "Commit prompt has unresolved placeholders: {:?}",
169 substitution_log.unsubstituted
170 ));
171 }
172
173 let Some(agent_config) = registry.resolve_config(commit_agent) else {
174 last_error = Some(anyhow::anyhow!("Agent not found: {commit_agent}"));
175 continue;
176 };
177 let cmd_str = agent_config.build_cmd_with_model(true, true, true, None);
178
179 let log_prefix = ".agent/logs/commit_generation/commit_generation";
180 let model_index = agent_index;
181 let attempt = 1u32;
182 let agent_for_log = commit_agent.to_lowercase();
183 let logfile = crate::pipeline::logfile::build_logfile_path_with_attempt(
184 log_prefix,
185 &agent_for_log,
186 model_index,
187 attempt,
188 );
189 let prompt_cmd = PromptCommand {
190 label: commit_agent,
191 display_name: commit_agent,
192 cmd_str: &cmd_str,
193 prompt: &prompt,
194 log_prefix,
195 model_index: Some(model_index),
196 attempt: Some(attempt),
197 logfile: &logfile,
198 parser_type: agent_config.json_parser,
199 env_vars: &agent_config.env_vars,
200 };
201
202 let result = match run_with_prompt(&prompt_cmd, runtime) {
203 Ok(r) => r,
204 Err(e) => {
205 last_error = Some(e.into());
206 continue;
207 }
208 };
209
210 let had_error = result.exit_code != 0;
211 let auth_failure = had_error && stderr_contains_auth_error(&result.stderr);
212
213 if auth_failure {
214 last_error = Some(anyhow::anyhow!("Authentication error detected"));
215 continue;
216 }
217
218 if had_error {
219 last_error = Some(anyhow::anyhow!(
220 "Agent {} failed with exit code {}",
221 commit_agent,
222 result.exit_code
223 ));
224 continue;
225 }
226
227 let extraction = extract_commit_message_from_file_with_workspace(workspace);
228 match extraction {
229 CommitExtractionOutcome::Valid {
230 extracted,
231 files: _,
232 ..
233 } => {
234 archive_xml_file_with_workspace(
235 workspace,
236 Path::new(xml_paths::COMMIT_MESSAGE_XML),
237 );
238 return Ok(CommitMessageResult {
239 outcome: CommitMessageOutcome::Message(extracted.into_message()),
240 });
241 }
242 CommitExtractionOutcome::Skipped(reason) => {
243 archive_xml_file_with_workspace(
244 workspace,
245 Path::new(xml_paths::COMMIT_MESSAGE_XML),
246 );
247 return Ok(CommitMessageResult {
248 outcome: CommitMessageOutcome::Skipped { reason },
249 });
250 }
251 CommitExtractionOutcome::InvalidXml(detail)
252 | CommitExtractionOutcome::MissingFile(detail) => {
253 last_error = Some(anyhow::anyhow!(detail));
254 }
255 }
256 }
257
258 Err(last_error.unwrap_or_else(|| anyhow::anyhow!("All agents in commit chain failed")))
260}