mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
c875bc8a33
## Why Goal steering prompts have grown into long inline Rust strings, which makes the authored prompt text hard to review and easy to damage while changing the surrounding plumbing. Moving those prompts into embedded Markdown templates keeps the policy text in the shape reviewers actually read, while preserving the existing runtime substitution and objective escaping behavior. ## What changed - Added `ext/goal/templates/goals/continuation.md`, `budget_limit.md`, and `objective_updated.md` for the three goal steering prompts. - Updated `ext/goal/src/steering.rs` to parse those embedded templates once with `codex-utils-template` and render the existing goal values into them. - Kept user objectives XML-escaped before rendering and converted budget counters into template variables. - Added the template directory to `ext/goal/BUILD.bazel` `compile_data` so Bazel has the same embedded prompt inputs as Cargo. ## Testing - Not run locally.
130 lines
4.4 KiB
Rust
130 lines
4.4 KiB
Rust
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('>', ">")
|
|
}
|