mirror of
https://github.com/openai/codex.git
synced 2026-05-22 12:04:19 +00:00
feat: generic hints for configs
This commit is contained in:
@@ -45,12 +45,22 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
||||
}
|
||||
validation
|
||||
.properties
|
||||
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());
|
||||
.insert(
|
||||
feature.key.to_string(),
|
||||
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||
codex_features::NoExtraFeatureConfigToml,
|
||||
>>(),
|
||||
);
|
||||
}
|
||||
for legacy_key in legacy_feature_keys() {
|
||||
validation
|
||||
.properties
|
||||
.insert(legacy_key.to_string(), schema_gen.subschema_for::<bool>());
|
||||
.insert(
|
||||
legacy_key.to_string(),
|
||||
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||
codex_features::NoExtraFeatureConfigToml,
|
||||
>>(),
|
||||
);
|
||||
}
|
||||
validation.additional_properties = Some(Box::new(Schema::Bool(false)));
|
||||
object.object = Some(Box::new(validation));
|
||||
|
||||
@@ -218,18 +218,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AppsMcpPathOverrideConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AskForApproval": {
|
||||
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
|
||||
"oneOf": [
|
||||
@@ -359,244 +347,244 @@
|
||||
"description": "Optional feature toggles scoped to this profile.",
|
||||
"properties": {
|
||||
"apply_patch_freeform": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apply_patch_streaming_events": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apps": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apps_mcp_path_override": {
|
||||
"$ref": "#/definitions/FeatureToml_for_AppsMcpPathOverrideConfigToml"
|
||||
},
|
||||
"browser_use": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"browser_use_external": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"chronicle": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"code_mode": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"code_mode_only": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"codex_git_commit": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"codex_hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"collaboration_modes": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"computer_use": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"connectors": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"default_mode_request_user_input": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"elevated_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_fanout": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_mcp_apps": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_request_compression": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"external_migration": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"fast_mode": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"goals": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"guardian_approval": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"image_detail_original": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"image_generation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"in_app_browser": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"js_repl": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"js_repl_tools_only": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"memories": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"memory_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"multi_agent": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"multi_agent_v2": {
|
||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||
},
|
||||
"personality": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"plugin_hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"plugins": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"prevent_idle_sleep": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_control": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_models": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_plugin": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_permissions": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_permissions_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_rule": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"responses_websockets": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"responses_websockets_v2": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"runtime_metrics": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"search_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_snapshot": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_zsh_fork": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"skill_env_var_dependency_prompt": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"skill_mcp_dependency_install": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"sqlite": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"steer": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"telepathy": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"terminal_resize_reflow": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_call_mcp_elicitation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_search": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_search_always_defer_mcp_tools": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_suggest": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tui_app_server": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"unavailable_dummy_tools": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"undo": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"unified_exec": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"use_legacy_landlock": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"use_linux_sandbox_bwrap": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search_cached": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search_request": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"workspace_dependencies": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"workspace_owner_usage_nudge": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -765,13 +753,75 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureConfigTable_for_AppsMcpPathOverrideConfigToml": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hint": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureConfigTable_for_MultiAgentV2ConfigToml": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hide_spawn_agent_metadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hint": {
|
||||
"type": "string"
|
||||
},
|
||||
"max_concurrent_threads_per_session": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"min_wait_timeout_ms": {
|
||||
"format": "int64",
|
||||
"maximum": 3600000.0,
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"root_agent_usage_hint_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"subagent_usage_hint_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"usage_hint_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"usage_hint_text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureConfigTable_for_NoExtraFeatureConfigToml": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hint": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureToml_for_AppsMcpPathOverrideConfigToml": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/AppsMcpPathOverrideConfigToml"
|
||||
"$ref": "#/definitions/FeatureConfigTable_for_AppsMcpPathOverrideConfigToml"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -781,7 +831,17 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/MultiAgentV2ConfigToml"
|
||||
"$ref": "#/definitions/FeatureConfigTable_for_MultiAgentV2ConfigToml"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FeatureToml_for_NoExtraFeatureConfigToml": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/FeatureConfigTable_for_NoExtraFeatureConfigToml"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1408,41 +1468,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MultiAgentV2ConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hide_spawn_agent_metadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"max_concurrent_threads_per_session": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"min_wait_timeout_ms": {
|
||||
"format": "int64",
|
||||
"maximum": 3600000.0,
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"root_agent_usage_hint_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"subagent_usage_hint_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"usage_hint_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"usage_hint_text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkDomainPermissionToml": {
|
||||
"enum": [
|
||||
"allow",
|
||||
@@ -3825,244 +3850,244 @@
|
||||
"description": "Centralized feature flags (new). Prefer this over individual toggles.",
|
||||
"properties": {
|
||||
"apply_patch_freeform": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apply_patch_streaming_events": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apps": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"apps_mcp_path_override": {
|
||||
"$ref": "#/definitions/FeatureToml_for_AppsMcpPathOverrideConfigToml"
|
||||
},
|
||||
"browser_use": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"browser_use_external": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"chronicle": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"code_mode": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"code_mode_only": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"codex_git_commit": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"codex_hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"collaboration_modes": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"computer_use": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"connectors": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"default_mode_request_user_input": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"elevated_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_fanout": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_mcp_apps": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"enable_request_compression": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"external_migration": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"fast_mode": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"goals": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"guardian_approval": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"image_detail_original": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"image_generation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"in_app_browser": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"js_repl": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"js_repl_tools_only": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"memories": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"memory_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"multi_agent": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"multi_agent_v2": {
|
||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||
},
|
||||
"personality": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"plugin_hooks": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"plugins": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"prevent_idle_sleep": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_control": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_models": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"remote_plugin": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_permissions": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_permissions_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"request_rule": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"responses_websockets": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"responses_websockets_v2": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"runtime_metrics": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"search_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_snapshot": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_tool": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"shell_zsh_fork": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"skill_env_var_dependency_prompt": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"skill_mcp_dependency_install": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"sqlite": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"steer": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"telepathy": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"terminal_resize_reflow": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_call_mcp_elicitation": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_search": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_search_always_defer_mcp_tools": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tool_suggest": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"tui_app_server": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"unavailable_dummy_tools": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"undo": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"unified_exec": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"use_legacy_landlock": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"use_linux_sandbox_bwrap": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search_cached": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"web_search_request": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"workspace_dependencies": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
},
|
||||
"workspace_owner_usage_nudge": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_NoExtraFeatureConfigToml"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -4497,4 +4522,4 @@
|
||||
},
|
||||
"title": "ConfigToml",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::AppsMcpPathOverrideConfigToml;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeatureConfigSource;
|
||||
use codex_features::FeatureConfigTable;
|
||||
use codex_features::FeatureOverrides;
|
||||
use codex_features::FeatureToml;
|
||||
use codex_features::Features;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
@@ -1876,44 +1876,66 @@ fn resolve_web_search_config(
|
||||
fn resolve_multi_agent_v2_config(
|
||||
config_toml: &ConfigToml,
|
||||
config_profile: &ConfigProfile,
|
||||
) -> MultiAgentV2Config {
|
||||
let base = multi_agent_v2_toml_config(config_toml.features.as_ref());
|
||||
let profile = multi_agent_v2_toml_config(config_profile.features.as_ref());
|
||||
) -> std::io::Result<MultiAgentV2Config> {
|
||||
let base = multi_agent_v2_toml_config(config_toml.features.as_ref())?;
|
||||
let profile = multi_agent_v2_toml_config(config_profile.features.as_ref())?;
|
||||
let default = MultiAgentV2Config::default();
|
||||
|
||||
let max_concurrent_threads_per_session = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.max_concurrent_threads_per_session)
|
||||
.or_else(|| base.and_then(|config| config.max_concurrent_threads_per_session))
|
||||
.or_else(|| {
|
||||
base.as_ref()
|
||||
.and_then(|config| config.max_concurrent_threads_per_session)
|
||||
})
|
||||
.unwrap_or(default.max_concurrent_threads_per_session);
|
||||
let min_wait_timeout_ms = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.min_wait_timeout_ms)
|
||||
.or_else(|| base.and_then(|config| config.min_wait_timeout_ms))
|
||||
.or_else(|| base.as_ref().and_then(|config| config.min_wait_timeout_ms))
|
||||
.unwrap_or(default.min_wait_timeout_ms);
|
||||
let usage_hint_enabled = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.usage_hint_enabled)
|
||||
.or_else(|| base.and_then(|config| config.usage_hint_enabled))
|
||||
.or_else(|| base.as_ref().and_then(|config| config.usage_hint_enabled))
|
||||
.unwrap_or(default.usage_hint_enabled);
|
||||
let usage_hint_text = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.usage_hint_text.as_ref())
|
||||
.or_else(|| base.and_then(|config| config.usage_hint_text.as_ref()))
|
||||
.or_else(|| {
|
||||
base.as_ref()
|
||||
.and_then(|config| config.usage_hint_text.as_ref())
|
||||
})
|
||||
.cloned()
|
||||
.or(default.usage_hint_text);
|
||||
let root_agent_usage_hint_text = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.root_agent_usage_hint_text.as_ref())
|
||||
.or_else(|| base.and_then(|config| config.root_agent_usage_hint_text.as_ref()))
|
||||
.or_else(|| {
|
||||
base.as_ref()
|
||||
.and_then(|config| config.root_agent_usage_hint_text.as_ref())
|
||||
})
|
||||
.cloned()
|
||||
.or(default.root_agent_usage_hint_text);
|
||||
let subagent_usage_hint_text = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.subagent_usage_hint_text.as_ref())
|
||||
.or_else(|| base.and_then(|config| config.subagent_usage_hint_text.as_ref()))
|
||||
.or_else(|| {
|
||||
base.as_ref()
|
||||
.and_then(|config| config.subagent_usage_hint_text.as_ref())
|
||||
})
|
||||
.cloned()
|
||||
.or(default.subagent_usage_hint_text);
|
||||
let hide_spawn_agent_metadata = profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.hide_spawn_agent_metadata)
|
||||
.or_else(|| base.and_then(|config| config.hide_spawn_agent_metadata))
|
||||
.or_else(|| {
|
||||
base.as_ref()
|
||||
.and_then(|config| config.hide_spawn_agent_metadata)
|
||||
})
|
||||
.unwrap_or(default.hide_spawn_agent_metadata);
|
||||
|
||||
MultiAgentV2Config {
|
||||
Ok(MultiAgentV2Config {
|
||||
max_concurrent_threads_per_session,
|
||||
min_wait_timeout_ms,
|
||||
usage_hint_enabled,
|
||||
@@ -1921,7 +1943,7 @@ fn resolve_multi_agent_v2_config(
|
||||
root_agent_usage_hint_text,
|
||||
subagent_usage_hint_text,
|
||||
hide_spawn_agent_metadata,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig {
|
||||
@@ -1938,19 +1960,37 @@ fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalRe
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_agent_v2_toml_config(features: Option<&FeaturesToml>) -> Option<&MultiAgentV2ConfigToml> {
|
||||
match features?.multi_agent_v2.as_ref()? {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => Some(config),
|
||||
}
|
||||
fn multi_agent_v2_toml_config(
|
||||
features: Option<&FeaturesToml>,
|
||||
) -> std::io::Result<Option<MultiAgentV2ConfigToml>> {
|
||||
typed_feature_config::<MultiAgentV2ConfigToml>(features, Feature::MultiAgentV2.key())
|
||||
.map(|config| config.map(|config| config.extra))
|
||||
}
|
||||
|
||||
fn apps_mcp_path_override_toml_config(
|
||||
features: Option<&FeaturesToml>,
|
||||
) -> Option<&AppsMcpPathOverrideConfigToml> {
|
||||
match features?.apps_mcp_path_override.as_ref()? {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => Some(config),
|
||||
) -> std::io::Result<Option<AppsMcpPathOverrideConfigToml>> {
|
||||
typed_feature_config::<AppsMcpPathOverrideConfigToml>(
|
||||
features,
|
||||
Feature::AppsMcpPathOverride.key(),
|
||||
)
|
||||
.map(|config| config.map(|config| config.extra))
|
||||
}
|
||||
|
||||
fn typed_feature_config<T>(
|
||||
features: Option<&FeaturesToml>,
|
||||
key: &'static str,
|
||||
) -> std::io::Result<Option<FeatureConfigTable<T>>>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
match features.and_then(|features| features.typed_config(key)) {
|
||||
Some(Ok(config)) => Ok(Some(config)),
|
||||
Some(Err(err)) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("invalid features.{key} config: {err}"),
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2476,13 +2516,14 @@ impl Config {
|
||||
let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features)
|
||||
.unwrap_or(WebSearchMode::Cached);
|
||||
let web_search_config = resolve_web_search_config(&cfg, &config_profile);
|
||||
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg, &config_profile);
|
||||
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg, &config_profile)?;
|
||||
let apps_mcp_path_override = if features.enabled(Feature::AppsMcpPathOverride) {
|
||||
let base = apps_mcp_path_override_toml_config(cfg.features.as_ref());
|
||||
let profile = apps_mcp_path_override_toml_config(config_profile.features.as_ref());
|
||||
let base = apps_mcp_path_override_toml_config(cfg.features.as_ref())?;
|
||||
let profile = apps_mcp_path_override_toml_config(config_profile.features.as_ref())?;
|
||||
profile
|
||||
.as_ref()
|
||||
.and_then(|config| config.path.as_ref())
|
||||
.or_else(|| base.and_then(|config| config.path.as_ref()))
|
||||
.or_else(|| base.as_ref().and_then(|config| config.path.as_ref()))
|
||||
.cloned()
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -4,7 +4,6 @@ use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::types::MemoriesToml;
|
||||
use codex_features::AppsMcpPathOverrideConfigToml;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeatureToml;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
use codex_protocol::ThreadId;
|
||||
@@ -143,14 +142,20 @@ fn save_config_resolved_fields(
|
||||
.features
|
||||
.get_or_insert_with(FeaturesToml::default);
|
||||
features.materialize_resolved_enabled(config.features.get());
|
||||
let mut multi_agent_v2: MultiAgentV2ConfigToml =
|
||||
let multi_agent_v2: MultiAgentV2ConfigToml =
|
||||
resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?;
|
||||
multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2));
|
||||
features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2));
|
||||
features.apps_mcp_path_override = Some(FeatureToml::Config(AppsMcpPathOverrideConfigToml {
|
||||
enabled: Some(config.features.enabled(Feature::AppsMcpPathOverride)),
|
||||
path: config.apps_mcp_path_override.clone(),
|
||||
}));
|
||||
features.materialize_resolved_config(
|
||||
Feature::MultiAgentV2,
|
||||
config.features.enabled(Feature::MultiAgentV2),
|
||||
multi_agent_v2,
|
||||
)?;
|
||||
features.materialize_resolved_config(
|
||||
Feature::AppsMcpPathOverride,
|
||||
config.features.enabled(Feature::AppsMcpPathOverride),
|
||||
AppsMcpPathOverrideConfigToml {
|
||||
path: config.apps_mcp_path_override.clone(),
|
||||
},
|
||||
)?;
|
||||
lock_config.memories = Some(resolved_config_to_toml::<MemoriesToml>(
|
||||
&config.memories,
|
||||
"memories",
|
||||
@@ -253,20 +258,31 @@ mod tests {
|
||||
}
|
||||
|
||||
let multi_agent_v2 = features
|
||||
.multi_agent_v2
|
||||
.as_ref()
|
||||
.get(Feature::MultiAgentV2.key())
|
||||
.expect("multi_agent_v2 config should be materialized");
|
||||
assert!(matches!(
|
||||
multi_agent_v2,
|
||||
FeatureToml::Config(MultiAgentV2ConfigToml {
|
||||
enabled: Some(false),
|
||||
max_concurrent_threads_per_session: Some(_),
|
||||
min_wait_timeout_ms: Some(_),
|
||||
usage_hint_enabled: Some(_),
|
||||
hide_spawn_agent_metadata: Some(_),
|
||||
FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(false),
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
));
|
||||
let multi_agent_v2 = features
|
||||
.typed_config::<MultiAgentV2ConfigToml>(Feature::MultiAgentV2.key())
|
||||
.expect("multi_agent_v2 config should parse")
|
||||
.expect("multi_agent_v2 config should deserialize");
|
||||
assert!(
|
||||
multi_agent_v2
|
||||
.extra
|
||||
.max_concurrent_threads_per_session
|
||||
.is_some()
|
||||
);
|
||||
assert!(multi_agent_v2.extra.min_wait_timeout_ms.is_some());
|
||||
assert!(multi_agent_v2.extra.usage_hint_enabled.is_some());
|
||||
assert!(multi_agent_v2.extra.hide_spawn_agent_metadata.is_some());
|
||||
|
||||
assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::FeatureConfig;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -6,8 +5,6 @@ use serde::Serialize;
|
||||
#[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>,
|
||||
@@ -26,31 +23,9 @@ pub struct MultiAgentV2ConfigToml {
|
||||
pub hide_spawn_agent_metadata: 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, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AppsMcpPathOverrideConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for AppsMcpPathOverrideConfigToml {
|
||||
fn enabled(&self) -> Option<bool> {
|
||||
self.enabled.or(self.path.as_ref().map(|_| true))
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = Some(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
807
codex-rs/features/src/machinery.rs
Normal file
807
codex-rs/features/src/machinery.rs
Normal file
@@ -0,0 +1,807 @@
|
||||
use crate::legacy::LegacyFeatureToggles;
|
||||
use crate::registry::FEATURES;
|
||||
use crate::registry::FeatureSpec;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::WarningEvent;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use toml::Table;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
/// High-level lifecycle stage for a feature.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Stage {
|
||||
/// Features that are still under development, not ready for external use
|
||||
UnderDevelopment,
|
||||
/// Experimental features made available to users through the `/experimental` menu
|
||||
Experimental {
|
||||
name: &'static str,
|
||||
menu_description: &'static str,
|
||||
announcement: &'static str,
|
||||
},
|
||||
/// Stable features. The feature flag is kept for ad-hoc enabling/disabling
|
||||
Stable,
|
||||
/// Deprecated feature that should not be used anymore.
|
||||
Deprecated,
|
||||
/// The feature flag is useless but kept for backward compatibility reason.
|
||||
Removed,
|
||||
}
|
||||
|
||||
impl Stage {
|
||||
pub fn experimental_menu_name(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Stage::Experimental { name, .. } => Some(name),
|
||||
Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn experimental_menu_description(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Stage::Experimental {
|
||||
menu_description, ..
|
||||
} => Some(menu_description),
|
||||
Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn experimental_announcement(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Stage::Experimental {
|
||||
announcement: "", ..
|
||||
} => None,
|
||||
Stage::Experimental { announcement, .. } => Some(announcement),
|
||||
Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique features toggled via configuration.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Feature {
|
||||
// Stable.
|
||||
/// Removed compatibility flag retained as a no-op so old configs can
|
||||
/// still parse `undo`.
|
||||
GhostCommit,
|
||||
/// Enable the default shell tool.
|
||||
ShellTool,
|
||||
/// Enable Claude-style lifecycle hooks loaded from hooks.json files.
|
||||
CodexHooks,
|
||||
|
||||
// Experimental
|
||||
/// Removed compatibility flag for the deleted JavaScript REPL feature.
|
||||
JsRepl,
|
||||
/// Enable JavaScript code mode backed by the in-process V8 runtime.
|
||||
CodeMode,
|
||||
/// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`).
|
||||
CodeModeOnly,
|
||||
/// Removed compatibility flag for the deleted JavaScript REPL tool-only mode.
|
||||
JsReplToolsOnly,
|
||||
/// Use the single unified PTY-backed exec tool.
|
||||
UnifiedExec,
|
||||
/// Route shell tool execution through the zsh exec bridge.
|
||||
ShellZshFork,
|
||||
/// Reflow transcript scrollback when the terminal is resized.
|
||||
TerminalResizeReflow,
|
||||
/// Include the freeform apply_patch tool.
|
||||
ApplyPatchFreeform,
|
||||
/// Stream structured progress while apply_patch input is being generated.
|
||||
ApplyPatchStreamingEvents,
|
||||
/// Allow exec tools to request additional permissions while staying sandboxed.
|
||||
ExecPermissionApprovals,
|
||||
/// Expose the built-in request_permissions tool.
|
||||
RequestPermissionsTool,
|
||||
/// Allow the model to request web searches that fetch live content.
|
||||
WebSearchRequest,
|
||||
/// Allow the model to request web searches that fetch cached content.
|
||||
/// Takes precedence over `WebSearchRequest`.
|
||||
WebSearchCached,
|
||||
/// Legacy search-tool feature flag kept for backward compatibility.
|
||||
SearchTool,
|
||||
/// Removed legacy Linux bubblewrap opt-in flag retained as a no-op so old
|
||||
/// wrappers and config can still parse it.
|
||||
UseLinuxSandboxBwrap,
|
||||
/// Use the legacy Landlock Linux sandbox fallback instead of the default
|
||||
/// bubblewrap pipeline.
|
||||
UseLegacyLandlock,
|
||||
/// Allow the model to request approval and propose exec rules.
|
||||
RequestRule,
|
||||
/// Enable Windows sandbox (restricted token) on Windows.
|
||||
WindowsSandbox,
|
||||
/// Use the elevated Windows sandbox pipeline (setup + runner).
|
||||
WindowsSandboxElevated,
|
||||
/// Legacy remote models flag kept for backward compatibility.
|
||||
RemoteModels,
|
||||
/// Experimental shell snapshotting.
|
||||
ShellSnapshot,
|
||||
/// Enable git commit attribution guidance via model instructions.
|
||||
CodexGitCommit,
|
||||
/// Enable runtime metrics snapshots via a manual reader.
|
||||
RuntimeMetrics,
|
||||
/// Persist rollout metadata to a local SQLite database.
|
||||
Sqlite,
|
||||
/// Enable startup memory extraction and file-backed memory consolidation.
|
||||
MemoryTool,
|
||||
/// Enable the Chronicle sidecar for passive screen-context memories.
|
||||
Chronicle,
|
||||
/// Append additional AGENTS.md guidance to user instructions.
|
||||
ChildAgentsMd,
|
||||
/// Compress request bodies (zstd) when sending streaming requests to codex-backend.
|
||||
EnableRequestCompression,
|
||||
/// Enable collab tools.
|
||||
Collab,
|
||||
/// Enable task-path-based multi-agent routing.
|
||||
MultiAgentV2,
|
||||
/// Enable CSV-backed agent job tools.
|
||||
SpawnCsv,
|
||||
/// Enable apps.
|
||||
Apps,
|
||||
/// Enable MCP apps.
|
||||
EnableMcpApps,
|
||||
/// Use the new path for the built-in apps MCP server.
|
||||
AppsMcpPathOverride,
|
||||
/// Enable the tool_search tool for apps.
|
||||
ToolSearch,
|
||||
/// Always defer MCP tools behind tool_search instead of exposing small sets directly.
|
||||
ToolSearchAlwaysDeferMcpTools,
|
||||
/// Expose placeholder tools for unavailable historical tool calls.
|
||||
UnavailableDummyTools,
|
||||
/// Enable discoverable tool suggestions for apps.
|
||||
ToolSuggest,
|
||||
/// Enable plugins.
|
||||
Plugins,
|
||||
/// Enable plugin-bundled lifecycle hooks.
|
||||
PluginHooks,
|
||||
/// Allow the in-app browser pane in desktop apps.
|
||||
///
|
||||
/// Requirements-only gate: this should be set from requirements, not user config.
|
||||
InAppBrowser,
|
||||
/// Allow Browser Use agent integration in desktop apps.
|
||||
///
|
||||
/// Requirements-only gate: this should be set from requirements, not user config.
|
||||
BrowserUse,
|
||||
/// Allow Browser Use integration with external browsers.
|
||||
///
|
||||
/// Requirements-only gate: this should be set from requirements, not user config.
|
||||
BrowserUseExternal,
|
||||
/// Allow Codex Computer Use.
|
||||
///
|
||||
/// Requirements-only gate: this should be set from requirements, not user config.
|
||||
ComputerUse,
|
||||
/// Temporary internal-only flag for PS-backed remote plugin catalog development.
|
||||
RemotePlugin,
|
||||
/// Show the startup prompt for migrating external agent config into Codex.
|
||||
ExternalMigration,
|
||||
/// Allow the model to invoke the built-in image generation tool.
|
||||
ImageGeneration,
|
||||
/// Allow prompting and installing missing MCP dependencies.
|
||||
SkillMcpDependencyInstall,
|
||||
/// Prompt for missing skill env var dependencies.
|
||||
SkillEnvVarDependencyPrompt,
|
||||
/// Steer feature flag - when enabled, Enter submits immediately instead of queuing.
|
||||
/// Kept for config backward compatibility; behavior is always steer-enabled.
|
||||
Steer,
|
||||
/// Allow request_user_input in Default collaboration mode.
|
||||
DefaultModeRequestUserInput,
|
||||
/// Enable automatic review for approval prompts.
|
||||
GuardianApproval,
|
||||
/// Enable persisted thread goals and automatic goal continuation.
|
||||
Goals,
|
||||
/// Enable collaboration modes (Plan, Default).
|
||||
/// Kept for config backward compatibility; behavior is always collaboration-modes-enabled.
|
||||
CollaborationModes,
|
||||
/// Route MCP tool approval prompts through the MCP elicitation request path.
|
||||
ToolCallMcpElicitation,
|
||||
/// Enable personality selection in the TUI.
|
||||
Personality,
|
||||
/// Enable native artifact tools.
|
||||
Artifact,
|
||||
/// Enable Fast mode selection in the TUI and request layer.
|
||||
FastMode,
|
||||
/// Enable experimental realtime voice conversation mode in the TUI.
|
||||
RealtimeConversation,
|
||||
/// Connect app-server to the ChatGPT remote control service.
|
||||
RemoteControl,
|
||||
/// Removed compatibility flag retained as a no-op so old wrappers can
|
||||
/// still pass `--enable image_detail_original`.
|
||||
ImageDetailOriginal,
|
||||
/// Removed compatibility flag. The TUI now always uses the app-server implementation.
|
||||
TuiAppServer,
|
||||
/// Prevent idle system sleep while a turn is actively running.
|
||||
PreventIdleSleep,
|
||||
/// Enable workspace-specific owner nudge copy and prompts in the TUI.
|
||||
WorkspaceOwnerUsageNudge,
|
||||
/// Legacy rollout flag for Responses API WebSocket transport experiments.
|
||||
ResponsesWebsockets,
|
||||
/// Legacy rollout flag for Responses API WebSocket transport v2 experiments.
|
||||
ResponsesWebsocketsV2,
|
||||
/// Enable workspace dependency support.
|
||||
WorkspaceDependencies,
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
pub fn key(self) -> &'static str {
|
||||
self.info().key
|
||||
}
|
||||
|
||||
pub fn stage(self) -> Stage {
|
||||
self.info().stage
|
||||
}
|
||||
|
||||
pub fn default_enabled(self) -> bool {
|
||||
self.info().default_enabled
|
||||
}
|
||||
|
||||
pub(crate) fn info(self) -> &'static FeatureSpec {
|
||||
FEATURES
|
||||
.iter()
|
||||
.find(|spec| spec.id == self)
|
||||
.unwrap_or_else(|| unreachable!("missing FeatureSpec for {self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct LegacyFeatureUsage {
|
||||
pub alias: String,
|
||||
pub feature: Feature,
|
||||
pub summary: String,
|
||||
pub details: Option<String>,
|
||||
}
|
||||
|
||||
/// Holds the effective set of enabled features.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct Features {
|
||||
enabled: BTreeSet<Feature>,
|
||||
legacy_usages: BTreeSet<LegacyFeatureUsage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FeatureOverrides {
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub web_search_request: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct FeatureConfigSource<'a> {
|
||||
pub features: Option<&'a FeaturesToml>,
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
}
|
||||
|
||||
impl FeatureOverrides {
|
||||
fn apply(self, features: &mut Features) {
|
||||
LegacyFeatureToggles {
|
||||
include_apply_patch_tool: self.include_apply_patch_tool,
|
||||
..Default::default()
|
||||
}
|
||||
.apply(features);
|
||||
if let Some(enabled) = self.web_search_request {
|
||||
if enabled {
|
||||
features.enable(Feature::WebSearchRequest);
|
||||
} else {
|
||||
features.disable(Feature::WebSearchRequest);
|
||||
}
|
||||
features.record_legacy_usage("web_search_request", Feature::WebSearchRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Features {
|
||||
/// Starts with built-in defaults.
|
||||
pub fn with_defaults() -> Self {
|
||||
let mut set = BTreeSet::new();
|
||||
for spec in FEATURES {
|
||||
if spec.default_enabled {
|
||||
set.insert(spec.id);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
enabled: set,
|
||||
legacy_usages: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self, f: Feature) -> bool {
|
||||
self.enabled.contains(&f)
|
||||
}
|
||||
|
||||
pub fn apps_enabled_for_auth(&self, has_chatgpt_auth: bool) -> bool {
|
||||
self.enabled(Feature::Apps) && has_chatgpt_auth
|
||||
}
|
||||
|
||||
pub fn use_legacy_landlock(&self) -> bool {
|
||||
self.enabled(Feature::UseLegacyLandlock)
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, f: Feature) -> &mut Self {
|
||||
self.enabled.insert(f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disable(&mut self, f: Feature) -> &mut Self {
|
||||
self.enabled.remove(&f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, f: Feature, enabled: bool) -> &mut Self {
|
||||
if enabled {
|
||||
self.enable(f)
|
||||
} else {
|
||||
self.disable(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_legacy_usage_force(&mut self, alias: &str, feature: Feature) {
|
||||
let (summary, details) = legacy_usage_notice(alias, feature);
|
||||
self.legacy_usages.insert(LegacyFeatureUsage {
|
||||
alias: alias.to_string(),
|
||||
feature,
|
||||
summary,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn record_legacy_usage(&mut self, alias: &str, feature: Feature) {
|
||||
if alias == feature.key() {
|
||||
return;
|
||||
}
|
||||
self.record_legacy_usage_force(alias, feature);
|
||||
}
|
||||
|
||||
pub fn legacy_feature_usages(&self) -> impl Iterator<Item = &LegacyFeatureUsage> + '_ {
|
||||
self.legacy_usages.iter()
|
||||
}
|
||||
|
||||
pub fn emit_metrics(&self, otel: &SessionTelemetry) {
|
||||
for feature in FEATURES {
|
||||
if matches!(feature.stage, Stage::Removed) {
|
||||
continue;
|
||||
}
|
||||
if self.enabled(feature.id) != feature.default_enabled {
|
||||
otel.counter(
|
||||
"codex.feature.state",
|
||||
/*inc*/ 1,
|
||||
&[
|
||||
("feature", feature.key),
|
||||
("value", &self.enabled(feature.id).to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a table of key -> bool toggles (e.g. from TOML).
|
||||
pub fn apply_map(&mut self, entries: &BTreeMap<String, bool>) {
|
||||
for (key, enabled) in entries {
|
||||
match key.as_str() {
|
||||
"web_search_request" => {
|
||||
self.record_legacy_usage_force(
|
||||
"features.web_search_request",
|
||||
Feature::WebSearchRequest,
|
||||
);
|
||||
}
|
||||
"web_search_cached" => {
|
||||
self.record_legacy_usage_force(
|
||||
"features.web_search_cached",
|
||||
Feature::WebSearchCached,
|
||||
);
|
||||
}
|
||||
"tui_app_server"
|
||||
| "undo"
|
||||
| "js_repl"
|
||||
| "js_repl_tools_only"
|
||||
| "image_detail_original" => {
|
||||
continue;
|
||||
}
|
||||
"use_legacy_landlock" => {
|
||||
self.record_legacy_usage_force(
|
||||
"features.use_legacy_landlock",
|
||||
Feature::UseLegacyLandlock,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match feature_for_key(key) {
|
||||
Some(feature) => {
|
||||
if matches!(feature, Feature::TuiAppServer) {
|
||||
continue;
|
||||
}
|
||||
if key != feature.key() {
|
||||
self.record_legacy_usage(key.as_str(), feature);
|
||||
}
|
||||
self.set_enabled(feature, *enabled);
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("unknown feature key in config: {key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_sources(
|
||||
base: FeatureConfigSource<'_>,
|
||||
profile: FeatureConfigSource<'_>,
|
||||
overrides: FeatureOverrides,
|
||||
) -> Self {
|
||||
let mut features = Features::with_defaults();
|
||||
|
||||
for source in [base, profile] {
|
||||
LegacyFeatureToggles {
|
||||
include_apply_patch_tool: source.include_apply_patch_tool,
|
||||
experimental_use_freeform_apply_patch: source.experimental_use_freeform_apply_patch,
|
||||
experimental_use_unified_exec_tool: source.experimental_use_unified_exec_tool,
|
||||
}
|
||||
.apply(&mut features);
|
||||
|
||||
if let Some(feature_entries) = source.features {
|
||||
features.apply_toml(feature_entries);
|
||||
}
|
||||
}
|
||||
|
||||
overrides.apply(&mut features);
|
||||
features.normalize_dependencies();
|
||||
|
||||
features
|
||||
}
|
||||
|
||||
pub fn enabled_features(&self) -> Vec<Feature> {
|
||||
self.enabled.iter().copied().collect()
|
||||
}
|
||||
|
||||
pub fn normalize_dependencies(&mut self) {
|
||||
if self.enabled(Feature::SpawnCsv) && !self.enabled(Feature::Collab) {
|
||||
self.enable(Feature::Collab);
|
||||
}
|
||||
if self.enabled(Feature::CodeModeOnly) && !self.enabled(Feature::CodeMode) {
|
||||
self.enable(Feature::CodeMode);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_toml(&mut self, features: &FeaturesToml) {
|
||||
let entries = features.entries();
|
||||
self.apply_map(&entries);
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_usage_notice(alias: &str, feature: Feature) -> (String, Option<String>) {
|
||||
let canonical = feature.key();
|
||||
match feature {
|
||||
Feature::WebSearchRequest | Feature::WebSearchCached => {
|
||||
let label = match alias {
|
||||
"web_search" => "[features].web_search",
|
||||
"features.web_search_request" | "web_search_request" => {
|
||||
"[features].web_search_request"
|
||||
}
|
||||
"features.web_search_cached" | "web_search_cached" => {
|
||||
"[features].web_search_cached"
|
||||
}
|
||||
_ => alias,
|
||||
};
|
||||
let summary =
|
||||
format!("`{label}` is deprecated because web search is enabled by default.");
|
||||
(summary, Some(web_search_details().to_string()))
|
||||
}
|
||||
Feature::UseLegacyLandlock => {
|
||||
let label = match alias {
|
||||
"features.use_legacy_landlock" | "use_legacy_landlock" => {
|
||||
"[features].use_legacy_landlock"
|
||||
}
|
||||
_ => alias,
|
||||
};
|
||||
let summary = format!("`{label}` is deprecated and will be removed soon.");
|
||||
let details =
|
||||
"Remove this setting to stop opting into the legacy Linux sandbox behavior."
|
||||
.to_string();
|
||||
(summary, Some(details))
|
||||
}
|
||||
_ => {
|
||||
let label = if alias.contains('.') || alias.starts_with('[') {
|
||||
alias.to_string()
|
||||
} else {
|
||||
format!("[features].{alias}")
|
||||
};
|
||||
let summary = format!("`{label}` is deprecated. Use `[features].{canonical}` instead.");
|
||||
let details = if alias == canonical {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"Enable it with `--enable {canonical}` or `[features].{canonical}` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details."
|
||||
))
|
||||
};
|
||||
(summary, details)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn web_search_details() -> &'static str {
|
||||
"Set `web_search` to `\"live\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it."
|
||||
}
|
||||
|
||||
/// Keys accepted in `[features]` tables.
|
||||
pub fn feature_for_key(key: &str) -> Option<Feature> {
|
||||
for spec in FEATURES {
|
||||
if spec.key == key {
|
||||
return Some(spec.id);
|
||||
}
|
||||
}
|
||||
crate::legacy::feature_for_key(key)
|
||||
}
|
||||
|
||||
pub fn canonical_feature_for_key(key: &str) -> Option<Feature> {
|
||||
FEATURES
|
||||
.iter()
|
||||
.find(|spec| spec.key == key)
|
||||
.map(|spec| spec.id)
|
||||
}
|
||||
|
||||
/// Returns `true` if the provided string matches a known feature toggle key.
|
||||
pub fn is_known_feature_key(key: &str) -> bool {
|
||||
feature_for_key(key).is_some()
|
||||
}
|
||||
|
||||
/// Deserializable features table for TOML.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct FeaturesToml {
|
||||
#[serde(flatten)]
|
||||
entries: BTreeMap<String, FeatureToml>,
|
||||
}
|
||||
|
||||
impl FeaturesToml {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_entries(entries: BTreeMap<String, FeatureToml>) -> Self {
|
||||
Self { entries }
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> BTreeMap<String, bool> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter_map(|(key, feature)| {
|
||||
feature_enabled_in_config(key, feature).map(|enabled| (key.clone(), enabled))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<&FeatureToml> {
|
||||
self.entries.get(key)
|
||||
}
|
||||
|
||||
pub fn typed_config<T>(
|
||||
&self,
|
||||
key: &str,
|
||||
) -> Option<Result<FeatureConfigTable<T>, toml::de::Error>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
self.get(key).and_then(FeatureToml::typed_config::<T>)
|
||||
}
|
||||
|
||||
pub fn hint(&self, key: &str) -> Option<&str> {
|
||||
self.get(key).and_then(FeatureToml::hint)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: String, feature: FeatureToml) {
|
||||
self.entries.insert(key, feature);
|
||||
}
|
||||
|
||||
pub fn materialize_resolved_enabled(&mut self, features: &Features) {
|
||||
for key in crate::legacy::legacy_feature_keys() {
|
||||
self.entries.remove(key);
|
||||
}
|
||||
for spec in FEATURES {
|
||||
let enabled = features.enabled(spec.id);
|
||||
materialize_resolved_feature_enabled(&mut self.entries, spec.key, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn materialize_resolved_config<T>(
|
||||
&mut self,
|
||||
feature: Feature,
|
||||
enabled: bool,
|
||||
extra: T,
|
||||
) -> Result<(), toml::ser::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let key = feature.key().to_string();
|
||||
let hint = self.hint(&key).map(ToOwned::to_owned);
|
||||
let feature = FeatureToml::Config(
|
||||
FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(enabled),
|
||||
hint,
|
||||
},
|
||||
extra,
|
||||
}
|
||||
.into_raw()?,
|
||||
);
|
||||
self.insert(key, feature);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize_resolved_feature_enabled(
|
||||
features: &mut BTreeMap<String, FeatureToml>,
|
||||
key: &str,
|
||||
enabled: bool,
|
||||
) {
|
||||
match features.get_mut(key) {
|
||||
Some(feature) => feature.set_enabled(enabled),
|
||||
None => {
|
||||
features.insert(key.to_string(), FeatureToml::Enabled(enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn feature_enabled_in_config(key: &str, feature: &FeatureToml) -> Option<bool> {
|
||||
match feature {
|
||||
FeatureToml::Enabled(enabled) => Some(*enabled),
|
||||
FeatureToml::Config(config) => config.common.enabled.or_else(|| {
|
||||
if key == Feature::AppsMcpPathOverride.key() {
|
||||
config.extra.contains_key("path").then_some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, bool>> for FeaturesToml {
|
||||
fn from(entries: BTreeMap<String, bool>) -> Self {
|
||||
Self {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(|(key, enabled)| (key, FeatureToml::Enabled(enabled)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type RawFeatureConfigExtras = BTreeMap<String, TomlValue>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CommonFeatureConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NoExtraFeatureConfigToml {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
pub struct FeatureConfigTable<T = RawFeatureConfigExtras> {
|
||||
#[serde(flatten)]
|
||||
pub common: CommonFeatureConfigToml,
|
||||
#[serde(flatten)]
|
||||
pub extra: T,
|
||||
}
|
||||
|
||||
// To be used for feature entries under `[features]` that can be either a bare
|
||||
// boolean toggle or a table with shared fields plus feature-specific extras.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum FeatureToml<T = RawFeatureConfigExtras> {
|
||||
Enabled(bool),
|
||||
Config(FeatureConfigTable<T>),
|
||||
}
|
||||
|
||||
impl<T> FeatureToml<T> {
|
||||
pub fn enabled(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Enabled(enabled) => Some(*enabled),
|
||||
Self::Config(config) => config.common.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hint(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Enabled(_) => None,
|
||||
Self::Config(config) => config.common.hint.as_deref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
match self {
|
||||
Self::Enabled(value) => *value = enabled,
|
||||
Self::Config(config) => config.common.enabled = Some(enabled),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureToml<RawFeatureConfigExtras> {
|
||||
pub fn typed_config<T>(&self) -> Option<Result<FeatureConfigTable<T>, toml::de::Error>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
match self {
|
||||
Self::Enabled(_) => None,
|
||||
Self::Config(config) => Some(config.clone().typed()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FeatureConfigTable<T> {
|
||||
pub fn into_raw(self) -> Result<FeatureConfigTable<RawFeatureConfigExtras>, toml::ser::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let extra = match TomlValue::try_from(self.extra)? {
|
||||
TomlValue::Table(table) => table.into_iter().collect(),
|
||||
other => {
|
||||
unreachable!("feature config extras must serialize as a TOML table: {other:?}")
|
||||
}
|
||||
};
|
||||
Ok(FeatureConfigTable {
|
||||
common: self.common,
|
||||
extra,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureConfigTable<RawFeatureConfigExtras> {
|
||||
pub fn typed<T>(self) -> Result<FeatureConfigTable<T>, toml::de::Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
Ok(FeatureConfigTable {
|
||||
common: self.common,
|
||||
extra: TomlValue::Table(self.extra.into_iter().collect()).try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unstable_features_warning_event(
|
||||
effective_features: Option<&Table>,
|
||||
suppress_unstable_features_warning: bool,
|
||||
features: &Features,
|
||||
config_path: &str,
|
||||
) -> Option<Event> {
|
||||
if suppress_unstable_features_warning {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut under_development_feature_keys = Vec::new();
|
||||
if let Some(table) = effective_features {
|
||||
for (key, value) in table {
|
||||
if configured_feature_enabled_in_effective_table(key, value) != Some(true) {
|
||||
continue;
|
||||
}
|
||||
let Some(spec) = FEATURES.iter().find(|spec| spec.key == key.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
if !features.enabled(spec.id) {
|
||||
continue;
|
||||
}
|
||||
if matches!(spec.stage, Stage::UnderDevelopment) {
|
||||
under_development_feature_keys.push(spec.key.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if under_development_feature_keys.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let under_development_feature_keys = under_development_feature_keys.join(", ");
|
||||
let message = format!(
|
||||
"Under-development features enabled: {under_development_feature_keys}. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in {config_path}."
|
||||
);
|
||||
Some(Event {
|
||||
id: String::new(),
|
||||
msg: EventMsg::Warning(WarningEvent { message }),
|
||||
})
|
||||
}
|
||||
|
||||
fn configured_feature_enabled_in_effective_table(key: &str, value: &TomlValue) -> Option<bool> {
|
||||
let feature: FeatureToml = value.clone().try_into().ok()?;
|
||||
feature_enabled_in_config(key, &feature)
|
||||
}
|
||||
464
codex-rs/features/src/registry.rs
Normal file
464
codex-rs/features/src/registry.rs
Normal file
@@ -0,0 +1,464 @@
|
||||
use crate::machinery::Feature;
|
||||
use crate::machinery::Stage;
|
||||
|
||||
/// Single, easy-to-read registry of all feature definitions.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FeatureSpec {
|
||||
pub id: Feature,
|
||||
pub key: &'static str,
|
||||
pub stage: Stage,
|
||||
pub default_enabled: bool,
|
||||
}
|
||||
|
||||
pub const FEATURES: &[FeatureSpec] = &[
|
||||
// Stable features.
|
||||
FeatureSpec {
|
||||
id: Feature::GhostCommit,
|
||||
key: "undo",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ShellTool,
|
||||
key: "shell_tool",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UnifiedExec,
|
||||
key: "unified_exec",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: !cfg!(windows),
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ShellZshFork,
|
||||
key: "shell_zsh_fork",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ShellSnapshot,
|
||||
key: "shell_snapshot",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::JsRepl,
|
||||
key: "js_repl",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CodeMode,
|
||||
key: "code_mode",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CodeModeOnly,
|
||||
key: "code_mode_only",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::JsReplToolsOnly,
|
||||
key: "js_repl_tools_only",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::TerminalResizeReflow,
|
||||
key: "terminal_resize_reflow",
|
||||
stage: Stage::Experimental {
|
||||
name: "Terminal resize reflow",
|
||||
menu_description: "Rebuild Codex-owned transcript scrollback when the terminal width changes.",
|
||||
announcement: "",
|
||||
},
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WebSearchRequest,
|
||||
key: "web_search_request",
|
||||
stage: Stage::Deprecated,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WebSearchCached,
|
||||
key: "web_search_cached",
|
||||
stage: Stage::Deprecated,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SearchTool,
|
||||
key: "search_tool",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
// Experimental program. Rendered in the `/experimental` menu for users.
|
||||
FeatureSpec {
|
||||
id: Feature::CodexGitCommit,
|
||||
key: "codex_git_commit",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RuntimeMetrics,
|
||||
key: "runtime_metrics",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Sqlite,
|
||||
key: "sqlite",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::MemoryTool,
|
||||
key: "memories",
|
||||
stage: Stage::Experimental {
|
||||
name: "Memories",
|
||||
menu_description: "Allow Codex to create new memories from conversations and bring relevant memories into new conversations.",
|
||||
announcement: "NEW: Codex can now generate and uses memories. Try is now with `/memories`",
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Chronicle,
|
||||
key: "chronicle",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ChildAgentsMd,
|
||||
key: "child_agents_md",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ApplyPatchFreeform,
|
||||
key: "apply_patch_freeform",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ApplyPatchStreamingEvents,
|
||||
key: "apply_patch_streaming_events",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ExecPermissionApprovals,
|
||||
key: "exec_permission_approvals",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CodexHooks,
|
||||
key: "hooks",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RequestPermissionsTool,
|
||||
key: "request_permissions_tool",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UseLinuxSandboxBwrap,
|
||||
key: "use_linux_sandbox_bwrap",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UseLegacyLandlock,
|
||||
key: "use_legacy_landlock",
|
||||
stage: Stage::Deprecated,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RequestRule,
|
||||
key: "request_rule",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WindowsSandbox,
|
||||
key: "experimental_windows_sandbox",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WindowsSandboxElevated,
|
||||
key: "elevated_windows_sandbox",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RemoteModels,
|
||||
key: "remote_models",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::EnableRequestCompression,
|
||||
key: "enable_request_compression",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Collab,
|
||||
key: "multi_agent",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::MultiAgentV2,
|
||||
key: "multi_agent_v2",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SpawnCsv,
|
||||
key: "enable_fanout",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Apps,
|
||||
key: "apps",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::EnableMcpApps,
|
||||
key: "enable_mcp_apps",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::AppsMcpPathOverride,
|
||||
key: "apps_mcp_path_override",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ToolSearch,
|
||||
key: "tool_search",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ToolSearchAlwaysDeferMcpTools,
|
||||
key: "tool_search_always_defer_mcp_tools",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UnavailableDummyTools,
|
||||
key: "unavailable_dummy_tools",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ToolSuggest,
|
||||
key: "tool_suggest",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Plugins,
|
||||
key: "plugins",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::PluginHooks,
|
||||
key: "plugin_hooks",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::InAppBrowser,
|
||||
key: "in_app_browser",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::BrowserUse,
|
||||
key: "browser_use",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::BrowserUseExternal,
|
||||
key: "browser_use_external",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ComputerUse,
|
||||
key: "computer_use",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RemotePlugin,
|
||||
key: "remote_plugin",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ExternalMigration,
|
||||
key: "external_migration",
|
||||
stage: Stage::Experimental {
|
||||
name: "External migration",
|
||||
menu_description: "Show a startup prompt when Codex detects migratable external agent config for this machine or project.",
|
||||
announcement: "",
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ImageGeneration,
|
||||
key: "image_generation",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SkillMcpDependencyInstall,
|
||||
key: "skill_mcp_dependency_install",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SkillEnvVarDependencyPrompt,
|
||||
key: "skill_env_var_dependency_prompt",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Steer,
|
||||
key: "steer",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::DefaultModeRequestUserInput,
|
||||
key: "default_mode_request_user_input",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::GuardianApproval,
|
||||
key: "guardian_approval",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Goals,
|
||||
key: "goals",
|
||||
stage: Stage::Experimental {
|
||||
name: "Goals",
|
||||
menu_description: "Set a persistent goal Codex can continue over time",
|
||||
announcement: "",
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CollaborationModes,
|
||||
key: "collaboration_modes",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ToolCallMcpElicitation,
|
||||
key: "tool_call_mcp_elicitation",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Personality,
|
||||
key: "personality",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Artifact,
|
||||
key: "artifact",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::FastMode,
|
||||
key: "fast_mode",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RealtimeConversation,
|
||||
key: "realtime_conversation",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RemoteControl,
|
||||
key: "remote_control",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ImageDetailOriginal,
|
||||
key: "image_detail_original",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::TuiAppServer,
|
||||
key: "tui_app_server",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::PreventIdleSleep,
|
||||
key: "prevent_idle_sleep",
|
||||
stage: if cfg!(any(
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "windows"
|
||||
)) {
|
||||
Stage::Experimental {
|
||||
name: "Prevent sleep while running",
|
||||
menu_description: "Keep your computer awake while Codex is running a thread.",
|
||||
announcement: "NEW: Prevent sleep while running is now available in /experimental.",
|
||||
}
|
||||
} else {
|
||||
Stage::UnderDevelopment
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WorkspaceOwnerUsageNudge,
|
||||
key: "workspace_owner_usage_nudge",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ResponsesWebsockets,
|
||||
key: "responses_websockets",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ResponsesWebsocketsV2,
|
||||
key: "responses_websockets_v2",
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WorkspaceDependencies,
|
||||
key: "workspace_dependencies",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::CommonFeatureConfigToml;
|
||||
use crate::Feature;
|
||||
use crate::FeatureConfigSource;
|
||||
use crate::FeatureConfigTable;
|
||||
use crate::FeatureOverrides;
|
||||
use crate::FeatureToml;
|
||||
use crate::Features;
|
||||
@@ -312,19 +314,9 @@ fn apps_require_feature_flag_and_chatgpt_auth() {
|
||||
|
||||
#[test]
|
||||
fn from_sources_applies_base_profile_and_overrides() {
|
||||
let mut base_entries = BTreeMap::new();
|
||||
base_entries.insert("plugins".to_string(), true);
|
||||
let base_features = FeaturesToml {
|
||||
entries: base_entries,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut profile_entries = BTreeMap::new();
|
||||
profile_entries.insert("code_mode_only".to_string(), true);
|
||||
let profile_features = FeaturesToml {
|
||||
entries: profile_entries,
|
||||
..Default::default()
|
||||
};
|
||||
let base_features = FeaturesToml::from(BTreeMap::from([("plugins".to_string(), true)]));
|
||||
let profile_features =
|
||||
FeaturesToml::from(BTreeMap::from([("code_mode_only".to_string(), true)]));
|
||||
|
||||
let features = Features::from_sources(
|
||||
FeatureConfigSource {
|
||||
@@ -416,7 +408,10 @@ multi_agent_v2 = true
|
||||
features.entries(),
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(features.multi_agent_v2, Some(FeatureToml::Enabled(true)));
|
||||
assert_eq!(
|
||||
features.get("multi_agent_v2"),
|
||||
Some(&FeatureToml::Enabled(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -425,6 +420,7 @@ fn multi_agent_v2_feature_config_deserializes_table() {
|
||||
r#"
|
||||
[multi_agent_v2]
|
||||
enabled = true
|
||||
hint = "Use this feature carefully."
|
||||
max_concurrent_threads_per_session = 4
|
||||
min_wait_timeout_ms = 2500
|
||||
usage_hint_enabled = false
|
||||
@@ -441,9 +437,49 @@ hide_spawn_agent_metadata = true
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: Some(true),
|
||||
features.get("multi_agent_v2"),
|
||||
Some(&FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(true),
|
||||
hint: Some("Use this feature carefully.".to_string()),
|
||||
},
|
||||
extra: BTreeMap::from([
|
||||
(
|
||||
"hide_spawn_agent_metadata".to_string(),
|
||||
TomlValue::Boolean(true),
|
||||
),
|
||||
(
|
||||
"max_concurrent_threads_per_session".to_string(),
|
||||
TomlValue::Integer(4),
|
||||
),
|
||||
("min_wait_timeout_ms".to_string(), TomlValue::Integer(2500)),
|
||||
(
|
||||
"root_agent_usage_hint_text".to_string(),
|
||||
TomlValue::String("Root guidance.".to_string()),
|
||||
),
|
||||
(
|
||||
"subagent_usage_hint_text".to_string(),
|
||||
TomlValue::String("Subagent guidance.".to_string()),
|
||||
),
|
||||
("usage_hint_enabled".to_string(), TomlValue::Boolean(false),),
|
||||
(
|
||||
"usage_hint_text".to_string(),
|
||||
TomlValue::String("Custom delegation guidance.".to_string()),
|
||||
),
|
||||
]),
|
||||
}))
|
||||
);
|
||||
let typed = features
|
||||
.typed_config::<crate::MultiAgentV2ConfigToml>("multi_agent_v2")
|
||||
.expect("table feature should expose a typed config")
|
||||
.expect("multi_agent_v2 config should deserialize");
|
||||
assert_eq!(
|
||||
typed.common.hint.as_deref(),
|
||||
Some("Use this feature carefully.")
|
||||
);
|
||||
assert_eq!(
|
||||
typed.extra,
|
||||
crate::MultiAgentV2ConfigToml {
|
||||
max_concurrent_threads_per_session: Some(4),
|
||||
min_wait_timeout_ms: Some(2500),
|
||||
usage_hint_enabled: Some(false),
|
||||
@@ -451,7 +487,7 @@ hide_spawn_agent_metadata = true
|
||||
root_agent_usage_hint_text: Some("Root guidance.".to_string()),
|
||||
subagent_usage_hint_text: Some("Subagent guidance.".to_string()),
|
||||
hide_spawn_agent_metadata: Some(true),
|
||||
}))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -476,20 +512,30 @@ usage_hint_enabled = false
|
||||
assert_eq!(features.enabled(Feature::MultiAgentV2), false);
|
||||
assert_eq!(features_toml.entries(), BTreeMap::new());
|
||||
assert_eq!(
|
||||
features_toml.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: None,
|
||||
max_concurrent_threads_per_session: None,
|
||||
min_wait_timeout_ms: None,
|
||||
usage_hint_enabled: Some(false),
|
||||
usage_hint_text: None,
|
||||
root_agent_usage_hint_text: None,
|
||||
subagent_usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: None,
|
||||
features_toml.get("multi_agent_v2"),
|
||||
Some(&crate::FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml::default(),
|
||||
extra: BTreeMap::from([("usage_hint_enabled".to_string(), TomlValue::Boolean(false),)]),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apps_mcp_path_override_path_still_enables_feature() {
|
||||
let features_toml: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[apps_mcp_path_override]
|
||||
path = "/custom/mcp"
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features_toml.entries(),
|
||||
BTreeMap::from([("apps_mcp_path_override".to_string(), true)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config() {
|
||||
let mut features = Features::with_defaults();
|
||||
@@ -497,15 +543,25 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
||||
features.enable(Feature::MultiAgentV2);
|
||||
features.disable(Feature::ToolSearch);
|
||||
|
||||
let mut features_toml = FeaturesToml {
|
||||
multi_agent_v2: Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: Some(false),
|
||||
min_wait_timeout_ms: Some(2500),
|
||||
..Default::default()
|
||||
})),
|
||||
entries: BTreeMap::from([("include_apply_patch_tool".to_string(), true)]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut features_toml = FeaturesToml::from_entries(BTreeMap::from([
|
||||
(
|
||||
"multi_agent_v2".to_string(),
|
||||
FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(false),
|
||||
hint: Some("Preserve me.".to_string()),
|
||||
},
|
||||
extra: BTreeMap::from([(
|
||||
"min_wait_timeout_ms".to_string(),
|
||||
TomlValue::Integer(2500),
|
||||
)]),
|
||||
}),
|
||||
),
|
||||
(
|
||||
"include_apply_patch_tool".to_string(),
|
||||
FeatureToml::Enabled(true),
|
||||
),
|
||||
]));
|
||||
|
||||
features_toml.materialize_resolved_enabled(&features);
|
||||
|
||||
@@ -520,11 +576,13 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
features_toml.multi_agent_v2,
|
||||
Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: Some(true),
|
||||
min_wait_timeout_ms: Some(2500),
|
||||
..Default::default()
|
||||
features_toml.get("multi_agent_v2"),
|
||||
Some(&FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(true),
|
||||
hint: Some("Preserve me.".to_string()),
|
||||
},
|
||||
extra: BTreeMap::from([("min_wait_timeout_ms".to_string(), TomlValue::Integer(2500),)]),
|
||||
}))
|
||||
);
|
||||
let replayed = Features::from_sources(
|
||||
@@ -538,10 +596,60 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
||||
assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_feature_config_deserializes_hint_without_per_feature_wiring() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[some_feature]
|
||||
enabled = true
|
||||
hint = "Remember {{ features.multi_agent_v2.max_concurrent_threads_per_session }}"
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("some_feature".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.get("some_feature"),
|
||||
Some(&FeatureToml::Config(FeatureConfigTable {
|
||||
common: CommonFeatureConfigToml {
|
||||
enabled: Some(true),
|
||||
hint: Some(
|
||||
"Remember {{ features.multi_agent_v2.max_concurrent_threads_per_session }}"
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
extra: BTreeMap::new(),
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
features.hint("some_feature"),
|
||||
Some("Remember {{ features.multi_agent_v2.max_concurrent_threads_per_session }}")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unstable_warning_event_only_mentions_enabled_under_development_features() {
|
||||
let mut configured_features = Table::new();
|
||||
configured_features.insert("child_agents_md".to_string(), TomlValue::Boolean(true));
|
||||
configured_features.insert(
|
||||
"child_agents_md".to_string(),
|
||||
TomlValue::Table(Table::from_iter([
|
||||
("enabled".to_string(), TomlValue::Boolean(true)),
|
||||
(
|
||||
"hint".to_string(),
|
||||
TomlValue::String("Use child_agents_md".to_string()),
|
||||
),
|
||||
])),
|
||||
);
|
||||
configured_features.insert(
|
||||
"code_mode".to_string(),
|
||||
TomlValue::Table(Table::from_iter([(
|
||||
"enabled".to_string(),
|
||||
TomlValue::Boolean(false),
|
||||
)])),
|
||||
);
|
||||
configured_features.insert("personality".to_string(), TomlValue::Boolean(true));
|
||||
configured_features.insert("unknown".to_string(), TomlValue::Boolean(true));
|
||||
|
||||
@@ -560,6 +668,8 @@ fn unstable_warning_event_only_mentions_enabled_under_development_features() {
|
||||
panic!("expected warning event");
|
||||
};
|
||||
assert!(message.contains("child_agents_md"));
|
||||
assert!(!message.contains("code_mode"));
|
||||
assert!(!message.contains("personality"));
|
||||
assert!(!message.contains("unknown"));
|
||||
assert!(message.contains("/tmp/config.toml"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user