Skip to content

Commit d3f4e93

Browse files
committed
fix(kiro): support sonnet 5 effort aliases
1 parent ba9359f commit d3f4e93

6 files changed

Lines changed: 114 additions & 24 deletions

File tree

crates/llm-access-kiro/src/anthropic/converter/identity.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@ use crate::anthropic::types::MessagesRequest;
1212
const MODEL_IDENTITY_PREFIX: &str = "You are powered by the model named ";
1313
const MODEL_IDENTITY_DELIMITER: &str = ". The exact model ID is ";
1414
const CLAUDE_CODE_MEMORY_PATH_MARKER: &str = "/.claude/projects/";
15+
const SONNET_5_EFFORTS: [&str; 5] = ["low", "medium", "high", "xhigh", "max"];
1516

1617
fn requested_model_identity_id(model: &str) -> &str {
1718
model.strip_suffix("-thinking").unwrap_or(model)
1819
}
1920

2021
fn requested_model_identity_name(model: &str) -> Option<&'static str> {
21-
match requested_model_identity_id(model) {
22-
"claude-sonnet-5" => Some("Sonnet 5"),
22+
let model_id = requested_model_identity_id(model);
23+
if model_id == "claude-sonnet-5"
24+
|| model_id
25+
.strip_prefix("claude-sonnet-5-thinking-")
26+
.is_some_and(|suffix| SONNET_5_EFFORTS.contains(&suffix))
27+
|| model_id
28+
.strip_prefix("claude-sonnet-5-")
29+
.is_some_and(|suffix| SONNET_5_EFFORTS.contains(&suffix))
30+
{
31+
return Some("Sonnet 5");
32+
}
33+
match model_id {
2334
"claude-opus-4-8" => Some("Opus 4.8"),
2435
"claude-opus-4-7" => Some("Opus 4.7"),
2536
"claude-opus-4-6" => Some("Opus 4.6"),

crates/llm-access-kiro/src/anthropic/converter/mod.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,15 @@ mod tests {
813813

814814
#[test]
815815
fn convert_request_uses_sonnet_5_upstream_model_id() {
816-
for public_model in ["claude-sonnet-5", "claude-sonnet-5-thinking"] {
816+
for public_model in [
817+
"claude-sonnet-5",
818+
"claude-sonnet-5-thinking",
819+
"claude-sonnet-5-low",
820+
"claude-sonnet-5-medium",
821+
"claude-sonnet-5-high",
822+
"claude-sonnet-5-xhigh",
823+
"claude-sonnet-5-max",
824+
] {
817825
let mut req = base_request(vec![AnthropicMessage {
818826
role: "user".to_string(),
819827
content: serde_json::json!("Hello"),
@@ -1798,24 +1806,26 @@ mod tests {
17981806

17991807
#[test]
18001808
fn convert_request_injects_sonnet_5_model_identity() {
1801-
let mut req = base_request(vec![AnthropicMessage {
1802-
role: "user".to_string(),
1803-
content: serde_json::json!("Hello"),
1804-
}]);
1805-
req.model = "claude-sonnet-5".to_string();
1806-
req.system = Some(vec![SystemMessage {
1807-
text: "You are Claude Code, Anthropic's official CLI for Claude.".to_string(),
1808-
}]);
1809+
for model in ["claude-sonnet-5", "claude-sonnet-5-max", "claude-sonnet-5-thinking-max"] {
1810+
let mut req = base_request(vec![AnthropicMessage {
1811+
role: "user".to_string(),
1812+
content: serde_json::json!("Hello"),
1813+
}]);
1814+
req.model = model.to_string();
1815+
req.system = Some(vec![SystemMessage {
1816+
text: "You are Claude Code, Anthropic's official CLI for Claude.".to_string(),
1817+
}]);
18091818

1810-
let result = convert_request(&req).expect("conversion should succeed");
1811-
let system_prefix = match &result.conversation_state.history[0] {
1812-
Message::User(message) => &message.user_input_message.content,
1813-
other => panic!("expected injected system user message, got {other:?}"),
1814-
};
1819+
let result = convert_request(&req).expect("conversion should succeed");
1820+
let system_prefix = match &result.conversation_state.history[0] {
1821+
Message::User(message) => &message.user_input_message.content,
1822+
other => panic!("expected injected system user message, got {other:?}"),
1823+
};
18151824

1816-
assert!(system_prefix.contains(
1817-
"You are powered by the model named Sonnet 5. The exact model ID is claude-sonnet-5."
1818-
));
1825+
assert!(system_prefix.contains(&format!(
1826+
"You are powered by the model named Sonnet 5. The exact model ID is {model}."
1827+
)));
1828+
}
18191829
}
18201830

18211831
#[test]

crates/llm-access-kiro/src/anthropic/converter/model.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
//! Model-id mapping and per-model context-window sizing.
22
3+
const SONNET_5_EFFORTS: [&str; 5] = ["low", "medium", "high", "xhigh", "max"];
4+
5+
fn is_sonnet_5_public_model(model: &str) -> bool {
6+
model == "claude-sonnet-5"
7+
|| model == "claude-sonnet-5-thinking"
8+
|| model
9+
.strip_prefix("claude-sonnet-5-")
10+
.is_some_and(|suffix| SONNET_5_EFFORTS.contains(&suffix))
11+
|| model
12+
.strip_prefix("claude-sonnet-5-thinking-")
13+
.is_some_and(|suffix| SONNET_5_EFFORTS.contains(&suffix))
14+
}
315

416
/// Maps an Anthropic model name (e.g. `"claude-sonnet-4-6"`) to the
517
/// canonical Kiro model identifier. Returns `None` for unrecognized models.
618
pub fn map_model(model: &str) -> Option<String> {
719
let model = model.to_lowercase();
820
let normalized = model.replace('.', "-");
9-
if normalized == "claude-sonnet-5" || normalized == "claude-sonnet-5-thinking" {
21+
if is_sonnet_5_public_model(&normalized) {
1022
return Some("claude-sonnet-5".to_string());
1123
}
1224
if model.contains("sonnet") {
@@ -58,8 +70,10 @@ mod tests {
5870
fn get_context_window_size_matches_latest_kiro_model_rules() {
5971
assert_eq!(map_model("claude-sonnet-5"), Some("claude-sonnet-5".to_string()));
6072
assert_eq!(map_model("claude-sonnet-5-thinking"), Some("claude-sonnet-5".to_string()));
73+
assert_eq!(map_model("claude-sonnet-5-max"), Some("claude-sonnet-5".to_string()));
6174
assert_eq!(get_context_window_size("claude-sonnet-5"), 1_000_000);
6275
assert_eq!(get_context_window_size("claude-sonnet-5-thinking"), 1_000_000);
76+
assert_eq!(get_context_window_size("claude-sonnet-5-max"), 1_000_000);
6377
assert_eq!(get_context_window_size("claude-sonnet-4-6"), 1_000_000);
6478
assert_eq!(get_context_window_size("claude-opus-4-20250514"), 1_000_000);
6579
assert_eq!(map_model("claude-opus-4-8"), Some("claude-opus-4.8".to_string()));

crates/llm-access-kiro/src/anthropic/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ pub mod websearch;
1010
use self::types::{CountTokensRequest, CountTokensResponse, Model, ModelsResponse};
1111
use crate::token;
1212

13-
const SUPPORTED_MODEL_CATALOG: [(&str, &str, i64); 16] = [
13+
const SUPPORTED_MODEL_CATALOG: [(&str, &str, i64); 21] = [
1414
("claude-sonnet-5", "Claude Sonnet 5", 1782777600),
1515
("claude-sonnet-5-thinking", "Claude Sonnet 5 (Thinking)", 1782777600),
16+
("claude-sonnet-5-low", "Claude Sonnet 5 (Low Effort)", 1782777600),
17+
("claude-sonnet-5-medium", "Claude Sonnet 5 (Medium Effort)", 1782777600),
18+
("claude-sonnet-5-high", "Claude Sonnet 5 (High Effort)", 1782777600),
19+
("claude-sonnet-5-xhigh", "Claude Sonnet 5 (XHigh Effort)", 1782777600),
20+
("claude-sonnet-5-max", "Claude Sonnet 5 (Max Effort)", 1782777600),
1621
("claude-sonnet-4-5-20250929", "Claude Sonnet 4.5", 1727568000),
1722
("claude-sonnet-4-5-20250929-thinking", "Claude Sonnet 4.5 (Thinking)", 1727568000),
1823
("claude-opus-4-5-20251101", "Claude Opus 4.5", 1730419200),

crates/llm-access/src/provider/kiro_model.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use super::{
2828
kiro_session_affinity::KiroSessionAffinity, KiroCacheContext, KIRO_REQUEST_SESSION_ID_HEADERS,
2929
};
3030

31+
const SONNET_5_EFFORTS: [&str; 5] = ["low", "medium", "high", "xhigh", "max"];
32+
3133
pub fn apply_kiro_model_mapping(
3234
model_name_map_json: &str,
3335
payload: &mut MessagesRequest,
@@ -47,11 +49,24 @@ pub fn apply_kiro_model_mapping(
4749
payload.model = target_model.clone();
4850
Ok(Some((source_model, target_model)))
4951
}
52+
53+
fn sonnet_5_effort_from_model_name(model: &str) -> Option<&'static str> {
54+
let suffix = model
55+
.strip_prefix("claude-sonnet-5-thinking-")
56+
.or_else(|| model.strip_prefix("claude-sonnet-5-"))?;
57+
SONNET_5_EFFORTS
58+
.iter()
59+
.copied()
60+
.find(|effort| *effort == suffix)
61+
}
62+
5063
pub fn override_kiro_thinking_from_model_name(payload: &mut MessagesRequest) {
5164
let model = payload.model.to_lowercase();
5265
let normalized_model = model.replace('.', "-");
53-
let is_sonnet_5 =
54-
normalized_model == "claude-sonnet-5" || normalized_model == "claude-sonnet-5-thinking";
66+
let sonnet_5_effort = sonnet_5_effort_from_model_name(&normalized_model);
67+
let is_sonnet_5 = normalized_model == "claude-sonnet-5"
68+
|| normalized_model == "claude-sonnet-5-thinking"
69+
|| sonnet_5_effort.is_some();
5570
if !model.contains("thinking") && !is_sonnet_5 {
5671
return;
5772
}
@@ -73,7 +88,9 @@ pub fn override_kiro_thinking_from_model_name(payload: &mut MessagesRequest) {
7388
effort: None,
7489
format: None,
7590
});
76-
if output_config.effort.is_none() {
91+
if let Some(effort) = sonnet_5_effort {
92+
output_config.effort = Some(effort.to_string());
93+
} else if output_config.effort.is_none() {
7794
output_config.effort = Some(if is_sonnet_5 { "high" } else { "xhigh" }.to_string());
7895
}
7996
}

crates/llm-access/src/provider/tests.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,6 +2588,34 @@ fn override_kiro_thinking_preserves_sonnet_5_effort_ladder() {
25882588
}
25892589
}
25902590

2591+
#[test]
2592+
fn override_kiro_thinking_sets_sonnet_5_effort_from_model_aliases() {
2593+
for effort in ["low", "medium", "high", "xhigh", "max"] {
2594+
let mut payload: llm_access_kiro::anthropic::types::MessagesRequest =
2595+
serde_json::from_value(json!({
2596+
"model": format!("claude-sonnet-5-{effort}"),
2597+
"max_tokens": 64,
2598+
"messages": [{
2599+
"role": "user",
2600+
"content": "hello"
2601+
}]
2602+
}))
2603+
.expect("request should deserialize");
2604+
2605+
super::override_kiro_thinking_from_model_name(&mut payload);
2606+
2607+
let thinking = payload.thinking.expect("thinking should be populated");
2608+
assert_eq!(thinking.thinking_type, "adaptive");
2609+
assert_eq!(
2610+
payload
2611+
.output_config
2612+
.and_then(|config| config.effort)
2613+
.as_deref(),
2614+
Some(effort)
2615+
);
2616+
}
2617+
}
2618+
25912619
#[test]
25922620
fn normalize_kiro_kmodel_name_maps_opus_dot_names_back_to_public_names() {
25932621
assert_eq!(super::normalize_kiro_kmodel_name("claude-opus-4.8"), "claude-opus-4-8");
@@ -7025,6 +7053,11 @@ async fn kiro_models_fetches_local_catalog_on_root_models_route() {
70257053
.collect::<Vec<_>>();
70267054
assert!(ids.contains(&"claude-sonnet-5"));
70277055
assert!(ids.contains(&"claude-sonnet-5-thinking"));
7056+
assert!(ids.contains(&"claude-sonnet-5-low"));
7057+
assert!(ids.contains(&"claude-sonnet-5-medium"));
7058+
assert!(ids.contains(&"claude-sonnet-5-high"));
7059+
assert!(ids.contains(&"claude-sonnet-5-xhigh"));
7060+
assert!(ids.contains(&"claude-sonnet-5-max"));
70287061
assert!(ids.contains(&"claude-opus-4-7"));
70297062
assert!(ids.contains(&"claude-opus-4-7-thinking"));
70307063
assert!(ids.contains(&"claude-opus-4-8"));

0 commit comments

Comments
 (0)