1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Runtime selection model for internal agents: which harness runs an agent,
//! and the settings only that harness understands.
//!
//! The on-disk shape of these selections lives in `config_format`.
use {crate::model_aliases::ModelAliases, rho_providers::reasoning::ReasoningLevel};
/// Provider selection for an internal agent that runs on Rho's own stack.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RhoInternalAgentModel {
pub provider: String,
pub model: String,
pub auth: String,
pub(super) model_alias: Option<String>,
}
/// Which harness runs an internal agent, together with the settings only that
/// harness understands.
///
/// The runtime axis travels as one value, mirroring `AgentRuntimeSpec`, so a
/// stored selection cannot pair one harness with another harness's model
/// vocabulary or auth.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InternalAgentTarget {
/// Rho's own provider stack, with an explicit provider, model, and auth.
Rho(RhoInternalAgentModel),
/// The installed `claude` binary under the user's Claude Code sign-in.
/// Rho holds no credential for it and resolves no model name.
ClaudeCli {
/// Pass-through `--model`. `None` omits the flag and lets Claude Code
/// pick.
model: Option<String>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InternalAgentModelConfig {
pub target: InternalAgentTarget,
/// Per-agent reasoning override. `None` keeps the agent definition default.
pub reasoning: Option<ReasoningLevel>,
}
impl InternalAgentModelConfig {
/// A selection on Rho's own runtime.
pub fn new(provider: String, model: String, auth: String) -> Self {
Self {
target: InternalAgentTarget::Rho(RhoInternalAgentModel {
provider,
model,
auth,
model_alias: None,
}),
reasoning: None,
}
}
/// A selection that delegates to the Claude Code CLI. `model` is passed
/// through as `--model`; `None` lets Claude Code choose.
pub fn claude_cli(model: Option<String>) -> Self {
Self {
target: InternalAgentTarget::ClaudeCli { model },
reasoning: None,
}
}
/// The Rho provider selection, or `None` when this agent delegates.
pub fn rho(&self) -> Option<&RhoInternalAgentModel> {
match &self.target {
InternalAgentTarget::Rho(model) => Some(model),
InternalAgentTarget::ClaudeCli { .. } => None,
}
}
/// How the selection reads on a status line, badge, or picker row.
///
/// Both runtimes render as `<source>/<model>` so one row format covers the
/// whole list.
pub fn display_reference(&self) -> String {
match &self.target {
InternalAgentTarget::Rho(selection) => {
rho_providers::provider::model_reference(&selection.provider, &selection.model)
}
InternalAgentTarget::ClaudeCli { model } => rho_providers::provider::model_reference(
crate::claude_runtime::models::CLAUDE_CODE_SOURCE_LABEL,
model
.as_deref()
.unwrap_or(crate::claude_runtime::models::CLAUDE_DEFAULT_MODEL_BADGE),
),
}
}
/// The Rho selection, for assertions that only make sense on Rho's stack.
#[cfg(test)]
pub fn expect_rho(&self) -> &RhoInternalAgentModel {
self.rho().expect("selection runs on the rho runtime")
}
/// The Rho selection for mutation in tests.
#[cfg(test)]
pub fn expect_rho_mut(&mut self) -> &mut RhoInternalAgentModel {
match &mut self.target {
InternalAgentTarget::Rho(model) => model,
InternalAgentTarget::ClaudeCli { .. } => {
panic!("selection runs on the rho runtime")
}
}
}
pub(super) fn current_alias<'a>(&'a self, aliases: &'a ModelAliases) -> Option<&'a str> {
let selection = self.rho()?;
let name = selection.model_alias.as_deref()?;
let target = aliases.get(name)?;
(target.model == selection.model
&& target.provider.as_deref().unwrap_or(&selection.provider) == selection.provider)
.then_some(name)
}
}