Files
codex/codex-rs/context-fragments/src/additional_context.rs
T
jifandGitHub 3389fa554e skills: resolve per-turn catalogs from turn input context (#26106)
## Why

The skills extension needs the resolved turn environments to build a
real per-turn `SkillListQuery`. The previous `TurnLifecycleContributor`
hook only had a turn id, so it could only seed a placeholder query and
never carry the executor authorities that executor-scoped skill routing
will need.

Moving catalog resolution onto `TurnInputContributor` puts the skills
extension on the same turn-preparation path that already has the
environment ids and working directories for the submitted turn, while
keeping the actual prompt injection work for follow-up changes.

## What changed

- switch `ext/skills` from `TurnLifecycleContributor` to
`TurnInputContributor`
- build `executor_authorities` from `TurnInputContext.environments` and
pass them through `SkillListQuery`
- keep storing the resolved catalog in `SkillsTurnState`, but drop the
placeholder query helper that no longer matches the real data flow
- update the extension TODOs to reflect that per-turn catalog resolution
now happens in the turn-input contributor, and that prompt/context
injection still needs to move later

## Testing

- Not run locally.
2026-06-03 13:32:55 +02:00

94 lines
2.5 KiB
Rust

use codex_utils_string::truncate_middle_with_token_budget;
use crate::ContextualUserFragment;
const MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS: usize = 1_000;
const ADDITIONAL_CONTEXT_END_MARKER_SUFFIX: &str = ">";
const ADDITIONAL_CONTEXT_START_MARKER_PREFIX: &str = "<external_";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdditionalContextUserFragment {
key: String,
value: String,
}
impl AdditionalContextUserFragment {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
impl ContextualUserFragment for AdditionalContextUserFragment {
fn role(&self) -> &'static str {
"user"
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}
fn type_markers() -> (&'static str, &'static str) {
(
ADDITIONAL_CONTEXT_START_MARKER_PREFIX,
ADDITIONAL_CONTEXT_END_MARKER_SUFFIX,
)
}
fn matches_text(text: &str) -> bool {
let trimmed = text.trim();
let Some(rest) = trimmed.strip_prefix(ADDITIONAL_CONTEXT_START_MARKER_PREFIX) else {
return false;
};
let Some((key, value_and_close)) = rest.split_once(ADDITIONAL_CONTEXT_END_MARKER_SUFFIX)
else {
return false;
};
value_and_close.ends_with(&format!("</external_{key}>"))
}
fn body(&self) -> String {
additional_context_body(&self.key, &self.value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdditionalContextDeveloperFragment {
key: String,
value: String,
}
impl AdditionalContextDeveloperFragment {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
impl ContextualUserFragment for AdditionalContextDeveloperFragment {
fn role(&self) -> &'static str {
"developer"
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}
fn type_markers() -> (&'static str, &'static str) {
("", "")
}
fn body(&self) -> String {
additional_context_developer_body(&self.key, &self.value)
}
}
fn additional_context_body(key: &str, value: &str) -> String {
let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0;
format!("{key}>{value}</external_{key}")
}
fn additional_context_developer_body(key: &str, value: &str) -> String {
let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0;
format!("<{key}>{value}</{key}>")
}