use super::*;
impl AgentSession {
pub(super) fn prepare_initial_run<'run, 'sink>(
&self,
run: &AgentRunRequest<'run, 'sink>,
tool_configuration: ToolRequestConfiguration,
preflight_projection: Option<PreflightRequestProjection>,
) -> anyhow::Result<PreparedInitialRun<'run>> {
let effective_prompt = run
.effective_prompt
.map(str::to_string)
.or_else(|| self.effective_prompt_for_run(run));
let provider_prompt = effective_prompt
.as_deref()
.unwrap_or(run.prompt)
.to_string();
let InitialConversation {
conversation,
session_read_diagnostics,
replay_warnings,
} = self.build_initial_conversation(&provider_prompt, run.session, run.tools)?;
let base_conversation = Arc::<[ProviderConversationItem]>::from(conversation);
let prompt_cache_key = prompt_cache_key_for_run(run, &self.provider_id, &self.model);
let conversation_id = run
.session
.map(|session| crate::providers::conversation_id_for_session(session.id()));
let initial_request = self.request_from_shared_conversation(
Arc::clone(&base_conversation),
&[],
None,
None,
&tool_configuration,
);
let projection = preflight_projection.and_then(|preflight| {
preflight.matching_projection(&self.provider_id, &initial_request, run.session)
});
let request_projection_cache = match projection {
Some(projection) => RequestTokenProjectionCache::new_with_base_projection(
&self.provider_id,
&self.model,
projection,
),
None => {
RequestTokenProjectionCache::new_for_request(&self.provider_id, &initial_request)
}
};
if self.context_budget.enabled {
self.ensure_request_context_fits(
request_projection_cache.initial_projection(),
ContextBudgetPhase::Initial,
)?;
}
Ok(PreparedInitialRun {
provider_prompt,
session_read_diagnostics,
replay_warnings,
base_conversation,
prompt_cache_key,
conversation_id,
request_projection_cache,
session_persistence: SessionPersistence::new(run.session, run.cwd)
.with_task_scope(run.tools),
tool_request_configuration: tool_configuration,
})
}
pub(super) fn effective_prompt_for_run(&self, run: &AgentRunRequest<'_, '_>) -> Option<String> {
if run.prompt_origin == crate::output::UserPromptOrigin::AutomaticCompaction {
return None;
}
Self::expand_effective_prompt(
run.prompt,
run.tools,
run.invocation_mode,
run.cwd,
&run.cancellation,
)
}
pub(crate) fn effective_prompt_for_projection(
prompt: &str,
tools: Option<&ToolRuntime>,
invocation_mode: InvocationMode,
) -> String {
tools
.and_then(|tools| {
Self::expand_effective_prompt(
prompt,
Some(tools),
invocation_mode,
tools.cwd_canonical(),
&AgentCancellation::default(),
)
})
.unwrap_or_else(|| prompt.to_string())
}
pub(super) fn expand_effective_prompt(
prompt: &str,
tools: Option<&ToolRuntime>,
invocation_mode: InvocationMode,
cwd: &Path,
cancellation: &AgentCancellation,
) -> Option<String> {
if !should_expand_prompt_context(invocation_mode) {
return None;
}
prompt_injections::expand_user_prompt(prompt, cwd, cancellation, |definition| match tools {
Some(tools) => tools.dispatch_with_context(
"bash",
json!({
"command": definition.command,
"intent": definition.description,
"timeout": 30,
}),
crate::output::ToolDispatchContext::new_with_hook_context_and_cancellation(
None,
None,
HookContextMetadata::new(invocation_mode),
cancellation.clone(),
),
),
None => ToolResult {
tool_name: "context_injection".to_string(),
success: false,
content: "Context command unavailable: tools are disabled".to_string(),
metadata: serde_json::Value::Null,
display: Default::default(),
},
})
}
pub(super) fn record_initial_user_input(
&self,
prepared: &mut PreparedInitialRun<'_>,
run: &mut AgentRunRequest<'_, '_>,
) -> anyhow::Result<()> {
let mut payload =
json!({"text": prepared.provider_prompt, "origin": run.prompt_origin.label()});
if run.prompt_origin == crate::output::UserPromptOrigin::User
&& run.invocation_mode != InvocationMode::Subagent
&& run.tools.is_some_and(|tools| tools.task_scope.is_some())
{
payload["task_clarification"] = true.into();
}
if self.reasoning_updates_enabled {
payload["reasoning_selection"] = self.reasoning_selection_payload();
}
prepared
.session_persistence
.record_required(SessionEventKind::UserInput, payload)
.map_err(RequiredUserInputPersistenceError::new)?;
if run.prompt_origin == crate::output::UserPromptOrigin::User
&& let Some(sink) = run.output_sink.as_deref_mut()
{
sink.user_input_persisted()?;
}
Ok(())
}
pub(super) fn update_tool_checkpoint_context(
&self,
run: &AgentRunRequest<'_, '_>,
) -> anyhow::Result<()> {
let Some(tools) = run.tools else {
return Ok(());
};
let Some(session) = run.session else {
tools.set_checkpoint_context(None)?;
return Ok(());
};
let Some(paths) = tools.checkpoint_paths() else {
tools.set_checkpoint_context(None)?;
return Ok(());
};
let user_turn = crate::sessions::session_user_input_count(session)? as u64;
tools.set_checkpoint_context(Some(crate::checkpoints::SnapshotContext {
store: crate::checkpoints::CheckpointStore::from_paths(&paths),
paths,
session_id: session.id().to_string(),
user_turn,
}))
}
pub(super) fn start_title_generation_guard(
&self,
run: &mut AgentRunRequest<'_, '_>,
cancellation: AgentCancellation,
) -> TitleGenerationGuard {
let mut title_handle = None;
if let (Some(session), Some(mut job)) = (run.session, run.session_title_job.take())
&& let Ok(metadata) =
crate::session_titles::should_start_title_generation_metadata(session)
&& metadata.latest_title.is_none()
&& metadata.user_input_count == 1
{
if let Some(first_prompt) = metadata.first_user_input_text {
job.first_prompt = first_prompt;
}
if let Some(reporter) =
filtered_herdr_reporter(run.invocation_mode, run.herdr_reporter.clone())
{
let notifier = job.notifier.take();
job.notifier = Some(Arc::new(move |update| {
reporter
.rename_workspace_from_generated_title(&update.session_id, &update.title);
if let Some(notifier) = ¬ifier {
notifier(update);
}
}));
}
title_handle = Some(crate::session_titles::spawn_background(job));
}
TitleGenerationGuard::new(title_handle, run.invocation_mode, cancellation)
}
pub(super) fn emit_initial_user_prompt_and_replay_diagnostics(
&self,
run: &mut AgentRunRequest<'_, '_>,
provider_prompt: String,
diagnostics: &[SessionReadDiagnostic],
warnings: &[String],
) -> anyhow::Result<()> {
if let Some(sink) = run.output_sink.as_deref_mut() {
let event = match run.prompt_origin {
crate::output::UserPromptOrigin::AutomaticCompaction => {
OutputEvent::AutomaticUserPrompt {
text: provider_prompt,
}
}
crate::output::UserPromptOrigin::Steering => OutputEvent::SteeringInjected {
text: provider_prompt,
},
crate::output::UserPromptOrigin::User => OutputEvent::UserPrompt {
text: provider_prompt,
},
};
sink.output_event(event)?;
}
emit_replay_diagnostics(&mut run.output_sink, diagnostics, warnings)
}
pub(super) fn build_hook_context(&self, run: &AgentRunRequest<'_, '_>) -> HookContextMetadata {
HookContextMetadata {
session_id: run.session.map(|session| session.id().to_string()),
session_path: run.session.map(|session| session.path().to_path_buf()),
provider_id: Some(self.provider_id.clone()),
model_id: Some(self.model.clone()),
agent_id: run.agent_id.clone(),
invocation_mode: run.invocation_mode,
turn_id: None,
message_id: None,
subagent: matches!(run.invocation_mode, InvocationMode::Subagent),
}
}
pub(super) fn start_print_turn_state<'run>(
&self,
prepared: PreparedInitialRun<'run>,
run: &AgentRunRequest<'run, '_>,
title_guard: TitleGenerationGuard,
request_sequence: Arc<AtomicU64>,
) -> PrintTurnState<'run> {
PrintTurnState {
base_conversation: prepared.base_conversation,
tool_request_configuration: prepared.tool_request_configuration,
prompt_cache_key: prepared.prompt_cache_key,
conversation_id: prepared.conversation_id,
codex_turn_context: crate::providers::CodexTurnContext::default(),
session_persistence: prepared.session_persistence,
subdir_instruction_state: SubdirInstructionState::new(
run.initial_instructions,
run.session,
),
assistant_chunk_batch: AssistantChunkBatch::new(run.session, run.cwd),
turn_state: AgentTurnState::default(),
output: AgentRunOutput::default(),
request_projection_cache: prepared.request_projection_cache,
request_sequence,
hook_context: self.build_hook_context(run),
herdr_reporter: filtered_herdr_reporter(
run.invocation_mode,
run.herdr_reporter.clone(),
),
title_guard,
}
}
}