From 8c47e36504b3abd59d6e987795fee00b8544af27 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Tue, 28 Apr 2026 18:36:19 -0700 Subject: [PATCH] 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. --- .../schema/json/ClientRequest.json | 27 ++++++++ .../codex_app_server_protocol.schemas.json | 50 ++++++++++++++ .../codex_app_server_protocol.v2.schemas.json | 50 ++++++++++++++ .../ModelProviderCapabilitiesReadParams.json | 5 ++ ...ModelProviderCapabilitiesReadResponse.json | 21 ++++++ .../schema/typescript/ClientRequest.ts | 3 +- .../v2/ModelProviderCapabilitiesReadParams.ts | 5 ++ .../ModelProviderCapabilitiesReadResponse.ts | 5 ++ .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 22 ++++++ .../app-server-protocol/src/protocol/v2.rs | 14 ++++ codex-rs/app-server/README.md | 1 + .../app-server/src/codex_message_processor.rs | 5 ++ codex-rs/app-server/src/message_processor.rs | 27 ++++++++ .../app-server/tests/common/mcp_process.rs | 11 +++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + .../v2/model_provider_capabilities_read.rs | 69 +++++++++++++++++++ 17 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts create mode 100644 codex-rs/app-server/tests/suite/v2/model_provider_capabilities_read.rs diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index c94a9254a..8f44e8383 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -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": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 1726a603f..35c9dc86b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -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" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 0e6d461ac..03ac3475e 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -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" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json new file mode 100644 index 000000000..2996bca0f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelProviderCapabilitiesReadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json new file mode 100644 index 000000000..08e4c2ada --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json @@ -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" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 7aaa17461..d1161cdba 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -43,6 +43,7 @@ import type { McpResourceReadParams } from "./v2/McpResourceReadParams"; import type { McpServerOauthLoginParams } from "./v2/McpServerOauthLoginParams"; import type { McpServerToolCallParams } from "./v2/McpServerToolCallParams"; import type { ModelListParams } from "./v2/ModelListParams"; +import type { ModelProviderCapabilitiesReadParams } from "./v2/ModelProviderCapabilitiesReadParams"; import type { PluginInstallParams } from "./v2/PluginInstallParams"; import type { PluginListParams } from "./v2/PluginListParams"; import type { PluginReadParams } from "./v2/PluginReadParams"; @@ -76,4 +77,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts new file mode 100644 index 000000000..00cbe470b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts @@ -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; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts new file mode 100644 index 000000000..043fc3043 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts @@ -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, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 7aa224b8c..38a1aaf33 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -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"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 5d506d05b..01985b7bd 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -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 { diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 40fcf1fc4..4669ec6cc 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -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/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index ac1574b16..d96b0e514 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -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 > 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. diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 362f8692b..77fe5f9ec 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -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"); diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 65bf22426..dbe013224 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -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, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 81be41f21..76e29d21a 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -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 { + 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, diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 776424cc9..db1cc71d7 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -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; diff --git a/codex-rs/app-server/tests/suite/v2/model_provider_capabilities_read.rs b/codex-rs/app-server/tests/suite/v2/model_provider_capabilities_read.rs new file mode 100644 index 000000000..6dcb9ac4e --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/model_provider_capabilities_read.rs @@ -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(()) +}