fix(buzz-acp): accept id-keyed config options when resolving model switch (#2795)

## Summary

Fixes #2794. Related: #2692.

`resolve_model_switch_method()` reads the `configId` key from each
`session/new` `configOptions` entry and skips entries that lack it.
`claude-agent-acp` (v0.61.0) keys its entries with `id`, so every
model-category entry was skipped, the desired model never matched, and
Claude Code sessions fell back to the CLI default from the user's
`~/.claude/settings.json`. The only trace was a `pool::model` WARN that
never reaches the per-agent log files.

This is the ACP-side half of the symptom reported in #2692. The open
desktop-side PRs (#2695, #2701, #2696) inject `ANTHROPIC_MODEL` at
spawn, which masks the problem for spawn-time selection but leaves the
config-option switch path broken.

## Changes

- `resolve_model_switch_method()` accepts either `configId` or `id` when
extracting the config id. The set request is unchanged: the ACP SDK
schema takes `configId` as the request param and the adapter resolves it
against its `id`-keyed entries, so only the read side needed fixing.
- Regression test with an `id`-keyed `configOptions` payload mirroring
the real adapter response (including `models: null`, so the unstable
fallback path cannot rescue the match).
- Doc comment on `extract_model_config_options()` notes the key drift.

## Testing

`cargo test -p buzz-acp --lib`: 599 passed, 0 failed. The new test fails
on main and passes with this change.

Verified against the real adapter: a stdio JSON-RPC probe of the bundled
`claude-agent-acp` 0.61.0 confirms `session/new` returns `id`-keyed
config options with `opus[1m]` present as a value, and the SDK's
`SetSessionConfigOptionRequest` schema accepts `{sessionId, configId,
value}` as sent by `session_set_config_option()`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: chillerno1 <gh.chiller@pm.me>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
chiller
2026-07-28 12:27:44 +10:00
committed by GitHub
parent be13b4bb9c
commit 925a9a7bf2

View File

@@ -1854,7 +1854,8 @@ pub enum ModelSwitchMethod {
/// Extract `configOptions` entries with `category == "model"` from a `session/new` result.
///
/// Returns the raw JSON array entries. Each entry has `configId`, `displayName`,
/// Returns the raw JSON array entries. Each entry has `configId` (spelled `id`
/// by some adapters, e.g. claude-agent-acp), `displayName`,
/// `options: [{ value, displayName }]`, etc.
pub fn extract_model_config_options(result: &serde_json::Value) -> Vec<serde_json::Value> {
result["configOptions"]
@@ -1888,7 +1889,14 @@ pub fn resolve_model_switch_method(
// 1. Search stable configOptions for a "model"-category entry whose
// options contain a value matching desired_model.
for config_opt in extract_model_config_options(session_new_result) {
let config_id = match config_opt.get("configId").and_then(|v| v.as_str()) {
// Adapters disagree on the key: the ACP spec says `configId`, but
// claude-agent-acp emits `id`. Accept both; the set request always
// uses `configId` on the wire.
let config_id = match config_opt
.get("configId")
.or_else(|| config_opt.get("id"))
.and_then(|v| v.as_str())
{
Some(id) => id,
None => continue,
};
@@ -2464,6 +2472,36 @@ mod tests {
);
}
#[test]
fn resolve_accepts_id_keyed_config_options() {
// claude-agent-acp (observed on v0.61.0) keys config options with
// `id` instead of the spec's `configId`. Payload mirrors its real
// `session/new` response.
let result = serde_json::json!({
"configOptions": [{
"id": "model",
"name": "Model",
"category": "model",
"type": "select",
"currentValue": "default",
"options": [
{ "value": "default", "name": "Default" },
{ "value": "opus[1m]", "name": "Opus" },
{ "value": "sonnet", "name": "Sonnet" }
]
}],
"models": null
});
let method = super::resolve_model_switch_method(&result, "opus[1m]");
assert_eq!(
method,
Some(super::ModelSwitchMethod::ConfigOption {
config_id: "model".to_string(),
option_value: "opus[1m]".to_string(),
})
);
}
#[test]
fn resolve_falls_back_to_unstable() {
let result = serde_json::json!({