From 96d2ea905857718e7e8f04d66b5f192563927ac3 Mon Sep 17 00:00:00 2001 From: xli-oai Date: Fri, 1 May 2026 00:16:25 -0700 Subject: [PATCH] Add remote plugin skill read API (#20150) ## Summary Adds an app-server `plugin/skill/read` method for remote plugin skill markdown. The new method calls the plugin-service skill detail endpoint and returns `skill_md_contents`, so clients can preview skills for remote plugins before the bundle is installed locally. ## Why Uninstalled remote plugin skills do not have local `SKILL.md` files. Without an on-demand remote read, the desktop plugin details UI cannot render the skill details modal for those skills. ## Validation - `just write-app-server-schema` - `just fmt` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server --test all -- suite::v2::plugin_read::plugin_skill_read_reads_remote_skill_contents_when_remote_plugin_enabled --exact` - `just fix -p codex-app-server-protocol -p codex-core-plugins -p codex-app-server` --- .../schema/json/ClientRequest.json | 43 + .../codex_app_server_protocol.schemas.json | 58 + .../codex_app_server_protocol.v2.schemas.json | 58 + .../schema/json/v2/PluginSkillReadParams.json | 21 + .../json/v2/PluginSkillReadResponse.json | 13 + .../schema/typescript/ClientRequest.ts | 3 +- .../typescript/v2/PluginSkillReadParams.ts | 5 + .../typescript/v2/PluginSkillReadResponse.ts | 5 + .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 5 + .../app-server-protocol/src/protocol/v2.rs | 33 + codex-rs/app-server/README.md | 1 + .../app-server/src/codex_message_processor.rs | 6 + .../src/codex_message_processor/plugins.rs | 102 +- .../app-server/tests/common/mcp_process.rs | 10 + .../app-server/tests/suite/v2/plugin_read.rs | 74 +- codex-rs/core-plugins/src/remote.rs | 115 ++ sdk/python/src/codex_app_server/api.py | 29 - .../generated/notification_registry.py | 2 + .../src/codex_app_server/generated/v2_all.py | 1142 ++++++++++------- .../tests/test_public_api_signatures.py | 14 - 21 files changed, 1212 insertions(+), 529 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadResponse.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index e6c5fbbbc..37a64fbe3 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2217,6 +2217,25 @@ ], "type": "object" }, + "PluginSkillReadParams": { + "properties": { + "remoteMarketplaceName": { + "type": "string" + }, + "remotePluginId": { + "type": "string" + }, + "skillName": { + "type": "string" + } + }, + "required": [ + "remoteMarketplaceName", + "remotePluginId", + "skillName" + ], + "type": "object" + }, "PluginUninstallParams": { "properties": { "pluginId": { @@ -5036,6 +5055,30 @@ "title": "Plugin/readRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "plugin/skill/read" + ], + "title": "Plugin/skill/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/PluginSkillReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Plugin/skill/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 d579dfe06..1facd9ca9 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 @@ -762,6 +762,30 @@ "title": "Plugin/readRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "plugin/skill/read" + ], + "title": "Plugin/skill/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/PluginSkillReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Plugin/skill/readRequest", + "type": "object" + }, { "properties": { "id": { @@ -12391,6 +12415,40 @@ "title": "PluginShareSaveResponse", "type": "object" }, + "PluginSkillReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "remoteMarketplaceName": { + "type": "string" + }, + "remotePluginId": { + "type": "string" + }, + "skillName": { + "type": "string" + } + }, + "required": [ + "remoteMarketplaceName", + "remotePluginId", + "skillName" + ], + "title": "PluginSkillReadParams", + "type": "object" + }, + "PluginSkillReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contents": { + "type": [ + "string", + "null" + ] + } + }, + "title": "PluginSkillReadResponse", + "type": "object" + }, "PluginSource": { "oneOf": [ { 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 f9e1f879c..7efd9b4be 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 @@ -1521,6 +1521,30 @@ "title": "Plugin/readRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "plugin/skill/read" + ], + "title": "Plugin/skill/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/PluginSkillReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Plugin/skill/readRequest", + "type": "object" + }, { "properties": { "id": { @@ -9044,6 +9068,40 @@ "title": "PluginShareSaveResponse", "type": "object" }, + "PluginSkillReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "remoteMarketplaceName": { + "type": "string" + }, + "remotePluginId": { + "type": "string" + }, + "skillName": { + "type": "string" + } + }, + "required": [ + "remoteMarketplaceName", + "remotePluginId", + "skillName" + ], + "title": "PluginSkillReadParams", + "type": "object" + }, + "PluginSkillReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contents": { + "type": [ + "string", + "null" + ] + } + }, + "title": "PluginSkillReadResponse", + "type": "object" + }, "PluginSource": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadParams.json new file mode 100644 index 000000000..12d2d3781 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadParams.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "remoteMarketplaceName": { + "type": "string" + }, + "remotePluginId": { + "type": "string" + }, + "skillName": { + "type": "string" + } + }, + "required": [ + "remoteMarketplaceName", + "remotePluginId", + "skillName" + ], + "title": "PluginSkillReadParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadResponse.json new file mode 100644 index 000000000..a1d53bc8e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginSkillReadResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contents": { + "type": [ + "string", + "null" + ] + } + }, + "title": "PluginSkillReadResponse", + "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 05b6d379c..989dbb655 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -51,6 +51,7 @@ import type { PluginReadParams } from "./v2/PluginReadParams"; import type { PluginShareDeleteParams } from "./v2/PluginShareDeleteParams"; import type { PluginShareListParams } from "./v2/PluginShareListParams"; import type { PluginShareSaveParams } from "./v2/PluginShareSaveParams"; +import type { PluginSkillReadParams } from "./v2/PluginSkillReadParams"; import type { PluginUninstallParams } from "./v2/PluginUninstallParams"; import type { ReviewStartParams } from "./v2/ReviewStartParams"; import type { SendAddCreditsNudgeEmailParams } from "./v2/SendAddCreditsNudgeEmailParams"; @@ -80,4 +81,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/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "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": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "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, }; +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/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "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": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "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/PluginSkillReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadParams.ts new file mode 100644 index 000000000..54a63599c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadParams.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 PluginSkillReadParams = { remoteMarketplaceName: string, remotePluginId: string, skillName: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadResponse.ts new file mode 100644 index 000000000..0ae37982b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PluginSkillReadResponse.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 PluginSkillReadResponse = { contents: string | null, }; 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 893cc202e..1a087c4f1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -287,6 +287,8 @@ export type { PluginShareListParams } from "./PluginShareListParams"; export type { PluginShareListResponse } from "./PluginShareListResponse"; export type { PluginShareSaveParams } from "./PluginShareSaveParams"; export type { PluginShareSaveResponse } from "./PluginShareSaveResponse"; +export type { PluginSkillReadParams } from "./PluginSkillReadParams"; +export type { PluginSkillReadResponse } from "./PluginSkillReadResponse"; export type { PluginSource } from "./PluginSource"; export type { PluginSummary } from "./PluginSummary"; export type { PluginUninstallParams } from "./PluginUninstallParams"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index b68d14031..c5a7d61f0 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -612,6 +612,11 @@ client_request_definitions! { serialization: global("config"), response: v2::PluginReadResponse, }, + PluginSkillRead => "plugin/skill/read" { + params: v2::PluginSkillReadParams, + serialization: global("config"), + response: v2::PluginSkillReadResponse, + }, PluginShareSave => "plugin/share/save" { params: v2::PluginShareSaveParams, serialization: global("config"), diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 960f3e968..18234cb8b 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4609,6 +4609,22 @@ pub struct PluginReadResponse { pub plugin: PluginDetail, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PluginSkillReadParams { + pub remote_marketplace_name: String, + pub remote_plugin_id: String, + pub skill_name: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PluginSkillReadResponse { + pub contents: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -10667,6 +10683,23 @@ mod tests { ); } + #[test] + fn plugin_skill_read_params_serialization_uses_remote_plugin_id() { + assert_eq!( + serde_json::to_value(PluginSkillReadParams { + remote_marketplace_name: "chatgpt-global".to_string(), + remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(), + skill_name: "plan-work".to_string(), + }) + .unwrap(), + json!({ + "remoteMarketplaceName": "chatgpt-global", + "remotePluginId": "plugins~Plugin_00000000000000000000000000000000", + "skillName": "plan-work", + }), + ); + } + #[test] fn plugin_share_params_and_response_serialization_use_camel_case_fields() { let plugin_path = if cfg!(windows) { diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 093a4640b..dab47ec3a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -203,6 +203,7 @@ Example with notification opt-out: - `marketplace/upgrade` — upgrade all configured Git plugin marketplaces, or one named marketplace when `marketplaceName` is provided. Returns selected marketplace names, upgraded roots, and per-marketplace errors. - `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**). - `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/apps/MCP server names. Returned plugin skills include their current `enabled` state after local config filtering. Plugin app summaries also include `needsAuth` when the server can determine connector accessibility (**under development; do not call from production clients yet**). +- `plugin/skill/read` — read remote plugin skill markdown on demand by `remoteMarketplaceName`, `remotePluginId`, and `skillName`. This lets clients preview uninstalled remote plugin skills without downloading the plugin bundle. - `skills/changed` — notification emitted when watched local skill files change. - `app/list` — list available apps. - `device/key/create` — create or load a controller-local device signing key for an account/client binding. This local-key API is available only over local transports such as stdio and in-process; remote transports reject it. Hardware-backed providers are the target protection class; an OS-protected non-extractable fallback is allowed only with `protectionPolicy: "allow_os_protected_nonextractable"` and returns the reported `protectionClass`. diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 8bf67497f..3baee6be3 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -126,6 +126,8 @@ use codex_app_server_protocol::PluginShareListParams; use codex_app_server_protocol::PluginShareListResponse; use codex_app_server_protocol::PluginShareSaveParams; use codex_app_server_protocol::PluginShareSaveResponse; +use codex_app_server_protocol::PluginSkillReadParams; +use codex_app_server_protocol::PluginSkillReadResponse; use codex_app_server_protocol::PluginSource; use codex_app_server_protocol::PluginSummary; use codex_app_server_protocol::PluginUninstallParams; @@ -1147,6 +1149,10 @@ impl CodexMessageProcessor { self.plugin_read(to_connection_request_id(request_id), params) .await; } + ClientRequest::PluginSkillRead { request_id, params } => { + self.plugin_skill_read(to_connection_request_id(request_id), params) + .await; + } ClientRequest::PluginShareSave { request_id, params } => { self.plugin_share_save(to_connection_request_id(request_id), params) .await; diff --git a/codex-rs/app-server/src/codex_message_processor/plugins.rs b/codex-rs/app-server/src/codex_message_processor/plugins.rs index 3101091b0..a7c665555 100644 --- a/codex-rs/app-server/src/codex_message_processor/plugins.rs +++ b/codex-rs/app-server/src/codex_message_processor/plugins.rs @@ -3,6 +3,8 @@ use crate::error_code::internal_error; use crate::error_code::invalid_request; use codex_app_server_protocol::PluginAvailability; use codex_app_server_protocol::PluginInstallPolicy; +use codex_core_plugins::remote::is_valid_remote_plugin_id; +use codex_core_plugins::remote::validate_remote_plugin_id; impl CodexMessageProcessor { pub(super) async fn plugin_list( @@ -261,15 +263,15 @@ impl CodexMessageProcessor { if !config.features.enabled(Feature::Plugins) || !config.features.enabled(Feature::RemotePlugin) { - return Err(invalid_request("remote plugin read is not enabled")); + return Err(invalid_request(format!( + "remote plugin read is not enabled for marketplace {remote_marketplace_name}" + ))); } let auth = self.auth_manager.auth().await; let remote_plugin_service_config = RemotePluginServiceConfig { chatgpt_base_url: config.chatgpt_base_url.clone(), }; - if plugin_name.is_empty() || !is_valid_remote_plugin_id(&plugin_name) { - return Err(invalid_request("invalid remote plugin id")); - } + validate_remote_plugin_id(&plugin_name)?; let remote_detail = codex_core_plugins::remote::fetch_remote_plugin_detail( &remote_plugin_service_config, auth.as_ref(), @@ -300,6 +302,61 @@ impl CodexMessageProcessor { Ok(PluginReadResponse { plugin }) } + pub(super) async fn plugin_skill_read( + &self, + request_id: ConnectionRequestId, + params: PluginSkillReadParams, + ) { + let result = self.plugin_skill_read_response(params).await; + self.outgoing.send_result(request_id, result).await; + } + + async fn plugin_skill_read_response( + &self, + params: PluginSkillReadParams, + ) -> Result { + let PluginSkillReadParams { + remote_marketplace_name, + remote_plugin_id, + skill_name, + } = params; + + let config = self.load_latest_config(/*fallback_cwd*/ None).await?; + if !config.features.enabled(Feature::Plugins) + || !config.features.enabled(Feature::RemotePlugin) + { + return Err(invalid_request(format!( + "remote plugin skill read is not enabled for marketplace {remote_marketplace_name}" + ))); + } + validate_remote_plugin_id(&remote_plugin_id)?; + if skill_name.is_empty() { + return Err(invalid_request( + "invalid remote plugin skill name: cannot be empty", + )); + } + + let auth = self.auth_manager.auth().await; + let remote_plugin_service_config = RemotePluginServiceConfig { + chatgpt_base_url: config.chatgpt_base_url.clone(), + }; + let remote_skill_detail = codex_core_plugins::remote::fetch_remote_plugin_skill_detail( + &remote_plugin_service_config, + auth.as_ref(), + &remote_marketplace_name, + &remote_plugin_id, + &skill_name, + ) + .await + .map_err(|err| { + remote_plugin_catalog_error_to_jsonrpc(err, "read remote plugin skill details") + })?; + + Ok(PluginSkillReadResponse { + contents: remote_skill_detail.contents, + }) + } + pub(super) async fn plugin_share_save( &self, request_id: ConnectionRequestId, @@ -514,13 +571,11 @@ impl CodexMessageProcessor { if !config.features.enabled(Feature::Plugins) || !config.features.enabled(Feature::RemotePlugin) { - return Err(invalid_request("remote plugin install is not enabled")); - } - if remote_plugin_id.is_empty() || !is_valid_remote_plugin_id(&remote_plugin_id) { - return Err(invalid_request( - "invalid remote plugin id: only ASCII letters, digits, `_`, `-`, and `~` are allowed", - )); + return Err(invalid_request(format!( + "remote plugin install is not enabled for marketplace {remote_marketplace_name}" + ))); } + validate_remote_plugin_id(&remote_plugin_id)?; let auth = self.auth_manager.auth().await; let remote_plugin_service_config = RemotePluginServiceConfig { @@ -703,13 +758,13 @@ impl CodexMessageProcessor { ) -> Result { let PluginUninstallParams { plugin_id } = params; if codex_plugin::PluginId::parse(&plugin_id).is_err() - && (plugin_id.is_empty() || !is_valid_remote_plugin_id(&plugin_id)) + && !is_valid_remote_uninstall_plugin_id(&plugin_id) { return Err(invalid_request( - "invalid plugin id: expected a local plugin id or remote plugin id", + "invalid plugin id: expected a local plugin id in the form `plugin@marketplace` or a remote plugin id starting with `plugins~`, `plugins_`, `app_`, `asdk_app_`, or `connector_`", )); } - if !plugin_id.is_empty() && is_valid_remote_plugin_id(&plugin_id) { + if is_valid_remote_uninstall_plugin_id(&plugin_id) { return self.remote_plugin_uninstall_response(plugin_id).await; } let plugins_manager = self.thread_manager.plugins_manager(); @@ -800,9 +855,7 @@ impl CodexMessageProcessor { { return Err(invalid_request("remote plugin uninstall is not enabled")); } - if plugin_id.is_empty() || !is_valid_remote_plugin_id(&plugin_id) { - return Err(invalid_request("invalid remote plugin id")); - } + validate_remote_plugin_id(&plugin_id)?; let auth = self.auth_manager.auth().await; let remote_plugin_service_config = RemotePluginServiceConfig { @@ -838,10 +891,13 @@ impl CodexMessageProcessor { } } -fn is_valid_remote_plugin_id(plugin_name: &str) -> bool { - plugin_name - .chars() - .all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' || ch == '~') +fn is_valid_remote_uninstall_plugin_id(plugin_name: &str) -> bool { + is_valid_remote_plugin_id(plugin_name) + && (plugin_name.starts_with("plugins~") + || plugin_name.starts_with("plugins_") + || plugin_name.starts_with("app_") + || plugin_name.starts_with("asdk_app_") + || plugin_name.starts_with("connector_")) } fn remote_marketplace_to_info(marketplace: RemoteMarketplace) -> PluginMarketplaceEntry { @@ -919,7 +975,8 @@ fn remote_plugin_catalog_error_to_jsonrpc( } } RemotePluginCatalogError::InvalidPluginPath { .. } - | RemotePluginCatalogError::ArchiveTooLarge { .. } => JSONRPCErrorError { + | RemotePluginCatalogError::ArchiveTooLarge { .. } + | RemotePluginCatalogError::UnknownMarketplace { .. } => JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: format!("{context}: {err}"), data: None, @@ -928,7 +985,10 @@ fn remote_plugin_catalog_error_to_jsonrpc( | RemotePluginCatalogError::Request { .. } | RemotePluginCatalogError::UnexpectedStatus { .. } | RemotePluginCatalogError::Decode { .. } + | RemotePluginCatalogError::InvalidBaseUrl(_) + | RemotePluginCatalogError::InvalidBaseUrlPath | RemotePluginCatalogError::UnexpectedPluginId { .. } + | RemotePluginCatalogError::UnexpectedSkillName { .. } | RemotePluginCatalogError::UnexpectedEnabledState { .. } | RemotePluginCatalogError::Archive { .. } | RemotePluginCatalogError::ArchiveJoin(_) diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 1bb6f4e36..2abdbd8f7 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -59,6 +59,7 @@ use codex_app_server_protocol::ModelProviderCapabilitiesReadParams; use codex_app_server_protocol::PluginInstallParams; use codex_app_server_protocol::PluginListParams; use codex_app_server_protocol::PluginReadParams; +use codex_app_server_protocol::PluginSkillReadParams; use codex_app_server_protocol::PluginUninstallParams; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ReviewStartParams; @@ -660,6 +661,15 @@ impl McpProcess { self.send_request("plugin/read", params).await } + /// Send a `plugin/skill/read` JSON-RPC request. + pub async fn send_plugin_skill_read_request( + &mut self, + params: PluginSkillReadParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("plugin/skill/read", params).await + } + /// Send an `mcpServerStatus/list` JSON-RPC request. pub async fn send_list_mcp_server_status_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/plugin_read.rs b/codex-rs/app-server/tests/suite/v2/plugin_read.rs index e77f9dc56..fd082ab41 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_read.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_read.rs @@ -22,6 +22,8 @@ use codex_app_server_protocol::PluginAuthPolicy; use codex_app_server_protocol::PluginInstallPolicy; use codex_app_server_protocol::PluginReadParams; use codex_app_server_protocol::PluginReadResponse; +use codex_app_server_protocol::PluginSkillReadParams; +use codex_app_server_protocol::PluginSkillReadResponse; use codex_app_server_protocol::PluginSource; use codex_app_server_protocol::RequestId; use codex_config::types::AuthCredentialsStoreMode; @@ -139,6 +141,7 @@ async fn plugin_read_rejects_remote_marketplace_when_remote_plugin_is_disabled() .message .contains("remote plugin read is not enabled") ); + assert!(err.error.message.contains("chatgpt-global")); Ok(()) } @@ -252,7 +255,7 @@ async fn plugin_read_reads_remote_plugin_details_when_remote_plugin_enabled() -> let request_id = mcp .send_plugin_read_request(PluginReadParams { marketplace_path: None, - remote_marketplace_name: Some("caller-marketplace-is-ignored".to_string()), + remote_marketplace_name: Some("chatgpt-global".to_string()), plugin_name: "plugins~Plugin_00000000000000000000000000000000".to_string(), }) .await?; @@ -286,6 +289,70 @@ async fn plugin_read_reads_remote_plugin_details_when_remote_plugin_enabled() -> Ok(()) } +#[tokio::test] +async fn plugin_skill_read_reads_remote_skill_contents_when_remote_plugin_enabled() -> Result<()> { + let codex_home = TempDir::new()?; + let server = MockServer::start().await; + write_remote_plugin_catalog_config( + codex_home.path(), + &format!("{}/backend-api/", server.uri()), + )?; + write_chatgpt_auth( + codex_home.path(), + ChatGptAuthFixture::new("chatgpt-token") + .account_id("account-123") + .chatgpt_user_id("user-123") + .chatgpt_account_id("account-123"), + AuthCredentialsStoreMode::File, + )?; + + let skill_body = r##"{ + "plugin_id": "plugins~Plugin_00000000000000000000000000000000", + "status": "ENABLED", + "plugin_release_id": "release-1", + "name": "plan-work", + "description": "Plan work from Linear issues", + "plugin_release_skill_id": "skill-1", + "skill_md_contents": "# Plan Work\n\nUse Linear issues to create a plan." +}"##; + + Mock::given(method("GET")) + .and(path( + "/backend-api/ps/plugins/plugins~Plugin_00000000000000000000000000000000/skills/plan-work", + )) + .and(header("authorization", "Bearer chatgpt-token")) + .and(header("chatgpt-account-id", "account-123")) + .respond_with(ResponseTemplate::new(200).set_body_string(skill_body)) + .mount(&server) + .await; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_plugin_skill_read_request(PluginSkillReadParams { + remote_marketplace_name: "chatgpt-global".to_string(), + remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(), + skill_name: "plan-work".to_string(), + }) + .await?; + + let response: JSONRPCResponse = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let response: PluginSkillReadResponse = to_response(response)?; + + assert_eq!( + response, + PluginSkillReadResponse { + contents: Some("# Plan Work\n\nUse Linear issues to create a plan.".to_string()), + } + ); + Ok(()) +} + #[tokio::test] async fn plugin_read_maps_missing_remote_plugin_to_invalid_request() -> Result<()> { let codex_home = TempDir::new()?; @@ -412,6 +479,11 @@ async fn plugin_read_rejects_invalid_remote_plugin_name() -> Result<()> { assert_eq!(err.error.code, -32600); assert!(err.error.message.contains("invalid remote plugin id")); + assert!( + err.error + .message + .contains("only ASCII letters, digits, `_`, `-`, and `~` are allowed") + ); Ok(()) } diff --git a/codex-rs/core-plugins/src/remote.rs b/codex-rs/core-plugins/src/remote.rs index 459ada6bd..7f0c7dd3a 100644 --- a/codex-rs/core-plugins/src/remote.rs +++ b/codex-rs/core-plugins/src/remote.rs @@ -1,5 +1,6 @@ use crate::store::PLUGINS_CACHE_DIR; use crate::store::PluginStore; +use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::PluginAuthPolicy; use codex_app_server_protocol::PluginAvailability; use codex_app_server_protocol::PluginInstallPolicy; @@ -16,6 +17,7 @@ use std::collections::HashSet; use std::fs; use std::path::PathBuf; use std::time::Duration; +use url::Url; mod remote_installed_plugin_sync; mod share; @@ -39,6 +41,7 @@ pub const REMOTE_WORKSPACE_MARKETPLACE_DISPLAY_NAME: &str = "ChatGPT Workspace P const REMOTE_PLUGIN_CATALOG_TIMEOUT: Duration = Duration::from_secs(30); const REMOTE_PLUGIN_LIST_PAGE_LIMIT: u32 = 200; const MAX_REMOTE_DEFAULT_PROMPT_LEN: usize = 128; +const INVALID_REQUEST_ERROR_CODE: i64 = -32600; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RemotePluginServiceConfig { @@ -93,6 +96,32 @@ pub struct RemotePluginSkill { pub enabled: bool, } +#[derive(Debug, Clone, PartialEq)] +pub struct RemotePluginSkillDetail { + pub contents: Option, +} + +pub fn is_valid_remote_plugin_id(plugin_id: &str) -> bool { + !plugin_id.is_empty() + && plugin_id + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' || ch == '~') +} + +pub fn validate_remote_plugin_id(plugin_id: &str) -> Result<(), JSONRPCErrorError> { + if !is_valid_remote_plugin_id(plugin_id) { + return Err(JSONRPCErrorError { + code: INVALID_REQUEST_ERROR_CODE, + message: + "invalid remote plugin id: only ASCII letters, digits, `_`, `-`, and `~` are allowed" + .to_string(), + data: None, + }); + } + + Ok(()) +} + #[derive(Debug, thiserror::Error)] pub enum RemotePluginCatalogError { #[error("chatgpt authentication required for remote plugin catalog")] @@ -127,11 +156,25 @@ pub enum RemotePluginCatalogError { source: serde_json::Error, }, + #[error("invalid remote plugin catalog base URL: {0}")] + InvalidBaseUrl(#[source] url::ParseError), + + #[error("invalid remote plugin catalog base URL path")] + InvalidBaseUrlPath, + + #[error("remote marketplace `{marketplace_name}` is not supported")] + UnknownMarketplace { marketplace_name: String }, + #[error( "remote plugin mutation returned unexpected plugin id: expected `{expected}`, got `{actual}`" )] UnexpectedPluginId { expected: String, actual: String }, + #[error( + "remote plugin skill response returned unexpected skill name: expected `{expected}`, got `{actual}`" + )] + UnexpectedSkillName { expected: String, actual: String }, + #[error( "remote plugin mutation returned unexpected enabled state for `{plugin_id}`: expected {expected_enabled}, got {actual_enabled}" )] @@ -202,6 +245,14 @@ impl RemotePluginScope { Self::Workspace => REMOTE_WORKSPACE_MARKETPLACE_DISPLAY_NAME, } } + + fn from_marketplace_name(name: &str) -> Option { + match name { + REMOTE_GLOBAL_MARKETPLACE_NAME => Some(Self::Global), + REMOTE_WORKSPACE_MARKETPLACE_NAME => Some(Self::Workspace), + _ => None, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -226,6 +277,13 @@ struct RemotePluginSkillResponse { interface: Option, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct RemotePluginSkillDetailResponse { + plugin_id: String, + name: String, + skill_md_contents: Option, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct RemotePluginReleaseInterfaceResponse { short_description: Option, @@ -462,6 +520,42 @@ pub async fn fetch_remote_plugin_detail_with_download_urls( .await } +pub async fn fetch_remote_plugin_skill_detail( + config: &RemotePluginServiceConfig, + auth: Option<&CodexAuth>, + marketplace_name: &str, + plugin_id: &str, + skill_name: &str, +) -> Result { + let auth = ensure_chatgpt_auth(auth)?; + if RemotePluginScope::from_marketplace_name(marketplace_name).is_none() { + return Err(RemotePluginCatalogError::UnknownMarketplace { + marketplace_name: marketplace_name.to_string(), + }); + } + + let url = remote_plugin_skill_detail_url(config, plugin_id, skill_name)?; + let client = build_reqwest_client(); + let request = authenticated_request(client.get(&url), auth)?; + let response: RemotePluginSkillDetailResponse = send_and_decode(request, &url).await?; + if response.plugin_id != plugin_id { + return Err(RemotePluginCatalogError::UnexpectedPluginId { + expected: plugin_id.to_string(), + actual: response.plugin_id, + }); + } + if response.name != skill_name { + return Err(RemotePluginCatalogError::UnexpectedSkillName { + expected: skill_name.to_string(), + actual: response.name, + }); + } + + Ok(RemotePluginSkillDetail { + contents: response.skill_md_contents, + }) +} + async fn fetch_remote_plugin_detail_with_download_url_option( config: &RemotePluginServiceConfig, auth: Option<&CodexAuth>, @@ -883,6 +977,27 @@ async fn fetch_plugin_detail( send_and_decode(request, &url).await } +fn remote_plugin_skill_detail_url( + config: &RemotePluginServiceConfig, + plugin_id: &str, + skill_name: &str, +) -> Result { + let mut url = Url::parse(config.chatgpt_base_url.trim_end_matches('/')) + .map_err(RemotePluginCatalogError::InvalidBaseUrl)?; + { + let mut segments = url + .path_segments_mut() + .map_err(|()| RemotePluginCatalogError::InvalidBaseUrlPath)?; + segments.pop_if_empty(); + segments.push("ps"); + segments.push("plugins"); + segments.push(plugin_id); + segments.push("skills"); + segments.push(skill_name); + } + Ok(url.to_string()) +} + fn ensure_chatgpt_auth(auth: Option<&CodexAuth>) -> Result<&CodexAuth, RemotePluginCatalogError> { let Some(auth) = auth else { return Err(RemotePluginCatalogError::AuthRequired); diff --git a/sdk/python/src/codex_app_server/api.py b/sdk/python/src/codex_app_server/api.py index ed0535db8..2c71859cc 100644 --- a/sdk/python/src/codex_app_server/api.py +++ b/sdk/python/src/codex_app_server/api.py @@ -10,7 +10,6 @@ from .generated.v2_all import ( ApprovalsReviewer, AskForApproval, ModelListResponse, - PermissionProfile, Personality, ReasoningEffort, ReasoningSummary, @@ -150,7 +149,6 @@ class Codex: ephemeral: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox: SandboxMode | None = None, service_name: str | None = None, @@ -167,7 +165,6 @@ class Codex: ephemeral=ephemeral, model=model, model_provider=model_provider, - permission_profile=permission_profile, personality=personality, sandbox=sandbox, service_name=service_name, @@ -215,10 +212,8 @@ class Codex: config: JsonObject | None = None, cwd: str | None = None, developer_instructions: str | None = None, - exclude_turns: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox: SandboxMode | None = None, service_tier: ServiceTier | None = None, @@ -231,10 +226,8 @@ class Codex: config=config, cwd=cwd, developer_instructions=developer_instructions, - exclude_turns=exclude_turns, model=model, model_provider=model_provider, - permission_profile=permission_profile, personality=personality, sandbox=sandbox, service_tier=service_tier, @@ -253,10 +246,8 @@ class Codex: cwd: str | None = None, developer_instructions: str | None = None, ephemeral: bool | None = None, - exclude_turns: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, sandbox: SandboxMode | None = None, service_tier: ServiceTier | None = None, ) -> Thread: @@ -269,10 +260,8 @@ class Codex: cwd=cwd, developer_instructions=developer_instructions, ephemeral=ephemeral, - exclude_turns=exclude_turns, model=model, model_provider=model_provider, - permission_profile=permission_profile, sandbox=sandbox, service_tier=service_tier, ) @@ -356,7 +345,6 @@ class AsyncCodex: ephemeral: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox: SandboxMode | None = None, service_name: str | None = None, @@ -374,7 +362,6 @@ class AsyncCodex: ephemeral=ephemeral, model=model, model_provider=model_provider, - permission_profile=permission_profile, personality=personality, sandbox=sandbox, service_name=service_name, @@ -423,10 +410,8 @@ class AsyncCodex: config: JsonObject | None = None, cwd: str | None = None, developer_instructions: str | None = None, - exclude_turns: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox: SandboxMode | None = None, service_tier: ServiceTier | None = None, @@ -440,10 +425,8 @@ class AsyncCodex: config=config, cwd=cwd, developer_instructions=developer_instructions, - exclude_turns=exclude_turns, model=model, model_provider=model_provider, - permission_profile=permission_profile, personality=personality, sandbox=sandbox, service_tier=service_tier, @@ -462,10 +445,8 @@ class AsyncCodex: cwd: str | None = None, developer_instructions: str | None = None, ephemeral: bool | None = None, - exclude_turns: bool | None = None, model: str | None = None, model_provider: str | None = None, - permission_profile: PermissionProfile | None = None, sandbox: SandboxMode | None = None, service_tier: ServiceTier | None = None, ) -> AsyncThread: @@ -479,10 +460,8 @@ class AsyncCodex: cwd=cwd, developer_instructions=developer_instructions, ephemeral=ephemeral, - exclude_turns=exclude_turns, model=model, model_provider=model_provider, - permission_profile=permission_profile, sandbox=sandbox, service_tier=service_tier, ) @@ -519,7 +498,6 @@ class Thread: effort: ReasoningEffort | None = None, model: str | None = None, output_schema: JsonObject | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox_policy: SandboxPolicy | None = None, service_tier: ServiceTier | None = None, @@ -533,7 +511,6 @@ class Thread: effort=effort, model=model, output_schema=output_schema, - permission_profile=permission_profile, personality=personality, sandbox_policy=sandbox_policy, service_tier=service_tier, @@ -556,7 +533,6 @@ class Thread: effort: ReasoningEffort | None = None, model: str | None = None, output_schema: JsonObject | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox_policy: SandboxPolicy | None = None, service_tier: ServiceTier | None = None, @@ -572,7 +548,6 @@ class Thread: effort=effort, model=model, output_schema=output_schema, - permission_profile=permission_profile, personality=personality, sandbox_policy=sandbox_policy, service_tier=service_tier, @@ -607,7 +582,6 @@ class AsyncThread: effort: ReasoningEffort | None = None, model: str | None = None, output_schema: JsonObject | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox_policy: SandboxPolicy | None = None, service_tier: ServiceTier | None = None, @@ -621,7 +595,6 @@ class AsyncThread: effort=effort, model=model, output_schema=output_schema, - permission_profile=permission_profile, personality=personality, sandbox_policy=sandbox_policy, service_tier=service_tier, @@ -644,7 +617,6 @@ class AsyncThread: effort: ReasoningEffort | None = None, model: str | None = None, output_schema: JsonObject | None = None, - permission_profile: PermissionProfile | None = None, personality: Personality | None = None, sandbox_policy: SandboxPolicy | None = None, service_tier: ServiceTier | None = None, @@ -661,7 +633,6 @@ class AsyncThread: effort=effort, model=model, output_schema=output_schema, - permission_profile=permission_profile, personality=personality, sandbox_policy=sandbox_policy, service_tier=service_tier, diff --git a/sdk/python/src/codex_app_server/generated/notification_registry.py b/sdk/python/src/codex_app_server/generated/notification_registry.py index 5b54207b5..a97dc98f3 100644 --- a/sdk/python/src/codex_app_server/generated/notification_registry.py +++ b/sdk/python/src/codex_app_server/generated/notification_registry.py @@ -38,6 +38,7 @@ from .v2_all import PlanDeltaNotification from .v2_all import ReasoningSummaryPartAddedNotification from .v2_all import ReasoningSummaryTextDeltaNotification from .v2_all import ReasoningTextDeltaNotification +from .v2_all import RemoteControlStatusChangedNotification from .v2_all import ServerRequestResolvedNotification from .v2_all import SkillsChangedNotification from .v2_all import TerminalInteractionNotification @@ -100,6 +101,7 @@ NOTIFICATION_MODELS: dict[str, type[BaseModel]] = { "mcpServer/startupStatus/updated": McpServerStatusUpdatedNotification, "model/rerouted": ModelReroutedNotification, "model/verification": ModelVerificationNotification, + "remoteControl/status/changed": RemoteControlStatusChangedNotification, "serverRequest/resolved": ServerRequestResolvedNotification, "skills/changed": SkillsChangedNotification, "thread/archived": ThreadArchivedNotification, diff --git a/sdk/python/src/codex_app_server/generated/v2_all.py b/sdk/python/src/codex_app_server/generated/v2_all.py index 70c700928..ae85d122c 100644 --- a/sdk/python/src/codex_app_server/generated/v2_all.py +++ b/sdk/python/src/codex_app_server/generated/v2_all.py @@ -49,6 +49,26 @@ class AccountLoginCompletedNotification(BaseModel): success: bool +class AdditionalWritableRootActivePermissionProfileModification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + path: AbsolutePathBuf + type: Annotated[ + Literal["additionalWritableRoot"], + Field(title="AdditionalWritableRootActivePermissionProfileModificationType"), + ] + + +class ActivePermissionProfileModification( + RootModel[AdditionalWritableRootActivePermissionProfileModification] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: AdditionalWritableRootActivePermissionProfileModification + + class AddCreditsNudgeCreditType(Enum): credits = "credits" usage_limit = "usage_limit" @@ -556,6 +576,13 @@ class CommandExecutionStatus(Enum): declined = "declined" +class CommandMigration(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + + class MdmConfigLayerSource(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -972,6 +999,10 @@ class ExternalAgentConfigMigrationItemType(Enum): skills = "SKILLS" plugins = "PLUGINS" mcp_server_config = "MCP_SERVER_CONFIG" + subagents = "SUBAGENTS" + hooks = "HOOKS" + commands = "COMMANDS" + sessions = "SESSIONS" class FeedbackUploadParams(BaseModel): @@ -1041,13 +1072,6 @@ class MinimalFileSystemSpecialPath(BaseModel): kind: Literal["minimal"] -class CurrentWorkingDirectoryFileSystemSpecialPath(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - kind: Literal["current_working_directory"] - - class KindFileSystemSpecialPath(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -1083,7 +1107,6 @@ class FileSystemSpecialPath( RootModel[ RootFileSystemSpecialPath | MinimalFileSystemSpecialPath - | CurrentWorkingDirectoryFileSystemSpecialPath | KindFileSystemSpecialPath | TmpdirFileSystemSpecialPath | SlashTmpFileSystemSpecialPath @@ -1096,7 +1119,6 @@ class FileSystemSpecialPath( root: ( RootFileSystemSpecialPath | MinimalFileSystemSpecialPath - | CurrentWorkingDirectoryFileSystemSpecialPath | KindFileSystemSpecialPath | TmpdirFileSystemSpecialPath | SlashTmpFileSystemSpecialPath @@ -1442,16 +1464,6 @@ class GetAccountParams(BaseModel): ] = False -class GhostCommit(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: str - parent: str | None = None - preexisting_untracked_dirs: list[str] - preexisting_untracked_files: list[str] - - class GitInfo(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -1527,6 +1539,14 @@ class GuardianWarningNotification(BaseModel): ] +class HookErrorInfo(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + message: str + path: str + + class HookEventName(Enum): pre_tool_use = "preToolUse" permission_request = "permissionRequest" @@ -1547,6 +1567,13 @@ class HookHandlerType(Enum): agent = "agent" +class HookMigration(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + + class HookOutputEntryKind(Enum): warning = "warning" stop = "stop" @@ -1582,11 +1609,25 @@ class HookSource(Enum): project = "project" mdm = "mdm" session_flags = "sessionFlags" + plugin = "plugin" + cloud_requirements = "cloudRequirements" legacy_managed_config_file = "legacyManagedConfigFile" legacy_managed_config_mdm = "legacyManagedConfigMdm" unknown = "unknown" +class HooksListParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cwds: Annotated[ + list[str] | None, + Field( + description="When empty, defaults to the current session working directory." + ), + ] = None + + class ImageDetail(Enum): auto = "auto" low = "low" @@ -1664,6 +1705,9 @@ class ChatgptLoginAccountParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + codex_streamlined_login: Annotated[ + bool | None, Field(alias="codexStreamlinedLogin") + ] = None type: Annotated[ Literal["chatgpt"], Field(title="Chatgptv2::LoginAccountParamsType") ] @@ -1930,6 +1974,13 @@ class McpResourceReadParams(BaseModel): uri: str +class McpServerMigration(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + + class McpServerOauthLoginCompletedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2092,6 +2143,22 @@ class ModelListParams(BaseModel): ] = None +class ModelProviderCapabilitiesReadParams(BaseModel): + pass + model_config = ConfigDict( + populate_by_name=True, + ) + + +class ModelProviderCapabilitiesReadResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + image_generation: Annotated[bool, Field(alias="imageGeneration")] + namespace_tools: Annotated[bool, Field(alias="namespaceTools")] + web_search: Annotated[bool, Field(alias="webSearch")] + + class ModelRerouteReason(RootModel[Literal["highRiskCyberActivity"]]): model_config = ConfigDict( populate_by_name=True, @@ -2278,6 +2345,26 @@ class UnrestrictedPermissionProfileFileSystemPermissions(BaseModel): ] +class AdditionalWritableRootPermissionProfileModificationParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + path: AbsolutePathBuf + type: Annotated[ + Literal["additionalWritableRoot"], + Field(title="AdditionalWritableRootPermissionProfileModificationParamsType"), + ] + + +class PermissionProfileModificationParams( + RootModel[AdditionalWritableRootPermissionProfileModificationParams] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: AdditionalWritableRootPermissionProfileModificationParams + + class PermissionProfileNetworkPermissions(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2285,6 +2372,26 @@ class PermissionProfileNetworkPermissions(BaseModel): enabled: bool +class ProfilePermissionProfileSelectionParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + modifications: list[PermissionProfileModificationParams] | None = None + type: Annotated[ + Literal["profile"], Field(title="ProfilePermissionProfileSelectionParamsType") + ] + + +class PermissionProfileSelectionParams( + RootModel[ProfilePermissionProfileSelectionParams] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: ProfilePermissionProfileSelectionParams + + class Personality(Enum): none = "none" friendly = "friendly" @@ -2433,6 +2540,59 @@ class PluginReadParams(BaseModel): ] = None +class PluginShareDeleteParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + + +class PluginShareDeleteResponse(BaseModel): + pass + model_config = ConfigDict( + populate_by_name=True, + ) + + +class PluginShareListParams(BaseModel): + pass + model_config = ConfigDict( + populate_by_name=True, + ) + + +class PluginShareSaveParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + plugin_path: Annotated[AbsolutePathBuf, Field(alias="pluginPath")] + remote_plugin_id: Annotated[str | None, Field(alias="remotePluginId")] = None + + +class PluginShareSaveResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + share_url: Annotated[str, Field(alias="shareUrl")] + + +class PluginSkillReadParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + remote_marketplace_name: Annotated[str, Field(alias="remoteMarketplaceName")] + remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + skill_name: Annotated[str, Field(alias="skillName")] + + +class PluginSkillReadResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + contents: str | None = None + + class LocalPluginSource(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2705,6 +2865,21 @@ class RemoteControlClientEnrollmentAudience( ] +class RemoteControlConnectionStatus(Enum): + disabled = "disabled" + connecting = "connecting" + connected = "connected" + errored = "errored" + + +class RemoteControlStatusChangedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + environment_id: Annotated[str | None, Field(alias="environmentId")] = None + status: RemoteControlConnectionStatus + + class RequestId(RootModel[str | int]): model_config = ConfigDict( populate_by_name=True, @@ -2877,16 +3052,6 @@ class ImageGenerationCallResponseItem(BaseModel): ] -class GhostSnapshotResponseItem(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - ghost_commit: GhostCommit - type: Annotated[ - Literal["ghost_snapshot"], Field(title="GhostSnapshotResponseItemType") - ] - - class CompactionResponseItem(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3197,6 +3362,17 @@ class McpServerStartupStatusUpdatedServerNotification(BaseModel): params: McpServerStatusUpdatedNotification +class RemoteControlStatusChangedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + method: Annotated[ + Literal["remoteControl/status/changed"], + Field(title="RemoteControl/status/changedNotificationMethod"), + ] + params: RemoteControlStatusChangedNotification + + class ExternalAgentConfigImportCompletedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3348,6 +3524,15 @@ class ServiceTier(Enum): flex = "flex" +class SessionMigration(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cwd: str + path: str + title: str | None = None + + class SessionSourceValue(Enum): cli = "cli" vscode = "vscode" @@ -3502,6 +3687,13 @@ class OtherSubAgentSource(BaseModel): other: str +class SubagentMigration(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + + class TerminalInteractionNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3618,6 +3810,37 @@ class ThreadCompactStartResponse(BaseModel): ) +class ThreadForkParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( + None + ) + approvals_reviewer: Annotated[ + ApprovalsReviewer | None, + Field( + alias="approvalsReviewer", + description="Override where approval requests are routed for review on this thread and subsequent turns.", + ), + ] = None + base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None + config: dict[str, Any] | None = None + cwd: str | None = None + developer_instructions: Annotated[ + str | None, Field(alias="developerInstructions") + ] = None + ephemeral: bool | None = None + model: Annotated[ + str | None, + Field(description="Configuration overrides for the forked thread, if any."), + ] = None + model_provider: Annotated[str | None, Field(alias="modelProvider")] = None + sandbox: SandboxMode | None = None + service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadGoalClearedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4034,7 +4257,7 @@ class ThreadRealtimeStartedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - session_id: Annotated[str | None, Field(alias="sessionId")] = None + realtime_session_id: Annotated[str | None, Field(alias="realtimeSessionId")] = None thread_id: Annotated[str, Field(alias="threadId")] version: RealtimeConversationVersion @@ -4061,6 +4284,37 @@ class ThreadRealtimeTranscriptDoneNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadResumeParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( + None + ) + approvals_reviewer: Annotated[ + ApprovalsReviewer | None, + Field( + alias="approvalsReviewer", + description="Override where approval requests are routed for review on this thread and subsequent turns.", + ), + ] = None + base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None + config: dict[str, Any] | None = None + cwd: str | None = None + developer_instructions: Annotated[ + str | None, Field(alias="developerInstructions") + ] = None + model: Annotated[ + str | None, + Field(description="Configuration overrides for the resumed thread, if any."), + ] = None + model_provider: Annotated[str | None, Field(alias="modelProvider")] = None + personality: Personality | None = None + sandbox: SandboxMode | None = None + service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadRollbackParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4190,29 +4444,6 @@ class ThreadStatusChangedNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] -class ThreadTurnsListParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - cursor: Annotated[ - str | None, - Field( - description="Opaque cursor to pass to the next call to continue after the last turn." - ), - ] = None - limit: Annotated[ - int | None, Field(description="Optional turn page size.", ge=0) - ] = None - sort_direction: Annotated[ - SortDirection | None, - Field( - alias="sortDirection", - description="Optional turn pagination direction; defaults to descending.", - ), - ] = None - thread_id: Annotated[str, Field(alias="threadId")] - - class ThreadUnarchiveParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4549,6 +4780,30 @@ class AccountUpdatedNotification(BaseModel): plan_type: Annotated[PlanType | None, Field(alias="planType")] = None +class ActivePermissionProfile(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + extends: Annotated[ + str | None, + Field( + description="Parent profile identifier once permissions profiles support inheritance. This is currently always `null`." + ), + ] = None + id: Annotated[ + str, + Field( + description="Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile." + ), + ] + modifications: Annotated[ + list[ActivePermissionProfileModification] | None, + Field( + description="Bounded user-requested modifications applied on top of the named profile, if any." + ), + ] = [] + + class AppConfig(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4606,6 +4861,26 @@ class InitializeRequest(BaseModel): params: InitializeParams +class ThreadResumeRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["thread/resume"], Field(title="Thread/resumeRequestMethod") + ] + params: ThreadResumeParams + + +class ThreadForkRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["thread/fork"], Field(title="Thread/forkRequestMethod")] + params: ThreadForkParams + + class ThreadArchiveRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4728,17 +5003,6 @@ class ThreadReadRequest(BaseModel): params: ThreadReadParams -class ThreadTurnsListRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[ - Literal["thread/turns/list"], Field(title="Thread/turns/listRequestMethod") - ] - params: ThreadTurnsListParams - - class ThreadInjectItemsRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4759,6 +5023,15 @@ class SkillsListRequest(BaseModel): params: SkillsListParams +class HooksListRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["hooks/list"], Field(title="Hooks/listRequestMethod")] + params: HooksListParams + + class MarketplaceAddRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4810,6 +5083,50 @@ class PluginReadRequest(BaseModel): params: PluginReadParams +class PluginSkillReadRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["plugin/skill/read"], Field(title="Plugin/skill/readRequestMethod") + ] + params: PluginSkillReadParams + + +class PluginShareSaveRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["plugin/share/save"], Field(title="Plugin/share/saveRequestMethod") + ] + params: PluginShareSaveParams + + +class PluginShareListRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["plugin/share/list"], Field(title="Plugin/share/listRequestMethod") + ] + params: PluginShareListParams + + +class PluginShareDeleteRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["plugin/share/delete"], Field(title="Plugin/share/deleteRequestMethod") + ] + params: PluginShareDeleteParams + + class AppListRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4970,6 +5287,18 @@ class ModelListRequest(BaseModel): params: ModelListParams +class ModelProviderCapabilitiesReadRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["modelProvider/capabilities/read"], + Field(title="ModelProvider/capabilities/readRequestMethod"), + ] + params: ModelProviderCapabilitiesReadParams + + class ExperimentalFeatureListRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5293,6 +5622,94 @@ class CommandExecOutputDeltaNotification(BaseModel): ] +class CommandExecParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + command: Annotated[ + list[str], Field(description="Command argv vector. Empty arrays are rejected.") + ] + cwd: Annotated[ + str | None, + Field(description="Optional working directory. Defaults to the server cwd."), + ] = None + disable_output_cap: Annotated[ + bool | None, + Field( + alias="disableOutputCap", + description="Disable stdout/stderr capture truncation for this request.\n\nCannot be combined with `outputBytesCap`.", + ), + ] = None + disable_timeout: Annotated[ + bool | None, + Field( + alias="disableTimeout", + description="Disable the timeout entirely for this request.\n\nCannot be combined with `timeoutMs`.", + ), + ] = None + env: Annotated[ + dict[str, Any] | None, + Field( + description="Optional environment overrides merged into the server-computed environment.\n\nMatching names override inherited values. Set a key to `null` to unset an inherited variable." + ), + ] = None + output_bytes_cap: Annotated[ + int | None, + Field( + alias="outputBytesCap", + description="Optional per-stream stdout/stderr capture cap in bytes.\n\nWhen omitted, the server default applies. Cannot be combined with `disableOutputCap`.", + ge=0, + ), + ] = None + process_id: Annotated[ + str | None, + Field( + alias="processId", + description="Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.", + ), + ] = None + sandbox_policy: Annotated[ + SandboxPolicy | None, + Field( + alias="sandboxPolicy", + description="Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`.", + ), + ] = None + size: Annotated[ + CommandExecTerminalSize | None, + Field( + description="Optional initial PTY size in character cells. Only valid when `tty` is true." + ), + ] = None + stream_stdin: Annotated[ + bool | None, + Field( + alias="streamStdin", + description="Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", + ), + ] = None + stream_stdout_stderr: Annotated[ + bool | None, + Field( + alias="streamStdoutStderr", + description="Stream stdout/stderr via `command/exec/outputDelta` notifications.\n\nStreamed bytes are not duplicated into the final response and require a client-supplied `processId`.", + ), + ] = None + timeout_ms: Annotated[ + int | None, + Field( + alias="timeoutMs", + description="Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", + ), + ] = None + tty: Annotated[ + bool | None, + Field( + description="Enable PTY mode.\n\nThis implies `streamStdin` and `streamStdoutStderr`." + ), + ] = None + + class CommandExecResizeParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5760,6 +6177,25 @@ class NetworkAccessGuardianApprovalReviewAction(BaseModel): ] +class HookMetadata(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + command: str | None = None + display_order: Annotated[int, Field(alias="displayOrder")] + enabled: bool + event_name: Annotated[HookEventName, Field(alias="eventName")] + handler_type: Annotated[HookHandlerType, Field(alias="handlerType")] + is_managed: Annotated[bool, Field(alias="isManaged")] + key: str + matcher: str | None = None + plugin_id: Annotated[str | None, Field(alias="pluginId")] = None + source: HookSource + source_path: Annotated[AbsolutePathBuf, Field(alias="sourcePath")] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + timeout_sec: Annotated[int, Field(alias="timeoutSec", ge=0)] + + class HookOutputEntry(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5797,6 +6233,23 @@ class HookStartedNotification(BaseModel): turn_id: Annotated[str | None, Field(alias="turnId")] = None +class HooksListEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cwd: str + errors: list[HookErrorInfo] + hooks: list[HookMetadata] + warnings: list[str] + + +class HooksListResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + data: list[HooksListEntry] + + class ListMcpServerStatusParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5851,7 +6304,14 @@ class MigrationDetails(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - plugins: list[PluginsMigration] + commands: list[CommandMigration] | None = [] + hooks: list[HookMigration] | None = [] + mcp_servers: Annotated[ + list[McpServerMigration] | None, Field(alias="mcpServers") + ] = [] + plugins: list[PluginsMigration] | None = [] + sessions: list[SessionMigration] | None = [] + subagents: list[SubagentMigration] | None = [] class Model(BaseModel): @@ -5983,6 +6443,13 @@ class PluginReadResponse(BaseModel): plugin: PluginDetail +class PluginShareListResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + data: list[PluginSummary] + + class RateLimitSnapshot(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6594,6 +7061,38 @@ class ThreadListParams(BaseModel): ] = None +class ThreadStartParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( + None + ) + approvals_reviewer: Annotated[ + ApprovalsReviewer | None, + Field( + alias="approvalsReviewer", + description="Override where approval requests are routed for review on this thread and subsequent turns.", + ), + ] = None + base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None + config: dict[str, Any] | None = None + cwd: str | None = None + developer_instructions: Annotated[ + str | None, Field(alias="developerInstructions") + ] = None + ephemeral: bool | None = None + model: str | None = None + model_provider: Annotated[str | None, Field(alias="modelProvider")] = None + personality: Personality | None = None + sandbox: SandboxMode | None = None + service_name: Annotated[str | None, Field(alias="serviceName")] = None + service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None + session_start_source: Annotated[ + ThreadStartSource | None, Field(alias="sessionStartSource") + ] = None + + class ThreadTokenUsage(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6658,6 +7157,77 @@ class TurnPlanUpdatedNotification(BaseModel): turn_id: Annotated[str, Field(alias="turnId")] +class TurnStartParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_policy: Annotated[ + AskForApproval | None, + Field( + alias="approvalPolicy", + description="Override the approval policy for this turn and subsequent turns.", + ), + ] = None + approvals_reviewer: Annotated[ + ApprovalsReviewer | None, + Field( + alias="approvalsReviewer", + description="Override where approval requests are routed for review on this turn and subsequent turns.", + ), + ] = None + cwd: Annotated[ + str | None, + Field( + description="Override the working directory for this turn and subsequent turns." + ), + ] = None + effort: Annotated[ + ReasoningEffort | None, + Field( + description="Override the reasoning effort for this turn and subsequent turns." + ), + ] = None + input: list[UserInput] + model: Annotated[ + str | None, + Field(description="Override the model for this turn and subsequent turns."), + ] = None + output_schema: Annotated[ + Any | None, + Field( + alias="outputSchema", + description="Optional JSON Schema used to constrain the final assistant message for this turn.", + ), + ] = None + personality: Annotated[ + Personality | None, + Field( + description="Override the personality for this turn and subsequent turns." + ), + ] = None + sandbox_policy: Annotated[ + SandboxPolicy | None, + Field( + alias="sandboxPolicy", + description="Override the sandbox policy for this turn and subsequent turns.", + ), + ] = None + service_tier: Annotated[ + ServiceTier | None, + Field( + alias="serviceTier", + description="Override the service tier for this turn and subsequent turns.", + ), + ] = None + summary: Annotated[ + ReasoningSummary | None, + Field( + description="Override the reasoning summary for this turn and subsequent turns." + ), + ] = None + thread_id: Annotated[str, Field(alias="threadId")] + + class TurnSteerParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6757,6 +7327,15 @@ class AppsListResponse(BaseModel): ] = None +class ThreadStartRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["thread/start"], Field(title="Thread/startRequestMethod")] + params: ThreadStartParams + + class ThreadListRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6777,6 +7356,15 @@ class DeviceKeyCreateRequest(BaseModel): params: DeviceKeyCreateParams +class TurnStartRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["turn/start"], Field(title="Turn/startRequestMethod")] + params: TurnStartParams + + class TurnSteerRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6807,6 +7395,15 @@ class McpServerStatusListRequest(BaseModel): params: ListMcpServerStatusParams +class CommandExecRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["command/exec"], Field(title="Command/execRequestMethod")] + params: CommandExecParams + + class CommandExecResizeRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7089,7 +7686,6 @@ class ResponseItem( | ToolSearchOutputResponseItem | WebSearchCallResponseItem | ImageGenerationCallResponseItem - | GhostSnapshotResponseItem | CompactionResponseItem | OtherResponseItem ] @@ -7109,7 +7705,6 @@ class ResponseItem( | ToolSearchOutputResponseItem | WebSearchCallResponseItem | ImageGenerationCallResponseItem - | GhostSnapshotResponseItem | CompactionResponseItem | OtherResponseItem ) @@ -7245,135 +7840,6 @@ class SessionSource( root: SessionSourceValue | CustomSessionSource | SubAgentSessionSource -class ThreadForkParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( - None - ) - approvals_reviewer: Annotated[ - ApprovalsReviewer | None, - Field( - alias="approvalsReviewer", - description="Override where approval requests are routed for review on this thread and subsequent turns.", - ), - ] = None - base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None - config: dict[str, Any] | None = None - cwd: str | None = None - developer_instructions: Annotated[ - str | None, Field(alias="developerInstructions") - ] = None - ephemeral: bool | None = None - exclude_turns: Annotated[ - bool | None, - Field( - alias="excludeTurns", - description="When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking.", - ), - ] = None - model: Annotated[ - str | None, - Field(description="Configuration overrides for the forked thread, if any."), - ] = None - model_provider: Annotated[str | None, Field(alias="modelProvider")] = None - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Full permissions override for the forked thread. Cannot be combined with `sandbox`.", - ), - ] = None - sandbox: SandboxMode | None = None - service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None - thread_id: Annotated[str, Field(alias="threadId")] - - -class ThreadResumeParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( - None - ) - approvals_reviewer: Annotated[ - ApprovalsReviewer | None, - Field( - alias="approvalsReviewer", - description="Override where approval requests are routed for review on this thread and subsequent turns.", - ), - ] = None - base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None - config: dict[str, Any] | None = None - cwd: str | None = None - developer_instructions: Annotated[ - str | None, Field(alias="developerInstructions") - ] = None - exclude_turns: Annotated[ - bool | None, - Field( - alias="excludeTurns", - description="When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming.", - ), - ] = None - model: Annotated[ - str | None, - Field(description="Configuration overrides for the resumed thread, if any."), - ] = None - model_provider: Annotated[str | None, Field(alias="modelProvider")] = None - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Full permissions override for the resumed thread. Cannot be combined with `sandbox`.", - ), - ] = None - personality: Personality | None = None - sandbox: SandboxMode | None = None - service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None - thread_id: Annotated[str, Field(alias="threadId")] - - -class ThreadStartParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - approval_policy: Annotated[AskForApproval | None, Field(alias="approvalPolicy")] = ( - None - ) - approvals_reviewer: Annotated[ - ApprovalsReviewer | None, - Field( - alias="approvalsReviewer", - description="Override where approval requests are routed for review on this thread and subsequent turns.", - ), - ] = None - base_instructions: Annotated[str | None, Field(alias="baseInstructions")] = None - config: dict[str, Any] | None = None - cwd: str | None = None - developer_instructions: Annotated[ - str | None, Field(alias="developerInstructions") - ] = None - ephemeral: bool | None = None - model: str | None = None - model_provider: Annotated[str | None, Field(alias="modelProvider")] = None - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Full permissions override for this thread. Cannot be combined with `sandbox`.", - ), - ] = None - personality: Personality | None = None - sandbox: SandboxMode | None = None - service_name: Annotated[str | None, Field(alias="serviceName")] = None - service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None - session_start_source: Annotated[ - ThreadStartSource | None, Field(alias="sessionStartSource") - ] = None - - class Turn(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7421,84 +7887,6 @@ class TurnCompletedNotification(BaseModel): turn: Turn -class TurnStartParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - approval_policy: Annotated[ - AskForApproval | None, - Field( - alias="approvalPolicy", - description="Override the approval policy for this turn and subsequent turns.", - ), - ] = None - approvals_reviewer: Annotated[ - ApprovalsReviewer | None, - Field( - alias="approvalsReviewer", - description="Override where approval requests are routed for review on this turn and subsequent turns.", - ), - ] = None - cwd: Annotated[ - str | None, - Field( - description="Override the working directory for this turn and subsequent turns." - ), - ] = None - effort: Annotated[ - ReasoningEffort | None, - Field( - description="Override the reasoning effort for this turn and subsequent turns." - ), - ] = None - input: list[UserInput] - model: Annotated[ - str | None, - Field(description="Override the model for this turn and subsequent turns."), - ] = None - output_schema: Annotated[ - Any | None, - Field( - alias="outputSchema", - description="Optional JSON Schema used to constrain the final assistant message for this turn.", - ), - ] = None - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`.", - ), - ] = None - personality: Annotated[ - Personality | None, - Field( - description="Override the personality for this turn and subsequent turns." - ), - ] = None - sandbox_policy: Annotated[ - SandboxPolicy | None, - Field( - alias="sandboxPolicy", - description="Override the sandbox policy for this turn and subsequent turns.", - ), - ] = None - service_tier: Annotated[ - ServiceTier | None, - Field( - alias="serviceTier", - description="Override the service tier for this turn and subsequent turns.", - ), - ] = None - summary: Annotated[ - ReasoningSummary | None, - Field( - description="Override the reasoning summary for this turn and subsequent turns." - ), - ] = None - thread_id: Annotated[str, Field(alias="threadId")] - - class TurnStartResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7514,35 +7902,6 @@ class TurnStartedNotification(BaseModel): turn: Turn -class ThreadStartRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[Literal["thread/start"], Field(title="Thread/startRequestMethod")] - params: ThreadStartParams - - -class ThreadResumeRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[ - Literal["thread/resume"], Field(title="Thread/resumeRequestMethod") - ] - params: ThreadResumeParams - - -class ThreadForkRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[Literal["thread/fork"], Field(title="Thread/forkRequestMethod")] - params: ThreadForkParams - - class DeviceKeySignRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7554,15 +7913,6 @@ class DeviceKeySignRequest(BaseModel): params: DeviceKeySignParams -class TurnStartRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[Literal["turn/start"], Field(title="Turn/startRequestMethod")] - params: TurnStartParams - - class ConfigBatchWriteRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7574,101 +7924,6 @@ class ConfigBatchWriteRequest(BaseModel): params: ConfigBatchWriteParams -class CommandExecParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - command: Annotated[ - list[str], Field(description="Command argv vector. Empty arrays are rejected.") - ] - cwd: Annotated[ - str | None, - Field(description="Optional working directory. Defaults to the server cwd."), - ] = None - disable_output_cap: Annotated[ - bool | None, - Field( - alias="disableOutputCap", - description="Disable stdout/stderr capture truncation for this request.\n\nCannot be combined with `outputBytesCap`.", - ), - ] = None - disable_timeout: Annotated[ - bool | None, - Field( - alias="disableTimeout", - description="Disable the timeout entirely for this request.\n\nCannot be combined with `timeoutMs`.", - ), - ] = None - env: Annotated[ - dict[str, Any] | None, - Field( - description="Optional environment overrides merged into the server-computed environment.\n\nMatching names override inherited values. Set a key to `null` to unset an inherited variable." - ), - ] = None - output_bytes_cap: Annotated[ - int | None, - Field( - alias="outputBytesCap", - description="Optional per-stream stdout/stderr capture cap in bytes.\n\nWhen omitted, the server default applies. Cannot be combined with `disableOutputCap`.", - ge=0, - ), - ] = None - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`.", - ), - ] = None - process_id: Annotated[ - str | None, - Field( - alias="processId", - description="Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.", - ), - ] = None - sandbox_policy: Annotated[ - SandboxPolicy | None, - Field( - alias="sandboxPolicy", - description="Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`.", - ), - ] = None - size: Annotated[ - CommandExecTerminalSize | None, - Field( - description="Optional initial PTY size in character cells. Only valid when `tty` is true." - ), - ] = None - stream_stdin: Annotated[ - bool | None, - Field( - alias="streamStdin", - description="Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", - ), - ] = None - stream_stdout_stderr: Annotated[ - bool | None, - Field( - alias="streamStdoutStderr", - description="Stream stdout/stderr via `command/exec/outputDelta` notifications.\n\nStreamed bytes are not duplicated into the final response and require a client-supplied `processId`.", - ), - ] = None - timeout_ms: Annotated[ - int | None, - Field( - alias="timeoutMs", - description="Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", - ), - ] = None - tty: Annotated[ - bool | None, - Field( - description="Enable PTY mode.\n\nThis implies `streamStdin` and `streamStdoutStderr`." - ), - ] = None - - class Config(BaseModel): model_config = ConfigDict( extra="allow", @@ -7991,20 +8246,13 @@ class ThreadForkResponse(BaseModel): ] = [] model: str model_provider: Annotated[str, Field(alias="modelProvider")] - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Canonical active permissions view for this thread.", - ), - ] = None reasoning_effort: Annotated[ ReasoningEffort | None, Field(alias="reasoningEffort") ] = None sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." ), ] service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None @@ -8068,20 +8316,13 @@ class ThreadResumeResponse(BaseModel): ] = [] model: str model_provider: Annotated[str, Field(alias="modelProvider")] - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Canonical active permissions view for this thread.", - ), - ] = None reasoning_effort: Annotated[ ReasoningEffort | None, Field(alias="reasoningEffort") ] = None sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." ), ] service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None @@ -8122,20 +8363,13 @@ class ThreadStartResponse(BaseModel): ] = [] model: str model_provider: Annotated[str, Field(alias="modelProvider")] - permission_profile: Annotated[ - PermissionProfile | None, - Field( - alias="permissionProfile", - description="Canonical active permissions view for this thread.", - ), - ] = None reasoning_effort: Annotated[ ReasoningEffort | None, Field(alias="reasoningEffort") ] = None sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." ), ] service_tier: Annotated[ServiceTier | None, Field(alias="serviceTier")] = None @@ -8149,27 +8383,6 @@ class ThreadStartedNotification(BaseModel): thread: Thread -class ThreadTurnsListResponse(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - backwards_cursor: Annotated[ - str | None, - Field( - alias="backwardsCursor", - description="Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one turn. Use it with the opposite `sortDirection` to include the anchor turn again and catch updates to that turn.", - ), - ] = None - data: list[Turn] - next_cursor: Annotated[ - str | None, - Field( - alias="nextCursor", - description="Opaque cursor to pass to the next call to continue after the last turn. if None, there are no more turns to return.", - ), - ] = None - - class ThreadUnarchiveResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8177,15 +8390,6 @@ class ThreadUnarchiveResponse(BaseModel): thread: Thread -class CommandExecRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[Literal["command/exec"], Field(title="Command/execRequestMethod")] - params: CommandExecParams - - class ExternalAgentConfigImportRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8216,14 +8420,18 @@ class ClientRequest( | ThreadListRequest | ThreadLoadedListRequest | ThreadReadRequest - | ThreadTurnsListRequest | ThreadInjectItemsRequest | SkillsListRequest + | HooksListRequest | MarketplaceAddRequest | MarketplaceRemoveRequest | MarketplaceUpgradeRequest | PluginListRequest | PluginReadRequest + | PluginSkillReadRequest + | PluginShareSaveRequest + | PluginShareListRequest + | PluginShareDeleteRequest | AppListRequest | DeviceKeyCreateRequest | DeviceKeyPublicRequest @@ -8245,6 +8453,7 @@ class ClientRequest( | TurnInterruptRequest | ReviewStartRequest | ModelListRequest + | ModelProviderCapabilitiesReadRequest | ExperimentalFeatureListRequest | ExperimentalFeatureEnablementSetRequest | McpServerOauthLoginRequest @@ -8293,14 +8502,18 @@ class ClientRequest( | ThreadListRequest | ThreadLoadedListRequest | ThreadReadRequest - | ThreadTurnsListRequest | ThreadInjectItemsRequest | SkillsListRequest + | HooksListRequest | MarketplaceAddRequest | MarketplaceRemoveRequest | MarketplaceUpgradeRequest | PluginListRequest | PluginReadRequest + | PluginSkillReadRequest + | PluginShareSaveRequest + | PluginShareListRequest + | PluginShareDeleteRequest | AppListRequest | DeviceKeyCreateRequest | DeviceKeyPublicRequest @@ -8322,6 +8535,7 @@ class ClientRequest( | TurnInterruptRequest | ReviewStartRequest | ModelListRequest + | ModelProviderCapabilitiesReadRequest | ExperimentalFeatureListRequest | ExperimentalFeatureEnablementSetRequest | McpServerOauthLoginRequest @@ -8401,6 +8615,7 @@ class ServerNotification( | AccountUpdatedServerNotification | AccountRateLimitsUpdatedServerNotification | AppListUpdatedServerNotification + | RemoteControlStatusChangedServerNotification | ExternalAgentConfigImportCompletedServerNotification | FsChangedServerNotification | ItemReasoningSummaryTextDeltaServerNotification @@ -8467,6 +8682,7 @@ class ServerNotification( | AccountUpdatedServerNotification | AccountRateLimitsUpdatedServerNotification | AppListUpdatedServerNotification + | RemoteControlStatusChangedServerNotification | ExternalAgentConfigImportCompletedServerNotification | FsChangedServerNotification | ItemReasoningSummaryTextDeltaServerNotification diff --git a/sdk/python/tests/test_public_api_signatures.py b/sdk/python/tests/test_public_api_signatures.py index 99e5d55b6..b432a6c33 100644 --- a/sdk/python/tests/test_public_api_signatures.py +++ b/sdk/python/tests/test_public_api_signatures.py @@ -65,7 +65,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "ephemeral", "model", "model_provider", - "permission_profile", "personality", "sandbox", "service_name", @@ -91,10 +90,8 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "config", "cwd", "developer_instructions", - "exclude_turns", "model", "model_provider", - "permission_profile", "personality", "sandbox", "service_tier", @@ -107,10 +104,8 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "cwd", "developer_instructions", "ephemeral", - "exclude_turns", "model", "model_provider", - "permission_profile", "sandbox", "service_tier", ], @@ -121,7 +116,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "effort", "model", "output_schema", - "permission_profile", "personality", "sandbox_policy", "service_tier", @@ -134,7 +128,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "effort", "model", "output_schema", - "permission_profile", "personality", "sandbox_policy", "service_tier", @@ -150,7 +143,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "ephemeral", "model", "model_provider", - "permission_profile", "personality", "sandbox", "service_name", @@ -176,10 +168,8 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "config", "cwd", "developer_instructions", - "exclude_turns", "model", "model_provider", - "permission_profile", "personality", "sandbox", "service_tier", @@ -192,10 +182,8 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "cwd", "developer_instructions", "ephemeral", - "exclude_turns", "model", "model_provider", - "permission_profile", "sandbox", "service_tier", ], @@ -206,7 +194,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "effort", "model", "output_schema", - "permission_profile", "personality", "sandbox_policy", "service_tier", @@ -219,7 +206,6 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None: "effort", "model", "output_schema", - "permission_profile", "personality", "sandbox_policy", "service_tier",