feat: expose provider capability bounds to app server clients (#20049)

follow up of #19442. The app server now exposes provider-derived bounds
through a new v2 `modelProvider/read` method. The response reports the
configured provider map key as `modelProvider` and returns the effective
capability booleans so clients can align their UI with the same
provider-owned limits used by core.
This commit is contained in:
Celia Chen
2026-04-28 18:36:19 -07:00
committed by GitHub
parent 4c39ad33cb
commit 8c47e36504
17 changed files with 317 additions and 1 deletions

View File

@@ -54,6 +54,7 @@ use codex_app_server_protocol::McpResourceReadParams;
use codex_app_server_protocol::McpServerToolCallParams;
use codex_app_server_protocol::MockExperimentalMethodParams;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelProviderCapabilitiesReadParams;
use codex_app_server_protocol::PluginInstallParams;
use codex_app_server_protocol::PluginListParams;
use codex_app_server_protocol::PluginReadParams;
@@ -517,6 +518,16 @@ impl McpProcess {
self.send_request("model/list", params).await
}
/// Send a `modelProvider/capabilities/read` JSON-RPC request.
pub async fn send_model_provider_capabilities_read_request(
&mut self,
params: ModelProviderCapabilitiesReadParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("modelProvider/capabilities/read", params)
.await
}
/// Send an `experimentalFeature/list` JSON-RPC request.
pub async fn send_experimental_feature_list_request(
&mut self,

View File

@@ -26,6 +26,7 @@ mod mcp_server_status;
mod mcp_tool;
mod memory_reset;
mod model_list;
mod model_provider_capabilities_read;
mod output_schema;
mod plan_item;
mod plugin_install;

View File

@@ -0,0 +1,69 @@
use std::time::Duration;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ModelProviderCapabilitiesReadParams;
use codex_app_server_protocol::ModelProviderCapabilitiesReadResponse;
use codex_app_server_protocol::RequestId;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn read_default_provider_capabilities() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_model_provider_capabilities_read_request(ModelProviderCapabilitiesReadParams {})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let received: ModelProviderCapabilitiesReadResponse = to_response(response)?;
let expected = ModelProviderCapabilitiesReadResponse {
namespace_tools: true,
image_generation: true,
web_search: true,
};
assert_eq!(received, expected);
Ok(())
}
#[tokio::test]
async fn read_amazon_bedrock_provider_capabilities() -> Result<()> {
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join("config.toml"),
r#"model_provider = "amazon-bedrock"
"#,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_model_provider_capabilities_read_request(ModelProviderCapabilitiesReadParams {})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let received: ModelProviderCapabilitiesReadResponse = to_response(response)?;
let expected = ModelProviderCapabilitiesReadResponse {
namespace_tools: false,
image_generation: false,
web_search: false,
};
assert_eq!(received, expected);
Ok(())
}