Assistant: switching models no longer reports "Method not found" for session/set_model (#9970) (#10022)

* claim: #9970

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Agent chat: no error when an agent does not serve session/set_model (#9970)

An ACP agent can list models in the unstable `models` field of its
session/new reply without serving `session/set_model`; it answers
JSON-RPC -32601 and the Assistant showed that as an error each time the
model was switched. Agents that offer the model as a configuration option
already go through session/set_config_option. A -32601 reply to
session/set_model is now logged at debug level instead of shown; every
other error still shows.

Fixes #9970

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
This commit is contained in:
rugbymauri
2026-10-31 12:51:26 +00:00
committed by GitHub
parent a22aeaaf93
commit cb9b50b11e
2 changed files with 60 additions and 0 deletions
@@ -143,6 +143,11 @@ impl AcpBackend {
out.push(Event::ConfigOptionFinished { id: config_id, result });
}
}
// `session/set_model` is still unstable in ACP: an agent can list `models` and
// still not serve it (#9970). It keeps its own model; that is no error to show.
(Pending::SetModel, Err((acp::METHOD_NOT_FOUND, message))) => {
log::debug!("agent does not serve session/set_model: {message}");
}
(what, Err((code, message))) => {
let config_error = what == Pending::SetConfig;
if what == Pending::SetConfig
+55
View File
@@ -1943,6 +1943,61 @@ done
let _ = std::fs::remove_dir_all(&dir);
}
/// An agent that lists `models` but does not serve the unstable `session/set_model` answers
/// "method not found": the choice stays and no error shows (#9970). Other errors still show.
#[cfg(unix)]
#[test]
fn set_model_method_not_found_shows_no_error() {
let dir = std::env::temp_dir().join(format!("sc-agent-chat-model-unserved-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let log = dir.join("received.log");
let script = r#"
log="$1"; n=0
while IFS= read -r line; do
printf '%s\n' "$line" >> "$log"
id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p')
case "$line" in
*'"method":"initialize"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{"agentInfo":{"name":"fake","version":"1"}}}\n' "$id" ;;
*'"method":"session/new"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{"sessionId":"s1","models":{"currentModelId":"fast","availableModels":[{"modelId":"fast","name":"Fast"},{"modelId":"smart","name":"Smart"}]}}}\n' "$id" ;;
*'"method":"session/set_model"'*)
n=$((n+1))
if [ "$n" = 1 ]; then printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-32601,"message":"\\"Method not found\\": session/set_model"}}\n' "$id"
else printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-32001,"message":"model unavailable"}}\n' "$id"; fi ;;
esac
done
"#;
let argv = vec!["sh".to_string(), "-c".to_string(), script.to_string(), "fake-agent".to_string(), log.to_string_lossy().into_owned()];
let start = backend::StartContext { cwd: dir.clone(), bridge: None, tools: None, wake: Box::new(|| {}) };
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
app.agent_chat.backend = Some(Box::new(acp_client::AcpBackend::start(&argv, start).unwrap()));
app.agent_chat.state = Some(State::Starting);
app.agent_chat.running_id = "fake".into();
let ctx = crate::fonts::test_context();
let errors = |app: &SolveApp| -> Vec<String> {
app.agent_chat.items.iter().filter_map(|i| if let Item::Notice { text, error: true } = i { Some(text.clone()) } else { None }).collect()
};
let t = std::time::Instant::now();
while !(app.agent_chat.state() == State::Ready && models_shown(&app)) {
poll(&mut app, &ctx);
assert!(t.elapsed() < std::time::Duration::from_secs(20), "agent not ready: {}", status(&app));
std::thread::sleep(std::time::Duration::from_millis(10));
}
set_model(&mut app, "smart");
set_model(&mut app, "fast");
// Replies come in order: once the second one's error shows, the first reply was handled too.
let t = std::time::Instant::now();
while !errors(&app).iter().any(|e| e == "model unavailable") {
poll(&mut app, &ctx);
assert!(t.elapsed() < std::time::Duration::from_secs(20), "no error for the second choice: {:?}", errors(&app));
std::thread::sleep(std::time::Duration::from_millis(10));
}
assert_eq!(errors(&app), ["model unavailable"], "method not found is no error to show");
assert!(app.agent_chat.state().running(), "the chat goes on");
assert_eq!(std::fs::read_to_string(&log).unwrap().matches("session/set_model").count(), 2);
disconnect(&mut app);
let _ = std::fs::remove_dir_all(&dir);
}
// ---- Files attached to a message ----
/// A folder of files to attach: a picture and a text file.