mirror of
https://github.com/openai/codex.git
synced 2026-05-10 22:32:36 +00:00
Compare commits
2 Commits
starr/wind
...
jif/featur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e337e9b216 | ||
|
|
d2aa463545 |
@@ -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",
|
||||
@@ -208,6 +213,9 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_features::CommonFeatureConfigToml;
|
||||
use codex_features::FeatureConfigTable;
|
||||
use codex_features::FeatureToml;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -253,20 +261,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);
|
||||
}
|
||||
|
||||
136
codex-rs/core/src/session/feature_hints.rs
Normal file
136
codex-rs/core/src/session/feature_hints.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use codex_features::FEATURES;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_utils_template::Template;
|
||||
use codex_utils_template::TemplateError;
|
||||
use codex_utils_template::TemplateRenderError;
|
||||
use toml::Value as TomlValue;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::context_manager::updates::build_developer_update_item;
|
||||
|
||||
use super::session::SessionConfiguration;
|
||||
use super::turn_context::TurnContext;
|
||||
|
||||
pub(super) fn render_feature_hint_messages(
|
||||
session_configuration: &SessionConfiguration,
|
||||
turn_context: &TurnContext,
|
||||
) -> Vec<ResponseItem> {
|
||||
let lockfile = match session_configuration.to_config_lockfile_toml() {
|
||||
Ok(lockfile) => lockfile,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to build config lock for feature hint rendering");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(features_toml) = lockfile.config.features.as_ref() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let template_variables = match build_template_variables(&lockfile.config) {
|
||||
Ok(variables) => variables,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to serialize resolved config for feature hint rendering");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
FEATURES
|
||||
.iter()
|
||||
.filter(|spec| turn_context.features.enabled(spec.id))
|
||||
.filter_map(|spec| {
|
||||
let hint = features_toml.hint(spec.key)?;
|
||||
let rendered = match render_feature_hint(hint, &template_variables) {
|
||||
Ok(rendered) => rendered,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
feature = spec.key,
|
||||
error = %err,
|
||||
"failed to render feature hint"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if rendered.is_empty() {
|
||||
return None;
|
||||
}
|
||||
build_developer_update_item(vec![rendered])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_feature_hint(
|
||||
source: &str,
|
||||
template_variables: &BTreeMap<String, String>,
|
||||
) -> Result<String, TemplateError> {
|
||||
let template = Template::parse(source)?;
|
||||
let variables = template
|
||||
.placeholders()
|
||||
.map(|placeholder| {
|
||||
template_variables
|
||||
.get(placeholder)
|
||||
.map(|value| (placeholder, value.as_str()))
|
||||
.ok_or_else(|| TemplateRenderError::MissingValue {
|
||||
name: placeholder.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
template.render(variables).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn build_template_variables(
|
||||
config: &codex_config::config_toml::ConfigToml,
|
||||
) -> Result<BTreeMap<String, String>, toml::ser::Error> {
|
||||
let value = TomlValue::try_from(config)?;
|
||||
let mut variables = BTreeMap::new();
|
||||
flatten_toml_value(/*prefix*/ None, &value, &mut variables);
|
||||
Ok(variables)
|
||||
}
|
||||
|
||||
fn flatten_toml_value(
|
||||
prefix: Option<&str>,
|
||||
value: &TomlValue,
|
||||
variables: &mut BTreeMap<String, String>,
|
||||
) {
|
||||
match value {
|
||||
TomlValue::Table(table) => {
|
||||
for (key, value) in table {
|
||||
let key = match prefix {
|
||||
Some(prefix) => format!("{prefix}.{key}"),
|
||||
None => key.clone(),
|
||||
};
|
||||
flatten_toml_value(Some(&key), value, variables);
|
||||
}
|
||||
}
|
||||
TomlValue::String(value) => insert_template_variable(prefix, value.clone(), variables),
|
||||
TomlValue::Integer(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Float(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Boolean(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Datetime(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Array(value) => {
|
||||
insert_template_variable(
|
||||
prefix,
|
||||
TomlValue::Array(value.clone()).to_string(),
|
||||
variables,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_template_variable(
|
||||
key: Option<&str>,
|
||||
value: String,
|
||||
variables: &mut BTreeMap<String, String>,
|
||||
) {
|
||||
if let Some(key) = key {
|
||||
variables.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,7 @@ use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
|
||||
mod config_lock;
|
||||
mod feature_hints;
|
||||
mod handlers;
|
||||
mod mcp;
|
||||
mod multi_agents;
|
||||
@@ -2540,6 +2541,7 @@ impl Session {
|
||||
collaboration_mode,
|
||||
base_instructions,
|
||||
session_source,
|
||||
session_configuration,
|
||||
) = {
|
||||
let state = self.state.lock().await;
|
||||
(
|
||||
@@ -2548,6 +2550,7 @@ impl Session {
|
||||
state.session_configuration.collaboration_mode.clone(),
|
||||
state.session_configuration.base_instructions.clone(),
|
||||
state.session_configuration.session_source.clone(),
|
||||
state.session_configuration.clone(),
|
||||
)
|
||||
};
|
||||
if let Some(model_switch_message) =
|
||||
@@ -2702,13 +2705,16 @@ impl Session {
|
||||
|
||||
let multi_agent_v2_usage_hint_text =
|
||||
multi_agents::usage_hint_text(turn_context, &session_source);
|
||||
let feature_hint_messages =
|
||||
feature_hints::render_feature_hint_messages(&session_configuration, turn_context);
|
||||
|
||||
let mut items = Vec::with_capacity(4);
|
||||
let mut items = Vec::with_capacity(4 + feature_hint_messages.len());
|
||||
if let Some(developer_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(developer_sections)
|
||||
{
|
||||
items.push(developer_message);
|
||||
}
|
||||
items.extend(feature_hint_messages);
|
||||
if let Some(usage_hint_text) = multi_agent_v2_usage_hint_text
|
||||
&& let Some(usage_hint_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![
|
||||
|
||||
@@ -5688,6 +5688,47 @@ async fn build_initial_context_omits_multi_agent_v2_usage_hints_when_feature_dis
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_renders_feature_hint_developer_message_with_resolved_config_values()
|
||||
{
|
||||
let (session, turn_context, _rx_event) = make_session_and_context_with_auth_and_config_and_rx(
|
||||
CodexAuth::from_api_key("Test API Key"),
|
||||
Vec::new(),
|
||||
|config| {
|
||||
let _ = config.features.enable(Feature::MemoryTool);
|
||||
config.agent_max_threads = Some(7);
|
||||
|
||||
let user_config_path = match config.config_layer_stack.get_user_layer() {
|
||||
Some(layer) => match &layer.name {
|
||||
codex_config::ConfigLayerSource::User { file } => file.clone(),
|
||||
other => panic!("expected user config layer, got {other:?}"),
|
||||
},
|
||||
None => panic!("expected user config layer"),
|
||||
};
|
||||
config.config_layer_stack = config.config_layer_stack.with_user_config(
|
||||
&user_config_path,
|
||||
toml::toml! {
|
||||
[features.memories]
|
||||
enabled = true
|
||||
hint = "This is a super hint {{ agents.max_threads }}"
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_context = session.build_initial_context(turn_context.as_ref()).await;
|
||||
|
||||
let developer_messages = developer_message_texts(&initial_context);
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.any(|message| message.as_slice() == ["This is a super hint 7"]),
|
||||
"expected rendered feature hint developer message, got {developer_messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn configured_multi_agent_v2_usage_hint_texts_use_effective_enabled_feature_state() {
|
||||
let (mut session, _turn_context) =
|
||||
|
||||
@@ -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