Module 33: Session Config Options
Expose per-session settings (a model picker, a mode, and a capability-gated boolean) as session config options, change them withsession/set_config_option, and push an agent-initiated change with a config_option_update. One agent, written twice: builder API and annotations.
What You’ll Learn
- Building a model picker with
SessionConfigSelect.model(...), and a categorized select withSessionConfigSelect.builder() - Offering a
SessionConfigBooleanonly to clients that advertised support for it - Returning the full option list from
session/newand every subsequent change, never a delta - Sending
ConfigOptionUpdateonly when the agent changes a setting itself - Rejecting a bad request with
-32602, since the SDK validates nothing about aset_config_optioncall - Reading the id and value directly with
@ConfigId/@ConfigValue, typed parameters instead of the raw request - Keeping legacy
session/set_modein step with acategory: "mode"config option - Dispatching option types with
instanceofand anUnknownSessionConfigOptionbranch
The Code
Declaring the options
Three options, one state object shared by both agent variants below. The model picker uses the dedicated factory; the mode select and the boolean use the builders:session/new.
Annotated agent
@Prompt, may declare NegotiatedCapabilities and AcpSyncAgent/AcpAsyncAgent parameters, resolved by type, for the connection the request arrived on. That’s why @NewSession reads supportsBooleanConfigOptions() straight from a parameter here, with no need to capture the built agent.
@ConfigId String and @ConfigValue (typed by the parameter: String for a select’s value,
boolean/Boolean for a boolean, Object for either, as above) replace pulling configId() and
value() out of the raw request. The SDK still doesn’t check that the session actually offered that
id or value, only that the value is the right kind; whether it’s a value the session offers is the
method’s own job, the same as with the raw request.
The same agent, with the builder API
(req, self) -> ..., so session/new reads the client’s capabilities straight off self.getClientCapabilities(), with no AtomicReference needed to reach the agent build() is about to return.
Rejecting a bad request
The spec defines no error for an invalidset_config_option; this agent answers -32602:
req.value() is an Object: a String for a select, a Boolean for a boolean, so the handler checks its type before trusting it, the same as the id.
Client: read, change, and watch for agent-initiated changes
instanceof and keep a branch for a type a newer agent might send:
ConfigOptionUpdate session update, handled before prompt() returns:
Setting an option the client was never offered (a boolean to a client that didn’t advertise support, or a model id that isn’t in the list) is rejected with
-32602, received as an AcpError. A client that predates config options can still work through the legacy session/set_mode: the agent keeps both representations of the same state in sync.Source Code
View on GitHubRunning the Example
verbose option is never offered and setting it is rejected, and a legacy session/set_mode call is shown keeping the same state.
Next Module
Module 34: Extension Methods: add your own_-prefixed requests and notifications to ACP.