Model options
model_options
¶
Aggregate and namespace ACP model selectors for the host proxy.
The proxy hides multiple in-container agents behind a single ACP
endpoint by namespacing each agent's model ids as agent:model.
Three operations live here, layered on top of the ACP SDK's pydantic
models:
build_aggregated_session_new— the pre-bindsession/newreply that advertises the union of every authenticated agent's models under one selector.build_model_option— theconfigOptions[category=model]entry carrying that aggregate selector. Since ACP 1.16 retired the dedicatedmodelsblock, this is the only channel on which models are advertised.find_model_option— the mirror image: which of a backend's config options is its model selector, so the probe can read its ids and the proxy can write a pick into it.namespace_model_options_in_place— the post-bind rewrite that puts theagent:prefix back on the bare model ids a bound backend emits in its own config options.
Plus the small vocabulary helpers (split_namespaced,
humanise_model_id,
model_ids_in_option)
that callers across the proxy share.
MODEL_OPTION_CATEGORY = 'model'
module-attribute
¶
ACP semantic category for the model selector configOption.
Used as both the category and the id field of the
acp.schema.SessionConfigOptionSelect we
build — keeping them in sync prevents drift between the discriminator
the proxy emits and the one downstream code matches on.
MODEL_NAMESPACE_SEP = ':'
module-attribute
¶
Separator between agent and model in the namespaced id (e.g.
claude:opus-4.6). Chosen over / to avoid collisions with
OpenRouter-style ids like anthropic/claude-opus-4.
build_aggregated_session_new(session_id, models)
¶
Build the pre-bind session/new reply for models.
ACP 1.16 retired the dedicated models block (SessionModelState
/ ModelInfo) that used to ride alongside configOptions; the
category="model" selector is now the only channel through which an
agent advertises what it can run. So the aggregate selector this
builds is no longer a mirror of the real thing — it is the real
thing.
Empty models yields a schema-valid response with no configOptions
block — a select has non-nullable required fields the proxy can't fill
in for an empty list, and modelling "no models" as an empty selector
trips client validation.
Source code in src/terok_executor/acp/model_options.py
build_model_option(namespaced_models, *, current)
¶
Build a category: "model" select option with namespaced ids.
Source code in src/terok_executor/acp/model_options.py
find_model_option(config_options)
¶
Pick the model selector out of an agent's configOptions.
Since ACP 1.16 dropped the dedicated models block, the model
selector is just one select among the agent's config options and has
to be recognised by convention. category is the semantic marker
the spec provides, but it is optional ("UX only"), so a wrapper that
omits it is matched on the well-known id instead. Returns
None when the agent exposes no model choice at all — a
single-model wrapper, which callers must treat as legitimate rather
than as an error.
The one place this rule lives: the probe reads models out of the option, the proxy writes a pick into it, and both must agree on which option they mean.
Source code in src/terok_executor/acp/model_options.py
model_ids_in_option(opt)
¶
List the model ids a selector offers, flattening any groups.
ACP lets a select present its options either flat or bucketed into named groups (providers, tiers, …). The roster only cares about the ids, so both shapes collapse to one ordered list.
Source code in src/terok_executor/acp/model_options.py
namespace_model_options_in_place(config_options, bound_agent)
¶
Prefix bare model ids in config_options with bound_agent:.
Used on every backend → client frame that carries
configOptions[*] post-bind (ConfigOptionUpdate notification,
SetSessionConfigOptionResponse). Already-namespaced values are
left alone so the function is idempotent — paths the proxy itself
constructed (e.g. ack of a model pick) round-trip cleanly.
Source code in src/terok_executor/acp/model_options.py
split_namespaced(namespaced)
¶
Split agent:model into (agent, model).
Empty halves signal a malformed id — callers that need to validate
do so by checking both halves. Centralises the partition so the
proxy's bind/lookup paths stop spelling
.partition(MODEL_NAMESPACE_SEP) themselves.
Source code in src/terok_executor/acp/model_options.py
humanise_model_id(namespaced)
¶
Render claude:opus-4.6 as Claude: opus-4.6 for the picker.
Colon matches the wire-level MODEL_NAMESPACE_SEP
so an OpenCode-style opencode:opencode/big-pickle reads as one
provider plus one slash-bearing model id. Forwards verbatim if the
input isn't a namespaced pair.