Files
codex/codex-rs/features/src/feature_configs.rs
T
rka-oaiandGitHub ecc4c30e28 [codex] add rollout token budget configuration (varlength 1/N) (#28746)
## What

This PR defines the structured configuration contract for shared rollout
token budgets (across ALL agent threads under 1 rollout).

```toml
[features.rollout_budget]
enabled = true
limit_tokens = 100000
reminder_interval_tokens = 10000
sampling_token_weight = 1.0
prefill_token_weight = 0.1
```

The reminder interval defaults to 10% of the rollout limit. Sampling and
prefill weights default to `1.0`.

## Scope

This PR only defines and validates configuration. It does not track
usage, inject reminders, or stop a rollout. Accounting and reminders are
implemented in the stacked follow-up #28494.

The existing `token_budget` feature remains unchanged. `rollout_budget`
has its own feature key and configuration type.

## Tests

The config test verifies that the structured fields resolve into
`RolloutBudgetConfig` and do not enable the existing `token_budget`
feature.

Local checks:

- `just write-config-schema`
- `just test -p codex-core load_config_resolves_rollout_budget`
- `cargo check -p codex-thread-manager-sample`
- `git diff --check`

The full workspace test suite was not run locally.
2026-06-18 04:29:47 -07:00

173 lines
6.2 KiB
Rust

use crate::FeatureConfig;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CodeModeConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
/// Exact tool namespaces to omit from the code-mode nested tool surface.
#[serde(skip_serializing_if = "Option::is_none")]
pub excluded_tool_namespaces: Option<Vec<String>>,
/// Exact tool namespaces to expose only as direct model tools.
/// These tools bypass deferral, remain top-level in code-mode-only sessions, and are omitted
/// from the nested code-mode tool surface.
#[serde(skip_serializing_if = "Option::is_none")]
pub direct_only_tool_namespaces: Option<Vec<String>>,
}
impl FeatureConfig for CodeModeConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = Some(enabled);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MultiAgentV2ConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub max_concurrent_threads_per_session: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0, max = 3600000))]
pub min_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0, max = 3600000))]
pub max_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0, max = 3600000))]
pub default_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_hint_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_hint_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root_agent_usage_hint_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subagent_usage_hint_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1, max = 64), regex(pattern = r"^[a-zA-Z0-9_-]+$"))]
pub tool_namespace: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hide_spawn_agent_metadata: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub non_code_mode_only: Option<bool>,
}
impl FeatureConfig for MultiAgentV2ConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = Some(enabled);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RolloutBudgetConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub limit_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub reminder_interval_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0.0))]
pub sampling_token_weight: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0.0))]
pub prefill_token_weight: Option<f64>,
}
impl FeatureConfig for RolloutBudgetConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = Some(enabled);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct RemovedAppsMcpPathOverrideConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NetworkProxyConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_socks5: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub socks_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_socks5_udp: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_upstream_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dangerously_allow_non_loopback_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dangerously_allow_all_unix_sockets: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<NetworkProxyModeToml>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domains: Option<BTreeMap<String, NetworkProxyDomainPermissionToml>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unix_sockets: Option<BTreeMap<String, NetworkProxyUnixSocketPermissionToml>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_local_binding: Option<bool>,
}
impl FeatureConfig for NetworkProxyConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = Some(enabled);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkProxyModeToml {
Limited,
Full,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkProxyDomainPermissionToml {
Allow,
Deny,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkProxyUnixSocketPermissionToml {
Allow,
Deny,
}