use crate::agent_backend::{AgentBackend, build_agent_backend};
use crate::artifacts::{RunArtifacts, SessionDotfileBackups};
use crate::cli::one_shot_session::{
OneShotCoderGuard, finish_one_shot_after_prompt, finish_one_shot_auth_and_backups,
};
use crate::cli::run_emit::{RunStartupEmitOpts, emit_run_logs_line, emit_run_startup_banner};
use crate::cli::{SharedOpts, WorkflowCliOptions};
use crate::prompts::PromptStore;
use crate::run_timing::TimingPhase;
use crate::workflow_context::format_prompt_path;
use super::WriteArgs;
use super::prep::{WriteInitialPromptInput, build_write_workflow_initial_prompt, compose_write_b_prompt, write_preflight};
struct WriteRunPrep {
client: AgentBackend,
artifacts: RunArtifacts,
prompt_b: String,
session_dotfile_backups: SessionDotfileBackups,
}
fn new_write_client(
shared: &SharedOpts,
workflow: WorkflowCliOptions,
) -> Result<AgentBackend, String> {
build_agent_backend(
shared,
workflow,
shared.acp_stdout_markdown_enabled(),
"write",
)
}
fn write_workspace_dir_display(artifacts: &RunArtifacts) -> String {
format_prompt_path(&artifacts.run_dir, &artifacts.work_dir)
}
fn create_write_artifacts(
request_text: &str,
request_work_dir: &std::path::Path,
) -> Result<RunArtifacts, String> {
let artifacts = crate::artifacts::create_run_artifacts_from_text_opts(
request_text,
Some(request_work_dir),
crate::run_id::RunDirOptions { gc: false },
)
.map_err(|e| e.to_string())?;
crate::run_id::activate_run(artifacts.run_dir.clone());
Ok(artifacts)
}
fn prepare_write_prompt_store() -> Result<PromptStore, String> {
let store = PromptStore::default_store();
store
.ensure_defaults()
.map_err(|e: crate::prompts::PromptError| e.0)?;
Ok(store)
}
fn bind_write_initial_prompt(
client: &mut AgentBackend,
shared: &SharedOpts,
artifacts: &RunArtifacts,
request_text: &str,
) -> Result<(), String> {
let store = prepare_write_prompt_store()?;
let workspace_dir = write_workspace_dir_display(artifacts);
let initial = build_write_workflow_initial_prompt(WriteInitialPromptInput {
store: &store,
artifacts,
model: &shared.model.canonical(),
git: shared.git,
request_text,
workspace_dir: &workspace_dir,
})?;
client.bind_session_header_parts(
initial.body,
artifacts.log_path(initial.log_who),
&initial.stdout_label,
initial.log_who,
);
Ok(())
}
async fn prepare_write_run(
write_args: &mut WriteArgs,
shared: &SharedOpts,
workflow: WorkflowCliOptions,
) -> Result<WriteRunPrep, String> {
let (request_text, request_work_dir, outputs) = write_preflight(
write_args.request.as_ref(),
&write_args.out_path,
write_args.out_path_explicit,
)?;
let mut client = new_write_client(shared, workflow)?;
let artifacts = create_write_artifacts(&request_text, &request_work_dir)?;
let tex_display = format_prompt_path(&outputs.tex_path, &artifacts.work_dir);
let pdf_display = format_prompt_path(&outputs.pdf_path, &artifacts.work_dir);
write_args.out_path = tex_display.clone();
let banner_request = write_args
.request
.clone()
.unwrap_or_else(|| request_text.clone());
emit_run_startup_banner(
&artifacts,
RunStartupEmitOpts::from_shared(shared, true),
&banner_request,
)?;
crate::run_id::maybe_gc_after_run_created(&artifacts.work_dir, &artifacts.run_dir);
let session_dotfile_backups = finish_one_shot_auth_and_backups(&mut client, &artifacts)?;
bind_write_initial_prompt(&mut client, shared, &artifacts, &request_text)?;
let store = prepare_write_prompt_store()?;
let workspace_dir = write_workspace_dir_display(&artifacts);
let prompt_b = compose_write_b_prompt(&store, &tex_display, &pdf_display, &workspace_dir)?;
Ok(WriteRunPrep {
client,
artifacts,
prompt_b,
session_dotfile_backups,
})
}
async fn run_write_coder_prompt(
client: &mut AgentBackend,
artifacts: &RunArtifacts,
prompt: &str,
who: &str,
) -> Result<(), String> {
client
.run_coder_prompt(
prompt,
&artifacts.log_path(who),
who,
crate::acp::CoderPromptOptions {
llm_phase: Some(TimingPhase::Implement),
do_trace_split: None,
stdout_bracket_label: None,
..Default::default()
},
)
.await
.map_err(|e| e.to_string())
}
async fn run_write_coder_session(
client: &mut AgentBackend,
artifacts: &RunArtifacts,
prompt_b: &str,
) -> Result<(), String> {
let guard = OneShotCoderGuard::begin(client, artifacts, "write").await?;
let run_res = run_write_coder_prompt(client, artifacts, prompt_b, "write_b").await;
guard.finish(client, run_res).await
}
pub async fn run_write(
write_args: &mut WriteArgs,
shared: &SharedOpts,
workflow: WorkflowCliOptions,
) -> Result<(), String> {
let mut prep = prepare_write_run(write_args, shared, workflow).await?;
emit_run_logs_line(&prep.artifacts)?;
let acp_res =
run_write_coder_session(&mut prep.client, &prep.artifacts, &prep.prompt_b).await;
finish_one_shot_after_prompt(
acp_res,
&prep.artifacts.work_dir,
&prep.session_dotfile_backups,
&prep.artifacts.artifact_result_md(),
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kiss_cov_run_write_symbol() {
let _: Option<WriteRunPrep> = None;
let _ = (
run_write,
write_workspace_dir_display,
new_write_client,
create_write_artifacts,
run_write_coder_prompt,
run_write_coder_session,
prepare_write_run,
bind_write_initial_prompt,
prepare_write_prompt_store,
);
}
#[test]
fn prepare_write_prompts_and_paths_for_subdir_work_dir() {
let tmp = tempfile::tempdir().expect("tempdir");
let work = tmp.path().join("docs");
std::fs::create_dir(&work).expect("mkdir");
let artifacts = create_write_artifacts("topic", &work).expect("art");
assert!(!write_workspace_dir_display(&artifacts).is_empty());
let tex = format_prompt_path(&work.join("write.tex"), &work);
let pdf = format_prompt_path(&work.join("write.pdf"), &work);
assert_eq!((tex.as_str(), pdf.as_str()), ("./write.tex", "./write.pdf"));
let workspace_dir = write_workspace_dir_display(&artifacts);
let store = PromptStore::default_store();
let b = compose_write_b_prompt(&store, &tex, &pdf, &workspace_dir).expect("b");
assert!(b.contains("`./write.tex`") && b.contains("`./write.pdf`"));
assert!(!b.contains("`docs/write.tex`") && !b.contains("{{"));
}
#[test]
fn write_client_uses_styled_agent_io() {
let shared = SharedOpts {
background: false,
model: crate::model_id::parse_model_id(crate::config::DEFAULT_CLI_MODEL)
.expect("model"),
no_force: true,
no_tenacious: false,
gates: false,
quiet: false,
verbose: false,
max_acp_retries: crate::config::DEFAULT_MAX_ACP_RETRIES,
doc: false,
git: false,
creative: None,
no_kpop: false,
};
let io = new_write_client(&shared, WorkflowCliOptions { force: false })
.expect("backend")
.io;
assert!(!io.raw_output && io.show_thoughts_on_stdout && io.emit_stdout_markdown);
}
}