use codex_core::context::ContextualUserFragment;use codex_core::context::InternalContextSource;use codex_core::context::InternalModelContextFragment;use codex_protocol::models::ResponseItem;use codex_protocol::protocol::ThreadGoal;use codex_utils_template::Template;use std::sync::LazyLock;static CONTINUATION_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| { parse_embedded_template( include_str!("../templates/goals/continuation.md"), "goals/continuation.md", )});static BUDGET_LIMIT_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| { parse_embedded_template( include_str!("../templates/goals/budget_limit.md"), "goals/budget_limit.md", )});static OBJECTIVE_UPDATED_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| { parse_embedded_template( include_str!("../templates/goals/objective_updated.md"), "goals/objective_updated.md", )});fn parse_embedded_template(source: &'static str, template_name: &str) -> Template { match Template::parse(source) { Ok(template) => template, Err(err) => panic!("embedded template {template_name} is invalid: {err}"), }}pub(crate) fn budget_limit_steering_item(goal: &ThreadGoal) -> ResponseItem { goal_context_input_item(budget_limit_prompt(goal))}pub(crate) fn objective_updated_steering_item(goal: &ThreadGoal) -> ResponseItem { goal_context_input_item(objective_updated_prompt(goal))}pub(crate) fn continuation_steering_item(goal: &ThreadGoal) -> ResponseItem { goal_context_input_item(continuation_prompt(goal))}fn goal_context_input_item(prompt: String) -> ResponseItem { ContextualUserFragment::into(InternalModelContextFragment::new( InternalContextSource::from_static("goal"), prompt, ))}fn continuation_prompt(goal: &ThreadGoal) -> String { let objective = escape_xml_text(&goal.objective); let tokens_used = goal.tokens_used.to_string(); let token_budget = goal .token_budget .map(|budget| budget.to_string()) .unwrap_or_else(|| "none".to_string()); let remaining_tokens = goal .token_budget .map(|budget| (budget - goal.tokens_used).max(0).to_string()) .unwrap_or_else(|| "unbounded".to_string()); CONTINUATION_PROMPT_TEMPLATE .render([ ("objective", objective.as_str()), ("tokens_used", tokens_used.as_str()), ("token_budget", token_budget.as_str()), ("remaining_tokens", remaining_tokens.as_str()), ]) .unwrap_or_else(|err| { panic!("embedded goals/continuation.md template failed to render: {err}") })}fn budget_limit_prompt(goal: &ThreadGoal) -> String { let objective = escape_xml_text(&goal.objective); let time_used_seconds = goal.time_used_seconds.to_string(); let tokens_used = goal.tokens_used.to_string(); let token_budget = goal .token_budget .map(|budget| budget.to_string()) .unwrap_or_else(|| "none".to_string()); BUDGET_LIMIT_PROMPT_TEMPLATE .render([ ("objective", objective.as_str()), ("time_used_seconds", time_used_seconds.as_str()), ("tokens_used", tokens_used.as_str()), ("token_budget", token_budget.as_str()), ]) .unwrap_or_else(|err| { panic!("embedded goals/budget_limit.md template failed to render: {err}") })}fn objective_updated_prompt(goal: &ThreadGoal) -> String { let objective = escape_xml_text(&goal.objective); let tokens_used = goal.tokens_used.to_string(); let (token_budget, remaining_tokens) = match goal.token_budget { Some(token_budget) => ( token_budget.to_string(), (token_budget - goal.tokens_used).max(0).to_string(), ), None => ("none".to_string(), "unknown".to_string()), }; OBJECTIVE_UPDATED_PROMPT_TEMPLATE .render([ ("objective", objective.as_str()), ("tokens_used", tokens_used.as_str()), ("token_budget", token_budget.as_str()), ("remaining_tokens", remaining_tokens.as_str()), ]) .unwrap_or_else(|err| { panic!("embedded goals/objective_updated.md template failed to render: {err}") })}fn escape_xml_text(input: &str) -> String { input .replace('&', "&") .replace('<', "<") .replace('>', ">")}