[codex] compact network context rendering (#21875)

## Why

The model-visible `<network>` context currently repeats indentation and
a pair of XML tags for every allowed or denied domain. Large domain sets
spend a surprising amount of prompt budget on that scaffolding instead
of the actual policy values.

## What changed

- Render allowed domains as one comma-separated `<allowed>` value
instead of one element per domain.
- Render denied domains the same way.
- Keep the full allow/deny domain sets model-visible while updating the
serialization and settings-update coverage for the denser shape.

## Example

Before:
```xml
<network enabled="true">
  <allowed>api.example.test</allowed>
  <allowed>cdn.example.test</allowed>
  <denied>blocked.example.test</denied>
</network>
```

After:
```xml
<network enabled="true"><allowed>api.example.test,cdn.example.test</allowed><denied>blocked.example.test</denied></network>
```

## Validation

- `cargo test -p codex-core environment_context`
- `cargo test -p codex-core
build_settings_update_items_emits_environment_item_for_network_changes`
- Ran a local `codex` session with a real network context containing 121
allowed domains and 42 denied domains, then inspected the raw prompt
with `raw_token_viewer_cli.py`. With the same domain set, the rendered
`<network>` section shrank from 7,175 characters across 161 lines to
3,666 characters on one line, and the containing environment-context
block fell from 6,428 tokens to 5,379 tokens.
This commit is contained in:
sayan-oai
2026-05-08 20:52:48 -07:00
committed by GitHub
Unverified
parent 479491ed89
commit 77d9223e9f
3 changed files with 23 additions and 16 deletions
@@ -96,6 +96,24 @@ impl NetworkContext {
denied_domains,
}
}
fn render(&self) -> String {
let mut rendered = "<network enabled=\"true\">".to_string();
Self::push_rendered_domain_element(&mut rendered, "allowed", &self.allowed_domains);
Self::push_rendered_domain_element(&mut rendered, "denied", &self.denied_domains);
rendered.push_str("</network>");
rendered
}
fn push_rendered_domain_element(rendered_network: &mut String, name: &str, domains: &[String]) {
if domains.is_empty() {
return;
}
rendered_network.push_str(&format!("<{name}>"));
rendered_network.push_str(&domains.join(","));
rendered_network.push_str(&format!("</{name}>"));
}
}
impl EnvironmentContext {
@@ -288,14 +306,7 @@ impl ContextualUserFragment for EnvironmentContext {
}
match &self.network {
Some(network) => {
lines.push(" <network enabled=\"true\">".to_string());
for allowed in &network.allowed_domains {
lines.push(format!(" <allowed>{allowed}</allowed>"));
}
for denied in &network.denied_domains {
lines.push(format!(" <denied>{denied}</denied>"));
}
lines.push(" </network>".to_string());
lines.push(format!(" {}", network.render()));
}
None => {
// TODO(mbolin): Include this line if it helps the model.
@@ -71,11 +71,7 @@ fn serialize_environment_context_with_network() {
<shell>bash</shell>
<current_date>2026-02-26</current_date>
<timezone>America/Los_Angeles</timezone>
<network enabled="true">
<allowed>api.example.com</allowed>
<allowed>*.openai.com</allowed>
<denied>blocked.example.com</denied>
</network>
<network enabled="true"><allowed>api.example.com,*.openai.com</allowed><denied>blocked.example.com</denied></network>
</environment_context>"#,
test_path_buf("/repo").display()
);
+3 -3
View File
@@ -5896,9 +5896,9 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
.into_iter()
.find(|text| text.contains("<environment_context>"))
.expect("environment update item should be emitted");
assert!(environment_update.contains("<network enabled=\"true\">"));
assert!(environment_update.contains("<allowed>api.example.com</allowed>"));
assert!(environment_update.contains("<denied>blocked.example.com</denied>"));
assert!(environment_update.contains(
"<network enabled=\"true\"><allowed>api.example.com</allowed><denied>blocked.example.com</denied></network>"
));
}
#[tokio::test]