Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/cc-config/src/runtime_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ pub struct SettingsJson {
pub effort_level: Option<String>,
pub fast_mode: Option<bool>,
pub fast_mode_per_session_opt_in: Option<bool>,
/// Optional advisor model id (issue #33). Persisted under
/// `settings.json::advisorModel`. When set and the active provider
/// supports advisors, this model is attached to the Messages request
/// via `MessagesRequest::advisor_model`.
pub advisor_model: Option<String>,

// -- Modes / integrations ------------------------------------------
pub teammate_mode: Option<bool>,
Expand Down
9 changes: 9 additions & 0 deletions crates/cc-config/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ pub struct RawSettings {
pub effort_level: Option<String>,
pub fast_mode: Option<bool>,
pub fast_mode_per_session_opt_in: Option<bool>,
/// Stronger secondary model used as an advisor (issue #33).
/// Persisted under `advisorModel`. Only honored by providers that
/// advertise advisor support; others log a warning and ignore it.
pub advisor_model: Option<String>,

// -- Modes / integrations ------------------------------------------
pub teammate_mode: Option<bool>,
Expand Down Expand Up @@ -516,6 +520,7 @@ impl RawSettings {
"claudeInChromeDefaultEnabled"
);
merge_opt!(auto_memory_enabled, "autoMemoryEnabled");
merge_opt!(advisor_model, "advisorModel");
merge_opt!(system_prompt, "systemPrompt");
merge_opt!(api_key, "apiKey");

Expand Down Expand Up @@ -709,6 +714,8 @@ pub struct EffectiveSettings {
pub teammate_mode: Option<bool>,
/// Auto-memory toggle (issue #45). `None` means "inherit default" (off).
pub auto_memory_enabled: Option<bool>,
/// Advisor model id (issue #33).
pub advisor_model: Option<String>,
}

impl EffectiveSettings {
Expand Down Expand Up @@ -751,6 +758,7 @@ impl EffectiveSettings {
fast_mode_per_session_opt_in: raw.fast_mode_per_session_opt_in,
teammate_mode: raw.teammate_mode,
auto_memory_enabled: raw.auto_memory_enabled,
advisor_model: raw.advisor_model,
}
}
}
Expand Down Expand Up @@ -1292,6 +1300,7 @@ pub fn settings_schema() -> Value {
"teammateMode": { "type": "boolean" },
"claudeInChromeDefaultEnabled": { "type": "boolean" },
"autoMemoryEnabled": { "type": "boolean" },
"advisorModel": { "type": "string" },
"systemPrompt": { "type": "string" },
"apiKey": { "type": "string" }
}
Expand Down
1 change: 1 addition & 0 deletions crates/claude-code-rs/src/api/bedrock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let raw = to_bedrock_body(&req).unwrap();
let v: Value = serde_json::from_slice(&raw).unwrap();
Expand Down
23 changes: 23 additions & 0 deletions crates/claude-code-rs/src/api/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ pub struct MessagesRequest {
pub thinking: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<Value>,
/// Optional advisor model id (issue #33). Carried through the request
/// pipeline only for providers that advertise advisor support
/// (see [`provider_supports_advisor`]). Serialized as `advisor_model`;
/// omitted when `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub advisor_model: Option<String>,
}

/// Return `true` when the given provider supports the advisor-model field.
///
/// Only the Anthropic Messages API currently recognizes `advisor_model`.
/// For Bedrock/Vertex (which ultimately reach the same Anthropic shape) we
/// also pass it through; OpenAI-compatible and Google providers don't have
/// the field in their native schema, so we drop it there and the `/advisor`
/// command surfaces an "inactive" message.
pub fn provider_supports_advisor(provider: &ApiProvider) -> bool {
matches!(
provider,
ApiProvider::Anthropic { .. }
| ApiProvider::Azure { .. }
| ApiProvider::Bedrock { .. }
| ApiProvider::Vertex { .. }
)
}

/// API client configuration
Expand Down
46 changes: 45 additions & 1 deletion crates/claude-code-rs/src/api/client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,15 +722,17 @@ fn test_messages_request_serialization() {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};

let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["model"], "claude-sonnet-4-20250514");
assert_eq!(json["max_tokens"], 1024);
assert_eq!(json["stream"], true);
// thinking and tool_choice should be omitted when None
// thinking, tool_choice and advisor_model should be omitted when None
assert!(json.get("thinking").is_none());
assert!(json.get("tool_choice").is_none());
assert!(json.get("advisor_model").is_none());
}

