mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
4c39ad33cb
commit
8c47e36504
@@ -1896,6 +1896,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkAccess": {
|
||||
"enum": [
|
||||
"restricted",
|
||||
@@ -5478,6 +5481,30 @@
|
||||
"title": "Model/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/capabilities/read"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ModelProviderCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
+50
@@ -1266,6 +1266,30 @@
|
||||
"title": "Model/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/capabilities/read"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ModelProviderCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -11035,6 +11059,32 @@
|
||||
"title": "ModelListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"imageGeneration": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespaceTools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"webSearch": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"imageGeneration",
|
||||
"namespaceTools",
|
||||
"webSearch"
|
||||
],
|
||||
"title": "ModelProviderCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelRerouteReason": {
|
||||
"enum": [
|
||||
"highRiskCyberActivity"
|
||||
|
||||
+50
@@ -1972,6 +1972,30 @@
|
||||
"title": "Model/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/capabilities/read"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ModelProviderCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -7689,6 +7713,32 @@
|
||||
"title": "ModelListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"imageGeneration": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespaceTools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"webSearch": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"imageGeneration",
|
||||
"namespaceTools",
|
||||
"webSearch"
|
||||
],
|
||||
"title": "ModelProviderCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelRerouteReason": {
|
||||
"enum": [
|
||||
"highRiskCyberActivity"
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"imageGeneration": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespaceTools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"webSearch": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"imageGeneration",
|
||||
"namespaceTools",
|
||||
"webSearch"
|
||||
],
|
||||
"title": "ModelProviderCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ModelProviderCapabilitiesReadParams = Record<string, never>;
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ModelProviderCapabilitiesReadResponse = { namespaceTools: boolean, imageGeneration: boolean, webSearch: boolean, };
|
||||
@@ -234,6 +234,8 @@ export type { Model } from "./Model";
|
||||
export type { ModelAvailabilityNux } from "./ModelAvailabilityNux";
|
||||
export type { ModelListParams } from "./ModelListParams";
|
||||
export type { ModelListResponse } from "./ModelListResponse";
|
||||
export type { ModelProviderCapabilitiesReadParams } from "./ModelProviderCapabilitiesReadParams";
|
||||
export type { ModelProviderCapabilitiesReadResponse } from "./ModelProviderCapabilitiesReadResponse";
|
||||
export type { ModelRerouteReason } from "./ModelRerouteReason";
|
||||
export type { ModelReroutedNotification } from "./ModelReroutedNotification";
|
||||
export type { ModelUpgradeInfo } from "./ModelUpgradeInfo";
|
||||
|
||||
@@ -642,6 +642,11 @@ client_request_definitions! {
|
||||
serialization: None,
|
||||
response: v2::ModelListResponse,
|
||||
},
|
||||
ModelProviderCapabilitiesRead => "modelProvider/capabilities/read" {
|
||||
params: v2::ModelProviderCapabilitiesReadParams,
|
||||
serialization: None,
|
||||
response: v2::ModelProviderCapabilitiesReadResponse,
|
||||
},
|
||||
ExperimentalFeatureList => "experimentalFeature/list" {
|
||||
params: v2::ExperimentalFeatureListParams,
|
||||
serialization: global("config"),
|
||||
@@ -2243,6 +2248,23 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_model_provider_capabilities_read() -> Result<()> {
|
||||
let request = ClientRequest::ModelProviderCapabilitiesRead {
|
||||
request_id: RequestId::Integer(7),
|
||||
params: v2::ModelProviderCapabilitiesReadParams {},
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "modelProvider/capabilities/read",
|
||||
"id": 7,
|
||||
"params": {}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_list_collaboration_modes() -> Result<()> {
|
||||
let request = ClientRequest::CollaborationModeList {
|
||||
|
||||
@@ -2355,6 +2355,20 @@ pub struct GetAccountResponse {
|
||||
pub requires_openai_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelProviderCapabilitiesReadParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelProviderCapabilitiesReadResponse {
|
||||
pub namespace_tools: bool,
|
||||
pub image_generation: bool,
|
||||
pub web_search: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -191,6 +191,7 @@ Example with notification opt-out:
|
||||
- `fs/unwatch` — stop sending notifications for a prior `fs/watch`; returns `{}`.
|
||||
- `fs/changed` — notification emitted when watched paths change, including the `watchId` and `changedPaths`.
|
||||
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, `additionalSpeedTiers`, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
|
||||
- `modelProvider/capabilities/read` — read provider-level capabilities for the currently configured model provider.
|
||||
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
|
||||
- `experimentalFeature/enablement/set` — patch the in-memory process-wide runtime feature enablement for the currently supported feature keys (`apps`, `memories`, `plugins`, `remote_control`, `tool_search`, `tool_suggest`, `tool_call_mcp_elicitation`). For each feature, precedence is: cloud requirements > --enable <feature_name> > config.toml > experimentalFeature/enablement/set (new) > code default.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
|
||||
|
||||
@@ -1275,6 +1275,11 @@ impl CodexMessageProcessor {
|
||||
ClientRequest::ConfigRequirementsRead { .. } => {
|
||||
warn!("ConfigRequirementsRead request reached CodexMessageProcessor unexpectedly");
|
||||
}
|
||||
ClientRequest::ModelProviderCapabilitiesRead { .. } => {
|
||||
warn!(
|
||||
"ModelProviderCapabilitiesRead request reached CodexMessageProcessor unexpectedly"
|
||||
);
|
||||
}
|
||||
ClientRequest::ExternalAgentConfigDetect { .. }
|
||||
| ClientRequest::ExternalAgentConfigImport { .. } => {
|
||||
warn!("ExternalAgentConfig request reached CodexMessageProcessor unexpectedly");
|
||||
|
||||
@@ -56,6 +56,7 @@ use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCRequest;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::ModelProviderCapabilitiesReadResponse;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequestPayload;
|
||||
use codex_app_server_protocol::experimental_required_message;
|
||||
@@ -76,6 +77,7 @@ use codex_login::default_client::USER_AGENT_SUFFIX;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_login::default_client::set_default_client_residency_requirement;
|
||||
use codex_login::default_client::set_default_originator;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
@@ -938,6 +940,13 @@ impl MessageProcessor {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::ModelProviderCapabilitiesRead {
|
||||
request_id,
|
||||
params: _,
|
||||
} => {
|
||||
self.handle_model_provider_capabilities_read(request_id_for_connection(request_id))
|
||||
.await;
|
||||
}
|
||||
other => {
|
||||
// Box the delegated future so this wrapper's async state machine does not
|
||||
// inline the full `CodexMessageProcessor::process_request` future, which
|
||||
@@ -957,6 +966,24 @@ impl MessageProcessor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_model_provider_capabilities_read(&self, request_id: ConnectionRequestId) {
|
||||
let result = async {
|
||||
let config = self
|
||||
.config_api
|
||||
.load_latest_config(/*fallback_cwd*/ None)
|
||||
.await?;
|
||||
let provider = create_model_provider(config.model_provider, /*auth_manager*/ None);
|
||||
let capabilities = provider.capabilities();
|
||||
Ok::<_, JSONRPCErrorError>(ModelProviderCapabilitiesReadResponse {
|
||||
namespace_tools: capabilities.namespace_tools,
|
||||
image_generation: capabilities.image_generation,
|
||||
web_search: capabilities.web_search,
|
||||
})
|
||||
}
|
||||
.await;
|
||||
self.outgoing.send_result(request_id, result).await;
|
||||
}
|
||||
|
||||
async fn handle_config_value_write(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user