fix(clippy): clear remaining tui lint findings

Apply the workspace lint suggestions for TUI enum layout, helper ordering, needless references, and lock lifetimes while preserving protocol wire shapes.
This commit is contained in:
ddlw committed 2026-10-28 04:15:29 +08:00
1 parent 21403c472d
commit 0296261823
13 files changed
+242 -238

No files matched your search

+1 -1
View File
@@ -1429,7 +1429,7 @@ impl CodewhaleClient {
// sanitizer, dump the offending indices so we can diagnose where
// they came from on the next failure.
if format!("{error:#}").contains("reasoning_content") {
log_thinking_mode_violations(&body);
log_thinking_mode_violations(body);
}
return Err(error);
}
+1 -1
View File
@@ -3802,7 +3802,7 @@ impl Engine {
controls.active = control.clone();
control
};
Box::pin(self.handle_message_operation(turn, Some(request))).await;
Box::pin(self.handle_message_operation(*turn, Some(request))).await;
}
Op::SendMessage(spec) => {
self.admitted_turn_control = {
@@ -1152,7 +1152,7 @@ async fn user_steer_during_parent_answer_still_gets_a_reply_with_healthy_childre
.expect("steering must preempt an indefinitely silent request");
assert_eq!(status, TurnOutcomeStatus::Completed, "{phase}: {error:?}");
assert_eq!(client.calls.load(Ordering::SeqCst), 3, "{phase}");
let requests = client.requests.lock().unwrap();
let requests = client.requests.lock().unwrap().clone();
assert!(requests[1].messages.iter().any(|message| message.content.iter().any(|block| {
matches!(block, ContentBlock::Text { text, .. } if text.contains("Also read state.txt and include its evidence."))
})), "{phase}");
@@ -1179,7 +1179,6 @@ async fn user_steer_during_parent_answer_still_gets_a_reply_with_healthy_childre
requests[0].system, requests[1].system,
"{phase}: pinned system prefix changed"
);
drop(requests);
let mut events = handle.rx_event.write().await;
let events = std::iter::from_fn(|| events.try_recv().ok()).collect::<Vec<_>>();
assert!(events.iter().all(|event| !matches!(event, Event::ToolCallStarted { model_call: Some(call), .. } if call.provider_id == "call-must-not-run")), "{phase}");
+1 -1
View File
@@ -240,7 +240,7 @@ pub struct TurnSpec {
#[derive(Debug)]
pub enum Op {
Review {
turn: TurnSpec,
turn: Box<TurnSpec>,
request: codewhale_protocol::op::ReviewOperation,
},
/// Send a message to the AI
+1 -1
View File
@@ -91,7 +91,7 @@ fn request(
};
let frame = json!({"jsonrpc":"2.0","id":id,"method":"proc/write","params":params});
match parse_host_message(frame, HostTier::Builtin).unwrap() {
HostMessage::Request { request, .. } => request,
HostMessage::Request { request, .. } => *request,
_ => panic!("decoded process write must be a request"),
}
}
+16 -16
View File
@@ -782,7 +782,7 @@ pub enum HostNotification {
pub enum HostMessage {
Request {
id: u64,
request: HostRequest,
request: Box<HostRequest>,
},
Notification(HostNotification),
Response {
@@ -864,12 +864,12 @@ pub fn parse_host_message(value: Value, tier: HostTier) -> Result<HostMessage, P
.map_err(|reason| perr(format!("{method}: {reason}")))?;
HostMessage::Request {
id,
request: HostRequest::Register(register),
request: Box::new(HostRequest::Register(register)),
}
}
("registry/unregister", Some(id)) => HostMessage::Request {
id,
request: HostRequest::Unregister(params(&method, p)?),
request: Box::new(HostRequest::Unregister(params(&method, p)?)),
},
("user-questions/ask", Some(id)) => HostMessage::Request {
id,
@@ -877,51 +877,51 @@ pub fn parse_host_message(value: Value, tier: HostTier) -> Result<HostMessage, P
},
("session/checkpoint", Some(id)) => HostMessage::Request {
id,
request: HostRequest::SessionCheckpoint(params(&method, p)?),
request: Box::new(HostRequest::SessionCheckpoint(params(&method, p)?)),
},
("core/call", Some(id)) => HostMessage::Request {
id,
request: HostRequest::CoreCall(params(&method, p)?),
request: Box::new(HostRequest::CoreCall(params(&method, p)?)),
},
("net/start", Some(id)) => HostMessage::Request {
id,
request: HostRequest::NetStart(params(&method, p)?),
request: Box::new(HostRequest::NetStart(params(&method, p)?)),
},
("net/fetch", Some(id)) => HostMessage::Request {
id,
request: HostRequest::NetFetch(params(&method, p)?),
request: Box::new(HostRequest::NetFetch(params(&method, p)?)),
},
("net/read", Some(id)) => HostMessage::Request {
id,
request: HostRequest::NetRead(params(&method, p)?),
request: Box::new(HostRequest::NetRead(params(&method, p)?)),
},
("net/release", Some(id)) => HostMessage::Request {
id,
request: HostRequest::NetRelease(params(&method, p)?),
request: Box::new(HostRequest::NetRelease(params(&method, p)?)),
},
("net/close", Some(id)) => HostMessage::Request {
id,
request: HostRequest::NetClose(params(&method, p)?),
request: Box::new(HostRequest::NetClose(params(&method, p)?)),
},
("proc/close", Some(id)) => HostMessage::Request {
id,
request: HostRequest::ProcClose(params(&method, p)?),
request: Box::new(HostRequest::ProcClose(params(&method, p)?)),
},
("proc/write", Some(id)) => HostMessage::Request {
id,
request: HostRequest::ProcWrite(params(&method, p)?),
request: Box::new(HostRequest::ProcWrite(params(&method, p)?)),
},
("proc/read", Some(id)) => HostMessage::Request {
id,
request: HostRequest::ProcRead(params(&method, p)?),
request: Box::new(HostRequest::ProcRead(params(&method, p)?)),
},
("exec/redeem", Some(id)) => HostMessage::Request {
id,
request: HostRequest::ExecutionRedeem(params(&method, p)?),
request: Box::new(HostRequest::ExecutionRedeem(params(&method, p)?)),
},
("proc/launch", Some(id)) => HostMessage::Request {
id,
request: HostRequest::ProcLaunch(params(&method, p)?),
request: Box::new(HostRequest::ProcLaunch(params(&method, p)?)),
},
("host/hello", None) => {
let hello: HelloParams = params(&method, p)?;
@@ -974,7 +974,7 @@ impl HostMessage {
#[must_use]
pub fn to_value(&self) -> Value {
match self {
Self::Request { id, request } => match request {
Self::Request { id, request } => match request.as_ref() {
HostRequest::Register(p) => request_value(*id, "registry/register", to_value(p)),
HostRequest::Unregister(p) => {
request_value(*id, "registry/unregister", to_value(p))
+14 -14
View File
@@ -810,15 +810,15 @@ fn native_readable_paths(
return Err("Native writable data overlaps a runtime or package read grant".into());
}
canonical.push(resolved);
if path.is_file() {
if let (Some(parent), Some(name)) = (path.parent(), path.file_name()) {
canonical.push(
parent
.canonicalize()
.map_err(|error| error.to_string())?
.join(name),
);
}
if path.is_file()
&& let (Some(parent), Some(name)) = (path.parent(), path.file_name())
{
canonical.push(
parent
.canonicalize()
.map_err(|error| error.to_string())?
.join(name),
);
}
}
paths.extend(canonical);
@@ -1731,7 +1731,7 @@ impl HostProcess {
if !snapshot_roots.iter().any(|grant| canonical.starts_with(grant) && canonical != *grant) {
return Err("Native snapshot store was not granted to this host; restart Codewhale to admit this store".into());
}
let current = native_snapshot_roots(&[authority.state_path.clone()])?;
let current = native_snapshot_roots(std::slice::from_ref(&authority.state_path))?;
if !current.iter().any(|grant| snapshot_roots.contains(grant) && canonical.starts_with(grant)) {
return Err("Native snapshot store changed after host launch".into());
}
@@ -2395,7 +2395,7 @@ impl Reader {
};
let _ = call.tx.send(result);
}
HostMessage::Request { id, request } => self.start_host_request(id, request)?,
HostMessage::Request { id, request } => self.start_host_request(id, *request)?,
HostMessage::Notification(notification) => match notification {
HostNotification::Hello(hello) => {
if let Some(tx) = self.handshake.lock().expect("handshake lock").hello.take() {
@@ -3011,7 +3011,7 @@ mod tests {
fn request(rig: &Rig, id: u64, plugin: &str) -> Result<(), String> {
rig.reader.handle(HostMessage::Request {
id,
request: register_request(plugin),
request: Box::new(register_request(plugin)),
})
}
@@ -3059,10 +3059,10 @@ mod tests {
rig.reader
.handle(HostMessage::Request {
id: 6,
request: HostRequest::Unregister(protocol::UnregisterParams {
request: Box::new(HostRequest::Unregister(protocol::UnregisterParams {
owner: owner("p"),
handle: 9,
}),
})),
})
.unwrap();
assert_eq!(
+1 -1
View File
@@ -653,7 +653,7 @@ condition = { type = "exit_code", code = 1 }
problem.event.is_none(),
"the override is not one hook's problem: {problem:?}"
);
assert!(problem_summary(&problem).contains("`[hooks]` setting"));
assert!(problem_summary(problem).contains("`[hooks]` setting"));
// Even unvalidated, the accessors refuse the value rather than hand a
// zero budget to the executor.
+2 -2
View File
@@ -1614,7 +1614,7 @@ enum McpCommand {
scopes: Vec<String>,
/// Arguments for command-based servers
#[arg(long = "arg", allow_hyphen_values = true)]
args: Vec<String>,
args: Box<Vec<String>>,
},
/// Authenticate to a URL-based MCP server using OAuth
Login {
@@ -12243,7 +12243,7 @@ async fn run_mcp_command(
}
let added_server = McpServerConfig {
command,
args,
args: *args,
env: std::collections::HashMap::new(),
cwd: None,
url,
+10 -8
View File
@@ -1556,6 +1556,15 @@ pub(crate) fn measure_static_prefix(system: Option<&SystemPrompt>, tools: &[Tool
const GUARD_PASSES_PER_SCOPE: usize = 2;
type GuardInstructionResult = Result<
(
Option<SystemPrompt>,
Option<crate::project_context::InstructionAssembly>,
Vec<crate::project_context::InstructionNotice>,
),
crate::project_context::InstructionRefusal,
>;
fn replace_installed_instruction_block(
prompt: &mut Option<SystemPrompt>,
raw: &crate::project_context::ProjectContext,
@@ -1598,14 +1607,7 @@ pub(crate) fn guard_instruction_prefix(
mut assembly: Option<crate::project_context::InstructionAssembly>,
budgets: crate::project_context::InstructionBudgets,
tools: &[Tool],
) -> Result<
(
Option<SystemPrompt>,
Option<crate::project_context::InstructionAssembly>,
Vec<crate::project_context::InstructionNotice>,
),
crate::project_context::InstructionRefusal,
> {
) -> GuardInstructionResult {
use crate::project_context::{
InstructionAction, InstructionBudgetPolicy, InstructionLimit, InstructionNotice,
InstructionRefusal, InstructionScope,
+189 -189
View File
@@ -524,6 +524,195 @@ pub fn unset(path: &Path, key: &str) -> Result<()> {
})
}
pub(crate) fn set_provider_model_document(
doc: &mut toml_edit::DocumentMut,
identity: &ProviderIdentity,
model: &str,
) -> anyhow::Result<()> {
let mut config =
codewhale_runtime::config::parse_config_base(&doc.to_string()).map_err(|_| {
anyhow::anyhow!("Could not parse destination route identity; contents omitted")
})?;
crate::plugins::providers::apply_startup_providers(&mut config)?;
persistence::set_verified_provider_model_document(doc, &config, identity, model)
}
/// One persistent owner and atomic write for an explicitly saved route.
///
/// Also returns the undo for exactly this write, taken under the config lock,
/// so a caller whose follow-up apply step is rejected can take back only the
/// switch.
pub(crate) fn persist_provider_selection(
config_path: Option<&Path>,
identity: &ProviderIdentity,
model: Option<&str>,
) -> anyhow::Result<(std::path::PathBuf, codewhale_config::ConfigDocumentUndo)> {
let path = persistence::config_toml_path(config_path)?;
let ((), undo) =
codewhale_config::mutate_config_document_undoable_with_migration(&path, |doc, moved| {
persistence::migrate_legacy_route_preferences(&path, doc)?;
let mut config = codewhale_runtime::config::parse_config_after_locked_migration(
&doc.to_string(),
moved,
)
.map_err(|_| anyhow::anyhow!("Could not parse destination route; contents omitted"))?;
crate::plugins::providers::apply_startup_providers(&mut config)?;
config
.verify_provider_identity(identity)
.map_err(anyhow::Error::msg)?;
if let Some(model) = model {
persistence::set_verified_provider_model_document(doc, &config, identity, model)?;
}
persistence::set_document_value(
doc,
&["provider"],
identity.persisted_id().unwrap_or(identity.key.as_str()),
)?;
reconcile_root_model_aliases(doc, &config, identity)
})?;
Ok((path, undo))
}
/// Keep a root `default_text_model` alias from stranding a route switch,
/// without discarding the choice it holds.
///
/// The root alias is the *active* route's fallback: `Config` resolves it only
/// when the selected route has no model of its own. A switch that leaves the
/// incoming route without a leaf therefore hands it an alias naming the route
/// on its way out, and `Config::load` rejects the file the caller just wrote —
/// a committed switch that produces an unloadable config — or, on a
/// pass-through incoming route that loads it, a model the incoming route never
/// chose while the outgoing route comes back on its catalog default.
///
/// When the outgoing route has no leaf and was resolving the alias, relocate
/// that value onto the outgoing route's own canonical leaf, because it is real
/// saved state, and only then clear the alias. Any other alias the incoming
/// route already shadows with its own leaf is inert and stays: deleting a
/// saved choice to satisfy validation of a value nothing resolves is data
/// loss, not a repair. Likewise an unnamed custom route stores its
/// model in the alias itself and has no leaf to receive it. If the incoming
/// route cannot shadow that value, refuse the switch atomically and ask for an
/// explicit destination model instead of saving an unloadable configuration.
///
/// `previous` is the document's configuration before the switch; `incoming` is
/// the identity now selected. Every route writer calls this, so no writer can
/// keep a private rule about which route owns the root alias.
pub(crate) fn reconcile_root_model_aliases(
doc: &mut toml_edit::DocumentMut,
previous: &codewhale_runtime::config::Config,
incoming: &codewhale_runtime::config::ProviderIdentity,
) -> anyhow::Result<()> {
// A freshly admitted id-less root keeps its legacy alias as a receipt.
// Its effective model is written to the canonical leaf created by this
// same-lock migration; the alias is never another route's saved choice.
if incoming.provider == ProviderKind::Custom && incoming.persisted_id().is_none() {
return Ok(());
}
// The outgoing route was resolving the alias as its own model. Move it
// onto that route's leaf even when the incoming route could load it: a
// pass-through incoming route would otherwise inherit a model it never
// chose, and the outgoing route would come back on its catalog default.
if let Some((outgoing, value)) = previous.root_model_alias_owned_by_outgoing(incoming) {
persistence::set_verified_provider_model_document(doc, previous, &outgoing, &value)?;
unset_root_model_aliases(doc)?;
return Ok(());
}
// Below, only `default_text_model` is read. The legacy root `model` key is
// never what blocks a load: `Config::default_model` already refuses to
// route a foreign legacy value to a provider that cannot serve it, and
// `Config::validate` does not consult it, so relocating it would move a
// value nothing is asking about. It is still cleared together with the
// alias that shadows it (`unset_root_model_aliases`).
const ROOT_KEY: &str = "default_text_model";
let Some(value) = doc
.get(ROOT_KEY)
.and_then(toml_edit::Item::as_str)
.map(str::to_owned)
else {
return Ok(());
};
let mut switched = codewhale_runtime::config::parse_config_base(&doc.to_string())
.map_err(|_| anyhow::anyhow!("Could not parse switched route; contents omitted"))?;
crate::plugins::providers::apply_startup_providers(&mut switched)?;
// `Config::validate` is the single authority on what the incoming route can
// serve, so a writer cannot disagree with the loader. Act only when this
// alias is what the loader rejects: a document already broken for an
// unrelated reason is not this writer's to rewrite.
let mut without_alias = switched.clone();
without_alias.default_text_model = None;
if switched
.provider_config_for(incoming)
.and_then(|entry| entry.model.as_deref())
.is_some()
|| switched.validate().is_ok()
|| without_alias.validate().is_err()
{
return Ok(());
}
// An empty or control-bearing alias names no saved model. Nothing to
// relocate, and clearing it loses nothing.
if value.trim().is_empty() || value.chars().any(char::is_control) {
unset_root_model_aliases(doc)?;
return Ok(());
}
// No leaf can hold this value. Keep the only copy of the user's choice
// rather than discard it for a document the incoming route loads as soon as
// it saves a model of its own.
let outgoing = previous.active_provider_identity().ok();
if outgoing.as_ref().is_some_and(|outgoing| {
outgoing != incoming
&& outgoing.provider == ProviderKind::Custom
&& outgoing.persisted_id().is_none()
}) {
// This same-lock canonicalizer copied the root model before moving
// its endpoint. Clear an alias only when that exact outgoing leaf
// proves the choice survives; otherwise retain the refusal.
if outgoing.as_ref().is_some_and(|outgoing| {
previous
.provider_config_for(outgoing)
.and_then(|entry| entry.model.as_deref())
== Some(value.as_str())
}) {
return unset_root_model_aliases(doc);
}
anyhow::bail!(
"Choose a model for the destination provider before switching from a legacy custom connection; the saved configuration was not changed"
);
}
let Some(outgoing) = outgoing.as_ref().filter(|outgoing| *outgoing != incoming) else {
return Ok(());
};
let mut scoped = switched;
scoped
.scope_to_provider_identity(outgoing)
.map_err(anyhow::Error::msg)?;
if scoped
.provider_config_for(outgoing)
.and_then(|entry| entry.model.as_deref())
.is_none()
{
persistence::set_verified_provider_model_document(doc, previous, outgoing, &value)?;
}
// The outgoing route either already saved its own choice or has just
// received this one, so the alias is now a shadowed duplicate that only
// blocks the incoming route.
unset_root_model_aliases(doc)?;
Ok(())
}
/// Clear the root model alias: `default_text_model` and the legacy root
/// `model` that `Config::default_model` falls back to when the former is
/// unset. While `default_text_model` is present the legacy key resolves on no
/// route, so removing only the alias would resurrect a stale legacy model on
/// the incoming route (a pass-through route would then use a model nobody
/// chose for it). When the legacy key *is* the alias, the caller has already
/// moved its value onto the outgoing route's leaf.
fn unset_root_model_aliases(doc: &mut toml_edit::DocumentMut) -> anyhow::Result<()> {
persistence::unset_document_value(doc, &["default_text_model"])?;
persistence::unset_document_value(doc, &["model"])?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -899,192 +1088,3 @@ model = "Other-X"
Ok(())
}
}
pub(crate) fn set_provider_model_document(
doc: &mut toml_edit::DocumentMut,
identity: &ProviderIdentity,
model: &str,
) -> anyhow::Result<()> {
let mut config =
codewhale_runtime::config::parse_config_base(&doc.to_string()).map_err(|_| {
anyhow::anyhow!("Could not parse destination route identity; contents omitted")
})?;
crate::plugins::providers::apply_startup_providers(&mut config)?;
persistence::set_verified_provider_model_document(doc, &config, identity, model)
}
/// One persistent owner and atomic write for an explicitly saved route.
///
/// Also returns the undo for exactly this write, taken under the config lock,
/// so a caller whose follow-up apply step is rejected can take back only the
/// switch.
pub(crate) fn persist_provider_selection(
config_path: Option<&Path>,
identity: &ProviderIdentity,
model: Option<&str>,
) -> anyhow::Result<(std::path::PathBuf, codewhale_config::ConfigDocumentUndo)> {
let path = persistence::config_toml_path(config_path)?;
let ((), undo) =
codewhale_config::mutate_config_document_undoable_with_migration(&path, |doc, moved| {
persistence::migrate_legacy_route_preferences(&path, doc)?;
let mut config = codewhale_runtime::config::parse_config_after_locked_migration(
&doc.to_string(),
moved,
)
.map_err(|_| anyhow::anyhow!("Could not parse destination route; contents omitted"))?;
crate::plugins::providers::apply_startup_providers(&mut config)?;
config
.verify_provider_identity(identity)
.map_err(anyhow::Error::msg)?;
if let Some(model) = model {
persistence::set_verified_provider_model_document(doc, &config, identity, model)?;
}
persistence::set_document_value(
doc,
&["provider"],
identity.persisted_id().unwrap_or(identity.key.as_str()),
)?;
reconcile_root_model_aliases(doc, &config, identity)
})?;
Ok((path, undo))
}
/// Keep a root `default_text_model` alias from stranding a route switch,
/// without discarding the choice it holds.
///
/// The root alias is the *active* route's fallback: `Config` resolves it only
/// when the selected route has no model of its own. A switch that leaves the
/// incoming route without a leaf therefore hands it an alias naming the route
/// on its way out, and `Config::load` rejects the file the caller just wrote —
/// a committed switch that produces an unloadable config — or, on a
/// pass-through incoming route that loads it, a model the incoming route never
/// chose while the outgoing route comes back on its catalog default.
///
/// When the outgoing route has no leaf and was resolving the alias, relocate
/// that value onto the outgoing route's own canonical leaf, because it is real
/// saved state, and only then clear the alias. Any other alias the incoming
/// route already shadows with its own leaf is inert and stays: deleting a
/// saved choice to satisfy validation of a value nothing resolves is data
/// loss, not a repair. Likewise an unnamed custom route stores its
/// model in the alias itself and has no leaf to receive it. If the incoming
/// route cannot shadow that value, refuse the switch atomically and ask for an
/// explicit destination model instead of saving an unloadable configuration.
///
/// `previous` is the document's configuration before the switch; `incoming` is
/// the identity now selected. Every route writer calls this, so no writer can
/// keep a private rule about which route owns the root alias.
pub(crate) fn reconcile_root_model_aliases(
doc: &mut toml_edit::DocumentMut,
previous: &codewhale_runtime::config::Config,
incoming: &codewhale_runtime::config::ProviderIdentity,
) -> anyhow::Result<()> {
// A freshly admitted id-less root keeps its legacy alias as a receipt.
// Its effective model is written to the canonical leaf created by this
// same-lock migration; the alias is never another route's saved choice.
if incoming.provider == ProviderKind::Custom && incoming.persisted_id().is_none() {
return Ok(());
}
// The outgoing route was resolving the alias as its own model. Move it
// onto that route's leaf even when the incoming route could load it: a
// pass-through incoming route would otherwise inherit a model it never
// chose, and the outgoing route would come back on its catalog default.
if let Some((outgoing, value)) = previous.root_model_alias_owned_by_outgoing(incoming) {
persistence::set_verified_provider_model_document(doc, previous, &outgoing, &value)?;
unset_root_model_aliases(doc)?;
return Ok(());
}
// Below, only `default_text_model` is read. The legacy root `model` key is
// never what blocks a load: `Config::default_model` already refuses to
// route a foreign legacy value to a provider that cannot serve it, and
// `Config::validate` does not consult it, so relocating it would move a
// value nothing is asking about. It is still cleared together with the
// alias that shadows it (`unset_root_model_aliases`).
const ROOT_KEY: &str = "default_text_model";
let Some(value) = doc
.get(ROOT_KEY)
.and_then(toml_edit::Item::as_str)
.map(str::to_owned)
else {
return Ok(());
};
let mut switched = codewhale_runtime::config::parse_config_base(&doc.to_string())
.map_err(|_| anyhow::anyhow!("Could not parse switched route; contents omitted"))?;
crate::plugins::providers::apply_startup_providers(&mut switched)?;
// `Config::validate` is the single authority on what the incoming route can
// serve, so a writer cannot disagree with the loader. Act only when this
// alias is what the loader rejects: a document already broken for an
// unrelated reason is not this writer's to rewrite.
let mut without_alias = switched.clone();
without_alias.default_text_model = None;
if switched
.provider_config_for(incoming)
.and_then(|entry| entry.model.as_deref())
.is_some()
|| switched.validate().is_ok()
|| without_alias.validate().is_err()
{
return Ok(());
}
// An empty or control-bearing alias names no saved model. Nothing to
// relocate, and clearing it loses nothing.
if value.trim().is_empty() || value.chars().any(char::is_control) {
unset_root_model_aliases(doc)?;
return Ok(());
}
// No leaf can hold this value. Keep the only copy of the user's choice
// rather than discard it for a document the incoming route loads as soon as
// it saves a model of its own.
let outgoing = previous.active_provider_identity().ok();
if outgoing.as_ref().is_some_and(|outgoing| {
outgoing != incoming
&& outgoing.provider == ProviderKind::Custom
&& outgoing.persisted_id().is_none()
}) {
// This same-lock canonicalizer copied the root model before moving
// its endpoint. Clear an alias only when that exact outgoing leaf
// proves the choice survives; otherwise retain the refusal.
if outgoing.as_ref().is_some_and(|outgoing| {
previous
.provider_config_for(outgoing)
.and_then(|entry| entry.model.as_deref())
== Some(value.as_str())
}) {
return unset_root_model_aliases(doc);
}
anyhow::bail!(
"Choose a model for the destination provider before switching from a legacy custom connection; the saved configuration was not changed"
);
}
let Some(outgoing) = outgoing.as_ref().filter(|outgoing| *outgoing != incoming) else {
return Ok(());
};
let mut scoped = switched;
scoped
.scope_to_provider_identity(outgoing)
.map_err(anyhow::Error::msg)?;
if scoped
.provider_config_for(outgoing)
.and_then(|entry| entry.model.as_deref())
.is_none()
{
persistence::set_verified_provider_model_document(doc, previous, outgoing, &value)?;
}
// The outgoing route either already saved its own choice or has just
// received this one, so the alias is now a shadowed duplicate that only
// blocks the incoming route.
unset_root_model_aliases(doc)?;
Ok(())
}
/// Clear the root model alias: `default_text_model` and the legacy root
/// `model` that `Config::default_model` falls back to when the former is
/// unset. While `default_text_model` is present the legacy key resolves on no
/// route, so removing only the alias would resurrect a stale legacy model on
/// the incoming route (a pass-through route would then use a model nobody
/// chose for it). When the legacy key *is* the alias, the caller has already
/// moved its value onto the outgoing route's leaf.
fn unset_root_model_aliases(doc: &mut toml_edit::DocumentMut) -> anyhow::Result<()> {
persistence::unset_document_value(doc, &["default_text_model"])?;
persistence::unset_document_value(doc, &["model"])?;
Ok(())
}
+4 -1
View File
@@ -17147,7 +17147,10 @@ impl RuntimeThreadManager {
submission_id: None,
};
let op = match &input_source {
RuntimeTurnInputSource::Review(request) => Op::Review { turn: spec, request: request.clone() },
RuntimeTurnInputSource::Review(request) => Op::Review {
turn: Box::new(spec),
request: request.clone(),
},
_ => Op::SendMessage(spec),
};
+1 -1
View File
@@ -20229,7 +20229,7 @@ fn restart_rebuild_restores_tool_call_identity_from_persisted_items() -> Result<
let turn = manager.store.load_turn(&checkpoint_turn.id)?;
let items = manager.prepared_items_for_turns(std::slice::from_ref(&turn))?;
assert_eq!(
manager.history_from_records(&[turn.clone()], &items)?,
manager.history_from_records(std::slice::from_ref(&turn), &items)?,
expected
);
first.metadata.as_mut().unwrap()["tool_input"] = json!(r#"{"path":"changed"}"#);