use super::super::{
DEFAULT_MAX_TOKENS, assembler, child_result, model_call, result_deposit, step_commit,
stop_signal, terminal, tool_step, tools, transcript,
};
use crate::config::Event;
use crate::prompt::inbox::Epitaph;
use crate::prompt::resolve::WorkerConfig;
use crate::prompt::step::{RESPONSE_FILE, STAGING_FILE, StepMeta, next_step_seq, step_dir_rel};
use crate::prompt::workflow_actions;
use crate::prompt::{Deps, Error};
use brazen::Content;
use model_call::ModelCall;
use std::path::Path;
pub(super) enum StepOutcome {
ToolsRan,
Terminal(Epitaph),
}
pub(super) fn step(
workspace: &Path,
agent_id: &str,
worktree: &Path,
cfg: &WorkerConfig,
deps: &Deps<'_>,
) -> Result<StepOutcome, Error> {
if stop_signal::stopped(deps.stop) {
return Ok(StepOutcome::Terminal(Epitaph::Stopped));
}
let resolved = cfg.as_resolved();
if terminal::budget_exhausted(
workspace,
agent_id,
agent_id,
worktree,
&resolved.budgets,
deps,
)? {
return Ok(StepOutcome::Terminal(Epitaph::BudgetExhausted));
}
workflow_actions::run_step_hook(&cfg.workflow, Event::PreStep, worktree, agent_id, deps.git)?;
let goal = std::fs::read_to_string(worktree.join(step_commit::GOAL_FILE))?;
let system_with_goal = step_commit::prepend_goal(&goal, &resolved.soul);
let call = ModelCall {
adapter: deps.adapter,
sleeper: deps.sleeper,
binary: &resolved.binary,
provider_row: resolved.provider_row,
retry: resolved.retry,
stop: deps.stop,
expect_handshake: resolved.expect_handshake,
};
let step_seq = next_step_seq(workspace, agent_id)?;
let commit_sha = step_commit::read_branch_tip(worktree, deps)?;
let messages = assembler::assemble(worktree, resolved.manifest)?;
let tools = tools::compose(worktree, resolved.role, resolved.tools, &messages)?;
let request = model_call::build_request(
&resolved.model.model_id,
&system_with_goal,
messages,
tools,
DEFAULT_MAX_TOKENS,
);
let request_value =
serde_json::to_value(&request).expect("CanonicalRequest is always serializable");
let step_dir_rel_str = step_dir_rel(agent_id, step_seq);
step_commit::write_request(workspace, &step_dir_rel_str, &request_value)?;
let request_bytes =
serde_json::to_vec(&request).expect("CanonicalRequest is always serializable");
let started_at = deps.clock.now_iso8601();
let response_path = workspace.join(&step_dir_rel_str).join(RESPONSE_FILE);
let call_outcome = model_call::run(&call, &request_bytes, &response_path);
if stop_signal::stopped(deps.stop) {
return Ok(StepOutcome::Terminal(Epitaph::Stopped));
}
call_outcome?;
let ended_at = deps.clock.now_iso8601();
step_commit::write_meta(
workspace,
&step_dir_rel_str,
&StepMeta {
commit: commit_sha,
started_at,
ended_at,
},
)?;
let staging_path = workspace.join(&step_dir_rel_str).join(STAGING_FILE);
let assistant_content = transcript::commit_assistant(
worktree,
agent_id,
&resolved.model.model_id,
&staging_path,
deps.git,
)?;
workflow_actions::run_step_hook(&cfg.workflow, Event::PostStep, worktree, agent_id, deps.git)?;
if !assistant_content
.iter()
.any(|b| matches!(b, Content::ToolUse { .. }))
{
let response = result_deposit::terminal_text(&assistant_content);
result_deposit::deposit_terminal(
workspace,
agent_id,
worktree,
Epitaph::FinalResponse,
response.as_deref(),
deps,
)?;
return Ok(StepOutcome::Terminal(Epitaph::FinalResponse));
}
if tool_step::run_tool_calls(
workspace,
worktree,
agent_id,
resolved.role,
&step_dir_rel_str,
&assistant_content,
deps,
)? || stop_signal::stopped(deps.stop)
{
return Ok(StepOutcome::Terminal(Epitaph::Stopped));
}
workflow_actions::run_step_hook(
&cfg.workflow,
Event::OnToolReturn,
worktree,
agent_id,
deps.git,
)?;
child_result::run_flush(workspace, agent_id, worktree, &cfg.workflow, deps)?;
Ok(StepOutcome::ToolsRan)
}