mirror of
https://github.com/openai/codex.git
synced 2026-11-09 00:39:32 +00:00
Add experimental context management activation (#42385)
## What changed - Add the under-development `features.context_management.experimental_mode` configuration and schema. - For eligible ChatGPT Plus, Pro, and Pro Lite sessions using the Codex backend, enable token-budget context, history notes, and the `new_context` tool when experimental context management is configured. - Keep the feature disabled for custom providers, provider credentials, non-Codex endpoints, and temporary structured threads. ## Testing - Cover subscription eligibility, backend restrictions, configuration resolution, token-budget guidance, and temporary-thread overrides. GitOrigin-RevId: 19402af19c4d69e09a7cdb3687ee7d233f6a562a
This commit is contained in:
1 parent
312709252d
commit
cff76fa96f
11 files changed
+236
-16
No files matched your search
@@ -104,6 +104,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if feature.id == codex_features::Feature::ContextManagement {
|
||||||
|
validation.properties.insert(
|
||||||
|
feature.key.to_string(),
|
||||||
|
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||||
|
codex_features::ContextManagementConfigToml,
|
||||||
|
>>(),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if feature.id == codex_features::Feature::RolloutBudget {
|
if feature.id == codex_features::Feature::RolloutBudget {
|
||||||
validation.properties.insert(
|
validation.properties.insert(
|
||||||
feature.key.to_string(),
|
feature.key.to_string(),
|
||||||
|
|||||||
@@ -699,6 +699,9 @@
|
|||||||
"content_item_kinds": {
|
"content_item_kinds": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"context_management": {
|
||||||
|
"$ref": "#/definitions/FeatureToml_for_ContextManagementConfigToml"
|
||||||
|
},
|
||||||
"current_time_reminder": {
|
"current_time_reminder": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
|
||||||
},
|
},
|
||||||
@@ -1142,6 +1145,16 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ContextManagementConfigToml": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"experimental_mode": {
|
||||||
|
"description": "Enables experimental context management.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"CurrentTimeReminderConfigToml": {
|
"CurrentTimeReminderConfigToml": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1255,6 +1268,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"FeatureToml_for_ContextManagementConfigToml": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/ContextManagementConfigToml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"FeatureToml_for_CurrentTimeReminderConfigToml": {
|
"FeatureToml_for_CurrentTimeReminderConfigToml": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
@@ -6014,6 +6037,9 @@
|
|||||||
"content_item_kinds": {
|
"content_item_kinds": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"context_management": {
|
||||||
|
"$ref": "#/definitions/FeatureToml_for_ContextManagementConfigToml"
|
||||||
|
},
|
||||||
"current_time_reminder": {
|
"current_time_reminder": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -707,7 +707,11 @@ async fn load_config_resolves_token_budget_config() -> std::io::Result<()> {
|
|||||||
for (config_toml, expected) in [
|
for (config_toml, expected) in [
|
||||||
(
|
(
|
||||||
"[features]\ntoken_budget = true\n",
|
"[features]\ntoken_budget = true\n",
|
||||||
TokenBudgetConfig::default(),
|
Some(TokenBudgetConfig::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"features.context_management.experimental_mode = true\n",
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"
|
r#"
|
||||||
@@ -720,14 +724,14 @@ guidance_message = "Preserve important state before compaction."
|
|||||||
auto_compact_fallback_prompt = " Write notes immediately. "
|
auto_compact_fallback_prompt = " Write notes immediately. "
|
||||||
auto_compact_fallback_buffer_tokens = 8000
|
auto_compact_fallback_buffer_tokens = 8000
|
||||||
"#,
|
"#,
|
||||||
TokenBudgetConfig {
|
Some(TokenBudgetConfig {
|
||||||
use_history_notes_extension: true,
|
use_history_notes_extension: true,
|
||||||
reminder_threshold_tokens: Some(16_000),
|
reminder_threshold_tokens: Some(16_000),
|
||||||
reminder_message_template: "Custom reminder: {n_remaining} tokens.".to_string(),
|
reminder_message_template: "Custom reminder: {n_remaining} tokens.".to_string(),
|
||||||
guidance_message: Some("Preserve important state before compaction.".to_string()),
|
guidance_message: Some("Preserve important state before compaction.".to_string()),
|
||||||
auto_compact_fallback_prompt: Some("Write notes immediately.".to_string()),
|
auto_compact_fallback_prompt: Some("Write notes immediately.".to_string()),
|
||||||
auto_compact_fallback_buffer_tokens: Some(8_000),
|
auto_compact_fallback_buffer_tokens: Some(8_000),
|
||||||
},
|
}),
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
let codex_home = tempdir()?;
|
let codex_home = tempdir()?;
|
||||||
@@ -739,8 +743,14 @@ auto_compact_fallback_buffer_tokens = 8000
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert!(config.features.enabled(Feature::TokenBudget));
|
assert_eq!(
|
||||||
assert_eq!(config.token_budget, Some(expected));
|
config.features.enabled(Feature::TokenBudget),
|
||||||
|
expected.is_some()
|
||||||
|
);
|
||||||
|
if expected.is_none() {
|
||||||
|
assert!(config.features.enabled(Feature::ContextManagement));
|
||||||
|
}
|
||||||
|
assert_eq!(config.token_budget, expected);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2766,7 +2766,7 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_token_budget_config(
|
pub(crate) fn resolve_token_budget_config(
|
||||||
config_toml: &ConfigToml,
|
config_toml: &ConfigToml,
|
||||||
features: &ManagedFeatures,
|
features: &ManagedFeatures,
|
||||||
) -> std::io::Result<Option<TokenBudgetConfig>> {
|
) -> std::io::Result<Option<TokenBudgetConfig>> {
|
||||||
|
|||||||
@@ -682,6 +682,8 @@ impl Session {
|
|||||||
let model_info = models_manager
|
let model_info = models_manager
|
||||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||||
.await;
|
.await;
|
||||||
|
let auth = auth_manager.auth_cached();
|
||||||
|
token_budget::apply_experimental_context(Arc::make_mut(&mut config), auth.as_ref())?;
|
||||||
// Intentionally resolve `enabled` and `use_history_notes_extension` only at
|
// Intentionally resolve `enabled` and `use_history_notes_extension` only at
|
||||||
// thread startup. Both activation flags stay fixed for this thread runtime,
|
// thread startup. Both activation flags stay fixed for this thread runtime,
|
||||||
// even if the selected model changes later.
|
// even if the selected model changes later.
|
||||||
|
|||||||
@@ -2,10 +2,59 @@ use super::session::Session;
|
|||||||
use super::turn_context::TurnContext;
|
use super::turn_context::TurnContext;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::config::TokenBudgetConfig;
|
use crate::config::TokenBudgetConfig;
|
||||||
|
use crate::config::resolve_token_budget_config;
|
||||||
use crate::context::ContextualUserFragment;
|
use crate::context::ContextualUserFragment;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_login::CodexAuth;
|
||||||
|
use codex_protocol::account::PlanType;
|
||||||
|
use codex_protocol::auth::AuthMode;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
|
|
||||||
|
fn experimental_context_is_eligible(auth_mode: AuthMode, plan_type: Option<PlanType>) -> bool {
|
||||||
|
auth_mode == AuthMode::Chatgpt
|
||||||
|
&& matches!(
|
||||||
|
plan_type,
|
||||||
|
Some(PlanType::Plus | PlanType::Pro | PlanType::ProLite)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn apply_experimental_context(
|
||||||
|
config: &mut Config,
|
||||||
|
auth: Option<&CodexAuth>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let provider = &config.model_provider;
|
||||||
|
if !config.features.enabled(Feature::ContextManagement)
|
||||||
|
|| !provider.supports_codex_backend_routes()
|
||||||
|
|| !provider.requires_openai_auth
|
||||||
|
|| provider.env_key.is_some()
|
||||||
|
|| provider.experimental_bearer_token.is_some()
|
||||||
|
|| provider.auth.is_some()
|
||||||
|
|| provider.aws.is_some()
|
||||||
|
|| !auth.is_some_and(|auth| {
|
||||||
|
experimental_context_is_eligible(auth.auth_mode(), auth.account_plan_type())
|
||||||
|
})
|
||||||
|
|| config.features.enable(Feature::TokenBudget).is_err()
|
||||||
|
|| !config.features.enabled(Feature::TokenBudget)
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.token_budget.is_none() {
|
||||||
|
let config_toml = config
|
||||||
|
.config_layer_stack
|
||||||
|
.effective_config()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
|
||||||
|
config.token_budget = resolve_token_budget_config(&config_toml, &config.features)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
.token_budget
|
||||||
|
.get_or_insert_default()
|
||||||
|
.use_history_notes_extension = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Detects explicit preferences before model defaults are applied to the turn config.
|
/// Detects explicit preferences before model defaults are applied to the turn config.
|
||||||
pub(super) fn has_explicit_settings(config: &Config) -> bool {
|
pub(super) fn has_explicit_settings(config: &Config) -> bool {
|
||||||
config
|
config
|
||||||
@@ -163,3 +212,27 @@ pub(super) async fn maybe_record(
|
|||||||
sess.record_conversation_items(turn_context, std::slice::from_ref(&response_item))
|
sess.record_conversation_items(turn_context, std::slice::from_ref(&response_item))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::experimental_context_is_eligible;
|
||||||
|
use codex_protocol::account::PlanType;
|
||||||
|
use codex_protocol::auth::AuthMode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn experimental_context_requires_eligible_chatgpt_subscription() {
|
||||||
|
for (auth_mode, plan_type, expected) in [
|
||||||
|
(AuthMode::Chatgpt, PlanType::Plus, true),
|
||||||
|
(AuthMode::Chatgpt, PlanType::Pro, true),
|
||||||
|
(AuthMode::Chatgpt, PlanType::ProLite, true),
|
||||||
|
(AuthMode::Chatgpt, PlanType::Free, false),
|
||||||
|
(AuthMode::Chatgpt, PlanType::Enterprise, false),
|
||||||
|
(AuthMode::ApiKey, PlanType::Pro, false),
|
||||||
|
] {
|
||||||
|
assert_eq!(
|
||||||
|
experimental_context_is_eligible(auth_mode, Some(plan_type)),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use codex_core::config::Config;
|
|||||||
use codex_core::config::TokenBudgetConfig;
|
use codex_core::config::TokenBudgetConfig;
|
||||||
use codex_extension_api::ExtensionRegistryBuilder;
|
use codex_extension_api::ExtensionRegistryBuilder;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_login::CodexAuth;
|
||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
||||||
use codex_protocol::items::TurnItem;
|
use codex_protocol::items::TurnItem;
|
||||||
@@ -235,8 +236,12 @@ async fn token_budget_context_is_only_emitted_with_full_context() -> Result<()>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case("features.token_budget.enabled = true"; "token_budget")]
|
||||||
|
#[test_case("features.context_management.experimental_mode = true"; "experimental_mode")]
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn token_budget_guidance_precedes_standalone_context_window() -> Result<()> {
|
async fn token_budget_guidance_precedes_standalone_context_window(
|
||||||
|
activation: &'static str,
|
||||||
|
) -> Result<()> {
|
||||||
skip_if_no_network!(Ok(()));
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
let server = start_mock_server().await;
|
let server = start_mock_server().await;
|
||||||
@@ -249,18 +254,26 @@ async fn token_budget_guidance_precedes_standalone_context_window() -> Result<()
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let guidance_message = "Preserve important state before compaction.";
|
let guidance_message = "Preserve important state before compaction.";
|
||||||
|
let backend_url = format!("{}/backend-api/codex", server.uri());
|
||||||
let test = test_codex()
|
let test = test_codex()
|
||||||
|
.with_auth(CodexAuth::from_external_chatgpt_tokens(
|
||||||
|
"header.e30.signature",
|
||||||
|
"account-123",
|
||||||
|
Some("plus"),
|
||||||
|
)?)
|
||||||
|
.with_pre_build_hook(move |home| {
|
||||||
|
std::fs::write(
|
||||||
|
home.join("config.toml"),
|
||||||
|
format!(
|
||||||
|
"{activation}\nfeatures.token_budget.guidance_message = {guidance_message:?}\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("write token-budget guidance configuration");
|
||||||
|
})
|
||||||
.with_config(move |config| {
|
.with_config(move |config| {
|
||||||
|
config.model_provider.base_url = Some(backend_url);
|
||||||
config.update_plan_enabled = true;
|
config.update_plan_enabled = true;
|
||||||
config.model_context_window = Some(CONFIGURED_CONTEXT_WINDOW);
|
config.model_context_window = Some(CONFIGURED_CONTEXT_WINDOW);
|
||||||
config.token_budget = Some(TokenBudgetConfig {
|
|
||||||
guidance_message: Some(guidance_message.to_string()),
|
|
||||||
..TokenBudgetConfig::default()
|
|
||||||
});
|
|
||||||
config
|
|
||||||
.features
|
|
||||||
.enable(Feature::TokenBudget)
|
|
||||||
.expect("test config should allow token budget");
|
|
||||||
})
|
})
|
||||||
.build_with_auto_env(&server)
|
.build_with_auto_env(&server)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -291,6 +304,59 @@ async fn token_budget_guidance_precedes_standalone_context_window() -> Result<()
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case("OpenAI", "/backend-api/codex", None, true; "codex_backend")]
|
||||||
|
#[test_case("Custom", "/backend-api/codex", None, false; "custom_provider")]
|
||||||
|
#[test_case("OpenAI", "/v1", None, false; "non_codex_endpoint")]
|
||||||
|
#[test_case("OpenAI", "/backend-api/codex", Some("test-provider-token"), false; "provider_credentials")]
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn experimental_context_requires_codex_backend(
|
||||||
|
provider_name: &'static str,
|
||||||
|
base_path: &'static str,
|
||||||
|
bearer_token: Option<&'static str>,
|
||||||
|
expected_enabled: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let response = mount_sse_once(&server, sse_completed("resp-1")).await;
|
||||||
|
let base_url = format!("{}{base_path}", server.uri());
|
||||||
|
let test = test_codex()
|
||||||
|
.with_auth(CodexAuth::from_external_chatgpt_tokens(
|
||||||
|
"header.e30.signature",
|
||||||
|
"account-123",
|
||||||
|
Some("plus"),
|
||||||
|
)?)
|
||||||
|
.with_config(move |config| {
|
||||||
|
config.model_provider.name = provider_name.to_string();
|
||||||
|
config.model_provider.base_url = Some(base_url);
|
||||||
|
config.model_provider.experimental_bearer_token = bearer_token.map(Into::into);
|
||||||
|
config.model_context_window = Some(CONFIGURED_CONTEXT_WINDOW);
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::ContextManagement)
|
||||||
|
.expect("test config should allow experimental context");
|
||||||
|
})
|
||||||
|
.build_with_auto_env(&server)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
test.submit_turn("inspect experimental context activation")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let request = response.single_request();
|
||||||
|
assert_eq!(
|
||||||
|
tool_names(&request)
|
||||||
|
.iter()
|
||||||
|
.any(|name| name == "new_context"),
|
||||||
|
expected_enabled
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
!token_budget_contexts(&request).is_empty(),
|
||||||
|
expected_enabled
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn token_budget_uses_model_message_defaults() -> Result<()> {
|
async fn token_budget_uses_model_message_defaults() -> Result<()> {
|
||||||
skip_if_no_network!(Ok(()));
|
skip_if_no_network!(Ok(()));
|
||||||
|
|||||||
@@ -293,6 +293,20 @@ impl FeatureConfig for MultiAgentV2ConfigToml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct ContextManagementConfigToml {
|
||||||
|
/// Enables experimental context management.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub experimental_mode: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeatureConfig for ContextManagementConfigToml {
|
||||||
|
fn enabled(&self) -> Option<bool> {
|
||||||
|
self.experimental_mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct TokenBudgetConfigToml {
|
pub struct TokenBudgetConfigToml {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod feature_configs;
|
|||||||
mod legacy;
|
mod legacy;
|
||||||
pub use feature_configs::CodeModeConfigToml;
|
pub use feature_configs::CodeModeConfigToml;
|
||||||
pub use feature_configs::CodeModeHostConfigToml;
|
pub use feature_configs::CodeModeHostConfigToml;
|
||||||
|
pub use feature_configs::ContextManagementConfigToml;
|
||||||
pub use feature_configs::CurrentTimeReminderConfigToml;
|
pub use feature_configs::CurrentTimeReminderConfigToml;
|
||||||
pub use feature_configs::CurrentTimeReminderDeliveryMode;
|
pub use feature_configs::CurrentTimeReminderDeliveryMode;
|
||||||
pub use feature_configs::CurrentTimeSource;
|
pub use feature_configs::CurrentTimeSource;
|
||||||
@@ -312,6 +313,8 @@ pub enum Feature {
|
|||||||
Goals,
|
Goals,
|
||||||
/// Add current context-window metadata to model-visible context.
|
/// Add current context-window metadata to model-visible context.
|
||||||
TokenBudget,
|
TokenBudget,
|
||||||
|
/// Enables experimental context management.
|
||||||
|
ContextManagement,
|
||||||
/// Track and report a shared token budget across a session's agent threads.
|
/// Track and report a shared token budget across a session's agent threads.
|
||||||
RolloutBudget,
|
RolloutBudget,
|
||||||
/// Add current-time reminders to model-visible context.
|
/// Add current-time reminders to model-visible context.
|
||||||
@@ -763,6 +766,8 @@ pub struct FeaturesToml {
|
|||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub token_budget: Option<FeatureToml<TokenBudgetConfigToml>>,
|
pub token_budget: Option<FeatureToml<TokenBudgetConfigToml>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub context_management: Option<FeatureToml<ContextManagementConfigToml>>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub rollout_budget: Option<FeatureToml<RolloutBudgetConfigToml>>,
|
pub rollout_budget: Option<FeatureToml<RolloutBudgetConfigToml>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub current_time_reminder: Option<FeatureToml<CurrentTimeReminderConfigToml>>,
|
pub current_time_reminder: Option<FeatureToml<CurrentTimeReminderConfigToml>>,
|
||||||
@@ -809,6 +814,13 @@ impl FeaturesToml {
|
|||||||
if let Some(enabled) = self.token_budget.as_ref().and_then(FeatureToml::enabled) {
|
if let Some(enabled) = self.token_budget.as_ref().and_then(FeatureToml::enabled) {
|
||||||
entries.insert(Feature::TokenBudget.key().to_string(), enabled);
|
entries.insert(Feature::TokenBudget.key().to_string(), enabled);
|
||||||
}
|
}
|
||||||
|
if let Some(enabled) = self
|
||||||
|
.context_management
|
||||||
|
.as_ref()
|
||||||
|
.and_then(FeatureToml::enabled)
|
||||||
|
{
|
||||||
|
entries.insert(Feature::ContextManagement.key().to_string(), enabled);
|
||||||
|
}
|
||||||
if let Some(enabled) = self.rollout_budget.as_ref().and_then(FeatureToml::enabled) {
|
if let Some(enabled) = self.rollout_budget.as_ref().and_then(FeatureToml::enabled) {
|
||||||
entries.insert(Feature::RolloutBudget.key().to_string(), enabled);
|
entries.insert(Feature::RolloutBudget.key().to_string(), enabled);
|
||||||
}
|
}
|
||||||
@@ -1549,6 +1561,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
stage: Stage::UnderDevelopment,
|
stage: Stage::UnderDevelopment,
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
},
|
},
|
||||||
|
FeatureSpec {
|
||||||
|
id: Feature::ContextManagement,
|
||||||
|
key: "context_management",
|
||||||
|
stage: Stage::UnderDevelopment,
|
||||||
|
default_enabled: false,
|
||||||
|
},
|
||||||
FeatureSpec {
|
FeatureSpec {
|
||||||
id: Feature::RolloutBudget,
|
id: Feature::RolloutBudget,
|
||||||
key: "rollout_budget",
|
key: "rollout_budget",
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ async fn temporary_recap_threads_disable_memories_and_remote_mcp_servers() -> Re
|
|||||||
let codex_home = tempdir()?;
|
let codex_home = tempdir()?;
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
codex_home.path().join("config.toml"),
|
codex_home.path().join("config.toml"),
|
||||||
"[mcp_servers.filesystem]\ncommand = 'true'\n",
|
"features.context_management.experimental_mode = true\n[mcp_servers.filesystem]\ncommand = 'true'\n",
|
||||||
)?;
|
)?;
|
||||||
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||||
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs());
|
||||||
@@ -433,6 +433,7 @@ async fn temporary_recap_threads_disable_memories_and_remote_mcp_servers() -> Re
|
|||||||
let starts = recorded_params(&requests, "thread/start");
|
let starts = recorded_params(&requests, "thread/start");
|
||||||
assert_eq!(starts.len(), 1);
|
assert_eq!(starts.len(), 1);
|
||||||
assert_eq!(starts[0]["config"]["features.memories"], false);
|
assert_eq!(starts[0]["config"]["features.memories"], false);
|
||||||
|
assert_eq!(starts[0]["config"]["features.context_management"], false);
|
||||||
assert!(starts[0]["config"].get("features.memory_tool").is_none());
|
assert!(starts[0]["config"].get("features.memory_tool").is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
starts[0]["config"]["mcp_servers"]["filesystem"]["enabled"],
|
starts[0]["config"]["mcp_servers"]["filesystem"]["enabled"],
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ pub(crate) async fn start_temporary_thread(
|
|||||||
("features.apps".to_string(), false.into()),
|
("features.apps".to_string(), false.into()),
|
||||||
("features.code_mode".to_string(), false.into()),
|
("features.code_mode".to_string(), false.into()),
|
||||||
("features.code_mode_only".to_string(), false.into()),
|
("features.code_mode_only".to_string(), false.into()),
|
||||||
|
("features.context_management".to_string(), false.into()),
|
||||||
("features.current_time_reminder".to_string(), false.into()),
|
("features.current_time_reminder".to_string(), false.into()),
|
||||||
("features.deferred_executor".to_string(), false.into()),
|
("features.deferred_executor".to_string(), false.into()),
|
||||||
("features.enable_fanout".to_string(), false.into()),
|
("features.enable_fanout".to_string(), false.into()),
|
||||||
|
|||||||
Reference in new issue
Block a user