#[test]
Expand All @@ -746,10 +748,52 @@ fn test_messages_request_with_thinking() {
stream: true,
thinking: Some(serde_json::json!({"type": "enabled", "budget_tokens": 2048})),
tool_choice: None,
advisor_model: None,
};

let json = serde_json::to_value(&req).unwrap();
assert!(json.get("thinking").is_some());
assert_eq!(json["thinking"]["type"], "enabled");
assert!(json.get("system").is_some());
}

#[test]
fn test_messages_request_advisor_model_serializes_when_set() {
let req = MessagesRequest {
model: "claude-sonnet-4-20250514".to_string(),
messages: vec![],
system: None,
max_tokens: 1024,
tools: None,
stream: true,
thinking: None,
tool_choice: None,
advisor_model: Some("claude-opus-4-20250514".to_string()),
};

let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["advisor_model"], "claude-opus-4-20250514");
}

#[test]
fn test_provider_supports_advisor_matrix() {
use crate::api::client::{provider_supports_advisor, ApiProvider};
assert!(provider_supports_advisor(&ApiProvider::Anthropic {
api_key: "k".into(),
base_url: None,
}));
assert!(provider_supports_advisor(&ApiProvider::Azure {
endpoint: "e".into(),
api_key: "k".into(),
}));
assert!(!provider_supports_advisor(&ApiProvider::OpenAiCompat {
name: "openai".into(),
api_key: "k".into(),
base_url: "u".into(),
default_model: "m".into(),
}));
assert!(!provider_supports_advisor(&ApiProvider::Google {
api_key: "k".into(),
base_url: "u".into(),
}));
}
5 changes: 5 additions & 0 deletions crates/claude-code-rs/src/api/google_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_gemini_request(&req);
assert_eq!(body["generationConfig"]["maxOutputTokens"], 1024);
Expand All @@ -361,6 +362,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_gemini_request(&req);
assert_eq!(
Expand All @@ -384,6 +386,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_gemini_request(&req);
let contents = body["contents"].as_array().unwrap();
Expand All @@ -408,6 +411,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_gemini_request(&req);
let contents = body["contents"].as_array().unwrap();
Expand Down Expand Up @@ -437,6 +441,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_gemini_request(&req);
let contents = body["contents"].as_array().unwrap();
Expand Down
4 changes: 4 additions & 0 deletions crates/claude-code-rs/src/api/openai_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_openai_request(&req, "openai");
assert_eq!(body["model"], "gpt-4o");
Expand Down Expand Up @@ -660,6 +661,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_openai_request(&req, OPENAI_CODEX_PROVIDER_NAME);
assert_eq!(body["model"], "gpt-5.4");
Expand All @@ -680,6 +682,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_openai_request(&req, "deepseek");
let messages = body["messages"].as_array().unwrap();
Expand All @@ -706,6 +709,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let body = build_openai_request(&req, "openai");
let messages = body["messages"].as_array().unwrap();
Expand Down
1 change: 1 addition & 0 deletions crates/claude-code-rs/src/api/vertex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ mod tests {
stream: true,
thinking: None,
tool_choice: None,
advisor_model: None,
};
let raw = to_vertex_body(&req).unwrap();
let v: Value = serde_json::from_slice(&raw).unwrap();
Expand Down
Loading
Loading