From afce426a43bbefa576ca415de3619a89cd492cbf Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 1 Nov 2026 06:39:04 +0100 Subject: [PATCH] feat(protocol): option controls, options patch, and provider.controls.list Add AgentOptionControl descriptors, snapshot options/controls, an options patch on agent.config.apply, provider.controls.list RPC, and server_info.features.providerOptionControls. featureValues stays accepted on the wire (COMPAT). --- packages/protocol/src/agent-profile.ts | 3 ++ packages/protocol/src/agent-types.ts | 40 ++++++++++++++ packages/protocol/src/messages.ts | 72 +++++++++++++++++++++++++ packages/protocol/src/schedule/types.ts | 1 + 4 files changed, 116 insertions(+) diff --git a/packages/protocol/src/agent-profile.ts b/packages/protocol/src/agent-profile.ts index 2534984240..9ed75a38a9 100644 --- a/packages/protocol/src/agent-profile.ts +++ b/packages/protocol/src/agent-profile.ts @@ -21,7 +21,10 @@ export const AgentProfileSchema = z model: z.string().optional(), modeId: z.string().optional(), thinkingOptionId: z.string().optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Read as `options`. featureValues: z.record(z.string(), z.unknown()).optional(), + /** Provider options applied when the profile creates an agent. */ + options: z.record(z.string(), z.unknown()).optional(), /** Free text, surfaced to orchestrating agents by the `list_profiles` MCP tool. */ notes: z.string().optional(), }) diff --git a/packages/protocol/src/agent-types.ts b/packages/protocol/src/agent-types.ts index ca04655430..6ece6958f9 100644 --- a/packages/protocol/src/agent-types.ts +++ b/packages/protocol/src/agent-types.ts @@ -154,6 +154,42 @@ export interface AgentFeatureSelect { export type AgentFeature = AgentFeatureToggle | AgentFeatureSelect; +export interface AgentOptionChoice { + value: string; + label: string; + description?: string; +} + +interface AgentOptionControlBase { + /** Top-level provider option key this control reads and writes. */ + key: string; + label: string; + description?: string; + tooltip?: string; + icon?: string; +} + +export interface AgentOptionToggleControl extends AgentOptionControlBase { + type: "toggle"; + /** What an unset option stands for; omitted means off. */ + defaultValue?: boolean; +} + +export interface AgentOptionSelectControl extends AgentOptionControlBase { + type: "select"; + /** Desktop toolbar presentation; omitted means labeled. Mobile selectors stay labeled. */ + desktopTrigger?: "icon" | "label"; + /** The choice an unset option stands for. */ + defaultValue?: string; + choices: AgentOptionChoice[]; +} + +/** + * A provider's description of a composer control over one top-level option key. + * The control carries no value: it is read from the agent's effective options. + */ +export type AgentOptionControl = AgentOptionToggleControl | AgentOptionSelectControl; + export interface AgentCapabilityFlags { [capability: string]: boolean | undefined; supportsStreaming: boolean; @@ -523,6 +559,10 @@ export interface AgentSessionConfig { modeId?: string; model?: string; thinkingOptionId?: string; + /** + * @deprecated COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. + * Folded into `providerOptions` at the daemon's edge; the daemon never reads it internally. + */ featureValues?: Record; title?: string | null; providerOptions?: ProviderOptions; diff --git a/packages/protocol/src/messages.ts b/packages/protocol/src/messages.ts index 18654bf337..30519993f2 100644 --- a/packages/protocol/src/messages.ts +++ b/packages/protocol/src/messages.ts @@ -361,6 +361,39 @@ export const AgentFeatureSchema = z.discriminatedUnion("type", [ AgentFeatureSelectSchema, ]); +export const AgentOptionChoiceSchema = z.object({ + value: z.string(), + label: z.string(), + description: z.string().optional(), +}); + +export const AgentOptionToggleControlSchema = z.object({ + type: z.literal("toggle"), + key: z.string(), + label: z.string(), + description: z.string().optional(), + tooltip: z.string().optional(), + icon: z.string().optional(), + defaultValue: z.boolean().optional(), +}); + +export const AgentOptionSelectControlSchema = z.object({ + type: z.literal("select"), + key: z.string(), + label: z.string(), + description: z.string().optional(), + tooltip: z.string().optional(), + icon: z.string().optional(), + desktopTrigger: z.enum(["icon", "label"]).optional(), + defaultValue: z.string().optional(), + choices: z.array(AgentOptionChoiceSchema), +}); + +export const AgentOptionControlSchema = z.discriminatedUnion("type", [ + AgentOptionToggleControlSchema, + AgentOptionSelectControlSchema, +]); + const AgentModelDefinitionSchema = z.object({ provider: AgentProviderSchema, id: z.string(), @@ -490,6 +523,7 @@ const AgentSessionConfigSchema = z.object({ modeId: z.string().optional(), model: z.string().optional(), thinkingOptionId: z.string().optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Folded into providerOptions. featureValues: z.record(z.string(), z.unknown()).optional(), title: z.string().trim().min(1).max(MAX_EXPLICIT_AGENT_TITLE_CHARS).optional().nullable(), providerOptions: ProviderOptionsSchema.optional(), @@ -866,7 +900,11 @@ export const AgentSnapshotPayloadSchema = z.object({ cwd: z.string(), workspaceId: z.string().optional(), model: z.string().nullable(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Built from controls + options. features: z.array(AgentFeatureSchema).optional(), + /** Effective provider options: `config.json` defaults deep-merged with the agent's own. */ + options: ProviderOptionsSchema.optional(), + controls: z.array(AgentOptionControlSchema).optional(), thinkingOptionId: z.string().nullable().optional(), effectiveThinkingOptionId: z.string().nullable().optional(), createdAt: z.string(), @@ -2055,7 +2093,10 @@ export const AgentConfigApplySchema = z.object({ modelId: z.string().nullable().optional(), modeId: z.string().optional(), thinkingOptionId: z.string().nullable().optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Maps to an options patch. featureValues: z.record(z.string(), z.unknown()).optional(), + /** Patch of the agent's own options: replaces top-level keys; `null` removes the key. */ + options: ProviderOptionsSchema.optional(), }); export type AgentConfigApply = z.infer; @@ -2971,7 +3012,9 @@ const ListCommandsDraftConfigSchema = z.object({ modeId: z.string().optional(), model: z.string().optional(), thinkingOptionId: z.string().optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Folded into providerOptions. featureValues: z.record(z.string(), z.unknown()).optional(), + providerOptions: ProviderOptionsSchema.optional(), }); export const ListProviderFeaturesRequestMessageSchema = z.object({ @@ -2980,6 +3023,12 @@ export const ListProviderFeaturesRequestMessageSchema = z.object({ requestId: z.string(), }); +export const ProviderControlsListRequestMessageSchema = z.object({ + type: z.literal("provider.controls.list.request"), + draftConfig: ListCommandsDraftConfigSchema, + requestId: z.string(), +}); + export const ListCommandsRequestSchema = z.object({ type: z.literal("list_commands_request"), agentId: z.string(), @@ -3157,6 +3206,7 @@ export const HubExecutionAgentCreateRequestSchema = z.object({ model: z.string().optional(), modeId: z.string().optional(), thinkingOptionId: z.string().optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Folded into providerOptions. featureValues: z.record(z.string(), z.unknown()).optional(), providerOptions: ProviderOptionsSchema.optional(), toolPolicy: ToolPolicySchema.optional(), @@ -3357,6 +3407,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [ ListProviderModelsRequestMessageSchema, ListProviderModesRequestMessageSchema, ListProviderFeaturesRequestMessageSchema, + ProviderControlsListRequestMessageSchema, ListAvailableProvidersRequestMessageSchema, GetProvidersSnapshotRequestMessageSchema, RefreshProvidersSnapshotRequestMessageSchema, @@ -3836,6 +3887,7 @@ export const ServerInfoStatusPayloadSchema = z selectiveAgentTimeline: z.boolean().optional(), explicitEventSubscriptions: z.boolean().optional(), ownedSubscriptions: z.boolean().optional(), + providerOptionControls: z.boolean().optional(), binaryChunkingV1: z.boolean().optional(), workspaceSetupObservation: z.boolean().optional(), workspaceSortTimes: z.boolean().optional(), @@ -6303,6 +6355,19 @@ export const ListProviderModesResponseMessageSchema = z.object({ }), }); +export const ProviderControlsListResponseMessageSchema = z.object({ + type: z.literal("provider.controls.list.response"), + payload: z.object({ + provider: AgentProviderSchema, + controls: z.array(AgentOptionControlSchema).optional(), + /** Effective options for the draft: `config.json` defaults merged with the draft's own. */ + options: ProviderOptionsSchema.optional(), + error: z.string().nullable().optional(), + fetchedAt: z.string(), + requestId: z.string(), + }), +}); + export const ListProviderFeaturesResponseMessageSchema = z.object({ type: z.literal("list_provider_features_response"), payload: z.object({ @@ -7195,6 +7260,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [ ListProviderModelsResponseMessageSchema, ListProviderModesResponseMessageSchema, ListProviderFeaturesResponseMessageSchema, + ProviderControlsListResponseMessageSchema, ListAvailableProvidersResponseSchema, GetProvidersSnapshotResponseMessageSchema, ProvidersSnapshotUpdateMessageSchema, @@ -7449,6 +7515,12 @@ export type ListProviderModelsRequestMessage = z.infer< typeof ListProviderModelsRequestMessageSchema >; export type ListProviderModesRequestMessage = z.infer; +export type ProviderControlsListRequestMessage = z.infer< + typeof ProviderControlsListRequestMessageSchema +>; +export type ProviderControlsListResponseMessage = z.infer< + typeof ProviderControlsListResponseMessageSchema +>; export type ListProviderFeaturesRequestMessage = z.infer< typeof ListProviderFeaturesRequestMessageSchema >; diff --git a/packages/protocol/src/schedule/types.ts b/packages/protocol/src/schedule/types.ts index 718c7e05ca..731d902970 100644 --- a/packages/protocol/src/schedule/types.ts +++ b/packages/protocol/src/schedule/types.ts @@ -34,6 +34,7 @@ export const ScheduleTargetSchema = z.discriminatedUnion("type", [ isolation: z.enum(["local", "worktree"]).optional(), title: z.string().trim().min(1).nullable().optional(), providerOptions: z.record(z.string(), z.unknown()).optional(), + // COMPAT(featureValues): added in v0.15.0, remove after 2027-05-01. Folded into providerOptions. featureValues: z.record(z.string(), z.unknown()).optional(), systemPrompt: z.string().optional(), mcpServers: z.record(z.string(), z.unknown()).optional(),