feat(app-server): gate permission tools by surface support

Hide conversational permission tools unless the initializing surface advertises
support for the matching server request method. This preserves existing model
behavior on unsupported app-server clients while keeping delivery-time denial
as a fallback.

Add request-permissions coverage for each supportedServerRequests capability
combination and update the app-server docs to describe the opt-in contract.
This commit is contained in:
Felipe Coury
2026-04-12 19:59:55 -03:00
parent a3550fd752
commit 5e1802f77f
4 changed files with 185 additions and 4 deletions
@@ -2331,6 +2331,13 @@ impl CodexMessageProcessor {
};
}
apply_surface_capability_feature_gates(
&mut config,
&listener_task_context.thread_state_manager,
&request_id,
)
.await;
let instruction_sources = Self::instruction_sources_from_config(&config).await;
let dynamic_tools = dynamic_tools.unwrap_or_default();
let core_dynamic_tools = if dynamic_tools.is_empty() {
@@ -3886,7 +3893,7 @@ impl CodexMessageProcessor {
let cloud_requirements = self.current_cloud_requirements();
let cli_overrides = self.current_cli_overrides();
let runtime_feature_enablement = self.current_runtime_feature_enablement();
let config = match derive_config_for_cwd(
let mut config = match derive_config_for_cwd(
&cli_overrides,
request_overrides,
typesafe_overrides,
@@ -3904,6 +3911,12 @@ impl CodexMessageProcessor {
return;
}
};
apply_surface_capability_feature_gates(
&mut config,
&self.thread_state_manager,
&request_id,
)
.await;
let fallback_model_provider = config.model_provider_id.clone();
let instruction_sources = Self::instruction_sources_from_config(&config).await;
@@ -4444,7 +4457,7 @@ impl CodexMessageProcessor {
let cloud_requirements = self.current_cloud_requirements();
let cli_overrides = self.current_cli_overrides();
let runtime_feature_enablement = self.current_runtime_feature_enablement();
let config = match derive_config_for_cwd(
let mut config = match derive_config_for_cwd(
&cli_overrides,
request_overrides,
typesafe_overrides,
@@ -4463,6 +4476,12 @@ impl CodexMessageProcessor {
return;
}
};
apply_surface_capability_feature_gates(
&mut config,
&self.thread_state_manager,
&request_id,
)
.await;
let fallback_model_provider = config.model_provider_id.clone();
let instruction_sources = Self::instruction_sources_from_config(&config).await;
@@ -8704,6 +8723,46 @@ async fn derive_config_from_params(
Ok(config)
}
async fn apply_surface_capability_feature_gates(
config: &mut Config,
thread_state_manager: &ThreadStateManager,
request_id: &ConnectionRequestId,
) {
let supported_server_requests = thread_state_manager
.supported_server_requests_for_connection(request_id.connection_id)
.await;
gate_permission_tool_feature(
config,
Feature::RequestPermissionsTool,
supported_server_requests
.contains(&SupportedServerRequestMethod::PermissionsRequestApproval),
);
gate_permission_tool_feature(
config,
Feature::RequestPermissionPresetTool,
supported_server_requests
.contains(&SupportedServerRequestMethod::PermissionPresetRequestApproval),
);
}
fn gate_permission_tool_feature(
config: &mut Config,
feature: Feature,
surface_supports_tool: bool,
) {
if surface_supports_tool || !config.features.get().enabled(feature) {
return;
}
if let Err(err) = config.features.disable(feature) {
warn!(
?feature,
error = %err,
"failed to gate permission tool feature for unsupported app-server surface"
);
}
}
async fn derive_config_for_cwd(
cli_overrides: &[(String, TomlValue)],
request_overrides: Option<HashMap<String, serde_json::Value>>,