mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
## Why Rendering host and executor skill catalogs independently allows their combined metadata to exceed the skills context budget. ## What changed - Allocate one budget across both catalogs when both contain model-visible skills. - Prefer retaining executor skills when budget pressure requires omissions, while preserving an executor omission marker and reporting when all host skills were omitted. - Evaluate absolute and aliased host paths under the shared budget and select the rendering that best preserves executor entries, total entries, and descriptions, in that order. ## Testing Added renderer and production-turn coverage for shared-budget description shortening, host-first omission, executor omission markers, and alias selection. GitOrigin-RevId: 30041736a76aa173cb641aa98a876a306cb437d2
210 lines
8.4 KiB
Rust
210 lines
8.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use codex_core_skills::HostSkillsSnapshot;
|
|
use codex_core_skills::build_available_skills;
|
|
use codex_core_skills::render::SkillRenderSideEffects;
|
|
use codex_extension_api::ContextualUserFragment;
|
|
use codex_extension_api::ExtensionMetrics;
|
|
use codex_extension_api::PreviousWorldStateSection;
|
|
use codex_extension_api::RenderedWorldStateFragment;
|
|
use codex_extension_api::WorldStateSectionContribution;
|
|
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG;
|
|
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
|
use serde_json::json;
|
|
|
|
use crate::fragments::AvailableSkillsInstructions;
|
|
use crate::render::SkillMetadataBudget;
|
|
use crate::render_observability::CatalogSurface;
|
|
use crate::render_observability::record_catalog_metrics;
|
|
|
|
pub(crate) const SKILLS_WORLD_STATE_ID: &str = "skills";
|
|
pub(crate) const HOST_SKILLS_WORLD_STATE_ID: &str = "host_skills";
|
|
const NO_EXECUTOR_SKILLS_BODY: &str =
|
|
"\n## Skills update\nNo selected-environment skills are currently available.\n";
|
|
const HIDDEN_EXECUTOR_SKILLS_BODY: &str = "\n## Skills update\nSelected-environment skills are not listed automatically. Explicit skill mentions can still be resolved when available.\n";
|
|
const NO_HOST_SKILLS_BODY: &str =
|
|
"\n## Host skills update\nNo host skills are currently available.\n";
|
|
const HIDDEN_HOST_SKILLS_BODY: &str = "\n## Host skills update\nHost skills are not listed automatically. Explicit skill mentions can still be resolved when available.\n";
|
|
const OMITTED_HOST_SKILLS_BODY: &str = "\n## Host skills update\nHost skills are available but omitted from the model-visible skills list because the skills context budget was exceeded.\n";
|
|
|
|
pub(crate) type HostSkillsWarningEmitter = Arc<dyn Fn(String) + Send + Sync>;
|
|
|
|
pub(crate) fn executor_skills_world_state_section(
|
|
body: Option<String>,
|
|
include_instructions: bool,
|
|
) -> WorldStateSectionContribution {
|
|
let snapshot = json!({
|
|
"body": body,
|
|
"includeInstructions": include_instructions,
|
|
});
|
|
let retained_body = body.clone();
|
|
|
|
let contribution =
|
|
WorldStateSectionContribution::new(SKILLS_WORLD_STATE_ID, snapshot, move |previous| {
|
|
let previous_is_absent = matches!(&previous, PreviousWorldStateSection::Absent);
|
|
if let PreviousWorldStateSection::Known(previous) = &previous {
|
|
let previous_body = previous.get("body").and_then(serde_json::Value::as_str);
|
|
let previous_include_instructions = previous
|
|
.get("includeInstructions")
|
|
.and_then(serde_json::Value::as_bool);
|
|
if previous_body == body.as_deref()
|
|
&& previous_include_instructions == Some(include_instructions)
|
|
{
|
|
return None;
|
|
}
|
|
}
|
|
|
|
let body = match body.as_deref() {
|
|
Some(body) => Some(body),
|
|
None if previous_is_absent => None,
|
|
None if !include_instructions => Some(HIDDEN_EXECUTOR_SKILLS_BODY),
|
|
None => Some(NO_EXECUTOR_SKILLS_BODY),
|
|
};
|
|
body.map(|body| {
|
|
RenderedWorldStateFragment::new(
|
|
"developer",
|
|
(SKILLS_INSTRUCTIONS_OPEN_TAG, SKILLS_INSTRUCTIONS_CLOSE_TAG),
|
|
body,
|
|
)
|
|
})
|
|
})
|
|
.with_legacy_matcher(|role, text| {
|
|
role == "developer"
|
|
&& text.trim_start().starts_with(SKILLS_INSTRUCTIONS_OPEN_TAG)
|
|
&& text.trim_end().ends_with(SKILLS_INSTRUCTIONS_CLOSE_TAG)
|
|
});
|
|
match retained_body {
|
|
Some(body) => contribution.with_retained_fragment_matcher(move |role, text| {
|
|
role == "developer" && text.contains(&body)
|
|
}),
|
|
None => contribution,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn host_skills_world_state_section(
|
|
host_snapshot: &HostSkillsSnapshot,
|
|
include_instructions: bool,
|
|
include_skills_usage_instructions: bool,
|
|
metadata_budget: SkillMetadataBudget,
|
|
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
|
|
warning_emitter: HostSkillsWarningEmitter,
|
|
) -> WorldStateSectionContribution {
|
|
let outcome = host_snapshot.outcome();
|
|
let metadata_budget = match metadata_budget {
|
|
SkillMetadataBudget::Tokens(limit) => codex_core_skills::SkillMetadataBudget::Tokens(limit),
|
|
SkillMetadataBudget::Characters(limit) => {
|
|
codex_core_skills::SkillMetadataBudget::Characters(limit)
|
|
}
|
|
};
|
|
let available = if include_instructions {
|
|
build_available_skills(outcome, metadata_budget, SkillRenderSideEffects::None)
|
|
} else {
|
|
None
|
|
};
|
|
let warning_message = available
|
|
.as_ref()
|
|
.and_then(|available| available.warning_message.clone());
|
|
let render_metrics = include_instructions.then(|| {
|
|
available
|
|
.as_ref()
|
|
.map(|available| {
|
|
let report = &available.report;
|
|
(
|
|
report.total_count,
|
|
report.included_count,
|
|
report.omitted_count,
|
|
report.truncated_description_chars,
|
|
)
|
|
})
|
|
.unwrap_or_default()
|
|
});
|
|
let body = available.map(|available| {
|
|
AvailableSkillsInstructions::from_available_skills(
|
|
available,
|
|
include_skills_usage_instructions,
|
|
)
|
|
.body()
|
|
});
|
|
rendered_host_skills_world_state_section(
|
|
body,
|
|
include_instructions,
|
|
render_metrics,
|
|
extension_metrics,
|
|
warning_message,
|
|
warning_emitter,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn rendered_host_skills_world_state_section(
|
|
body: Option<String>,
|
|
include_instructions: bool,
|
|
render_metrics: Option<(usize, usize, usize, usize)>,
|
|
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
|
|
warning_message: Option<String>,
|
|
warning_emitter: HostSkillsWarningEmitter,
|
|
) -> WorldStateSectionContribution {
|
|
let body = body.or_else(|| {
|
|
render_metrics
|
|
.is_some_and(|(total_count, included_count, omitted_count, _)| {
|
|
total_count > 0 && included_count == 0 && omitted_count > 0
|
|
})
|
|
.then(|| OMITTED_HOST_SKILLS_BODY.to_string())
|
|
});
|
|
let snapshot = json!({
|
|
"body": body,
|
|
"includeInstructions": include_instructions,
|
|
});
|
|
let retained_fragment = body
|
|
.as_ref()
|
|
.map(|body| format!("{SKILLS_INSTRUCTIONS_OPEN_TAG}{body}{SKILLS_INSTRUCTIONS_CLOSE_TAG}"));
|
|
|
|
let contribution =
|
|
WorldStateSectionContribution::new(HOST_SKILLS_WORLD_STATE_ID, snapshot, move |previous| {
|
|
let previous_is_absent = matches!(&previous, PreviousWorldStateSection::Absent);
|
|
if let PreviousWorldStateSection::Known(previous) = &previous {
|
|
let previous_body = previous.get("body").and_then(serde_json::Value::as_str);
|
|
let previous_include_instructions = previous
|
|
.get("includeInstructions")
|
|
.and_then(serde_json::Value::as_bool);
|
|
if previous_body == body.as_deref()
|
|
&& previous_include_instructions == Some(include_instructions)
|
|
{
|
|
return None;
|
|
}
|
|
}
|
|
|
|
if let Some((total_count, included_count, omitted_count, truncated_description_chars)) =
|
|
render_metrics
|
|
{
|
|
record_catalog_metrics(
|
|
extension_metrics.as_deref(),
|
|
CatalogSurface::HostWorldState,
|
|
total_count,
|
|
included_count,
|
|
omitted_count,
|
|
truncated_description_chars,
|
|
);
|
|
}
|
|
let body = match body.as_deref() {
|
|
Some(body) => body,
|
|
None if previous_is_absent => return None,
|
|
None if !include_instructions => HIDDEN_HOST_SKILLS_BODY,
|
|
None => NO_HOST_SKILLS_BODY,
|
|
};
|
|
if let Some(message) = warning_message.as_ref() {
|
|
warning_emitter(message.clone());
|
|
}
|
|
Some(RenderedWorldStateFragment::new(
|
|
"developer",
|
|
(SKILLS_INSTRUCTIONS_OPEN_TAG, SKILLS_INSTRUCTIONS_CLOSE_TAG),
|
|
body,
|
|
))
|
|
});
|
|
match retained_fragment {
|
|
Some(fragment) => contribution.with_retained_fragment_matcher(move |role, text| {
|
|
role == "developer" && text.contains(&fragment)
|
|
}),
|
|
None => contribution,
|
|
}
|
|
}
|