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
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ updates:
patterns: ["*"]
update-types: ["minor", "patch"]
ignore:
# The Claude Agent SDK is deliberately exact-pinned (0.3.190) because its
# The Claude Agent SDK is deliberately exact-pinned (0.3.282) because its
# pre-1.0 API drifts. Below 1.0 semver gives NO stability guarantee for
# patch releases either — a 0.3.x → 0.3.y bump of this fast-moving agent
# SDK can carry breaking changes, and the engine is the load-bearing
Expand Down
36 changes: 18 additions & 18 deletions apps/desktop/src-tauri/src/contracts/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "start-session",
"prompt": "do the thing",
"providerId": "claude",
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"autonomy": "auto-accept",
"cwd": "/tmp/work",
Expand Down Expand Up @@ -73,7 +73,7 @@
"set-model": {
"sessionId": 3,
"type": "set-model",
"model": "claude-sonnet-4-6"
"model": "claude-sonnet-5"
},
"set-autonomy": {
"sessionId": 4,
Expand Down Expand Up @@ -113,7 +113,7 @@
"bugs",
"security"
],
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"maxConcurrency": 3,
"maxTurnsPerCategory": 40,
Expand All @@ -137,7 +137,7 @@
"folder-structure",
"imports-boundaries"
],
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"maxConcurrency": 3,
"maxTurnsPerCategory": 40,
Expand All @@ -161,7 +161,7 @@
"tests",
"security"
],
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"maxConcurrency": 3,
"maxTurnsPerDimension": 40,
Expand All @@ -188,7 +188,7 @@
"tests",
"contracts"
],
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"maxConcurrency": 3,
"deep": {
Expand Down Expand Up @@ -229,7 +229,7 @@
"diff": "diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx\n@@ -1,3 +1,5 @@\n+ if (!project) return <ProjectsView />;\n"
}
],
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"effort": "high",
"maxTurns": 40,
"maxBudgetUsd": 2
Expand Down Expand Up @@ -318,22 +318,22 @@
],
"maxTurns": 12,
"maxBudgetUsd": 2,
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
}
},
"events": {
"session-started": {
"sessionId": 0,
"type": "session-started",
"prompt": "hi",
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"permissionMode": "default"
},
"session-ready": {
"sessionId": 0,
"type": "session-ready",
"sdkSessionId": "sdk-uuid",
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"tools": [
"Read",
"Bash"
Expand Down Expand Up @@ -458,7 +458,7 @@
"bugs",
"security"
],
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
},
"analysis-category-started": {
"type": "analysis-category-started",
Expand Down Expand Up @@ -586,7 +586,7 @@
"folder-structure",
"imports-boundaries"
],
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
},
"harness-profile-ready": {
"type": "harness-profile-ready",
Expand Down Expand Up @@ -877,7 +877,7 @@
"tests",
"security"
],
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
},
"scorecard-dimension-started": {
"type": "scorecard-dimension-started",
Expand Down Expand Up @@ -979,7 +979,7 @@
"tests",
"contracts"
],
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
},
"pr-review-lens-started": {
"type": "pr-review-lens-started",
Expand Down Expand Up @@ -1081,7 +1081,7 @@
"type": "issue-validation-started",
"runId": "run-iv1",
"issueNumber": 128,
"model": "claude-opus-4-8"
"model": "claude-opus-5-5"
},
"issue-validation-progress": {
"type": "issue-validation-progress",
Expand Down Expand Up @@ -1204,15 +1204,15 @@
}
]
},
"model": "claude-opus-4-8",
"model": "claude-opus-5-5",
"permissionMode": "acceptEdits",
"outputStyle": "default",
"extrasStatus": "supported"
},
"models": [
{
"value": "claude-opus-4-8",
"displayName": "Claude Opus 4.8",
"value": "claude-opus-5-5",
"displayName": "Opus 5.5",
"description": "Most capable; deep reasoning and long-horizon work.",
"supportsEffort": true,
"supportedEffortLevels": [
Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/src-tauri/src/contracts/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,14 +1153,14 @@ pub enum IssueVerdict {

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KnownModel {
#[serde(rename = "claude-opus-4-8")]
ClaudeOpus48,
#[serde(rename = "claude-sonnet-4-6")]
ClaudeSonnet46,
#[serde(rename = "claude-opus-5-5")]
ClaudeOpus55,
#[serde(rename = "claude-sonnet-5")]
ClaudeSonnet5,
#[serde(rename = "claude-haiku-4-5")]
ClaudeHaiku45,
#[serde(rename = "claude-fable-5")]
ClaudeFable5,
#[serde(rename = "claude-fable-5-1")]
ClaudeFable51,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/sidecar/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ mod tests {
let key = ModelCacheKey::new(CLAUDE_PROVIDER_ID, "n/a");
let models = fallback(CLAUDE_PROVIDER_ID, &key, &cache).expect("claude never errors");
assert_eq!(models.len(), 4);
assert_eq!(models[0].value, "claude-opus-4-8");
assert_eq!(models[0].value, "claude-opus-5-5");
}

#[test]
Expand Down
49 changes: 26 additions & 23 deletions apps/desktop/src-tauri/src/store/model_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ impl ModelCache {
/// `Record<KnownModel, ModelMeta>`.
pub fn claude_static_catalog() -> Vec<ModelDescriptor> {
const CATALOG: [KnownModel; 4] = [
KnownModel::ClaudeOpus48,
KnownModel::ClaudeSonnet46,
KnownModel::ClaudeOpus55,
KnownModel::ClaudeSonnet5,
KnownModel::ClaudeHaiku45,
KnownModel::ClaudeFable5,
KnownModel::ClaudeFable51,
];
CATALOG
.into_iter()
Expand All @@ -186,8 +186,8 @@ pub fn claude_static_catalog() -> Vec<ModelDescriptor> {
display_name: display_name.to_string(),
description: description.to_string(),
// Every shipped Claude model honors the effort option; the SDK silently
// downgrades any level a model can't serve, so an over-generous premium
// set (xhigh/max on Opus/Fable) is safe.
// downgrades any level a model can't serve, so an over-generous set
// (xhigh/max on Opus/Sonnet/Fable, any level on Haiku) is safe.
supports_effort: true,
supported_effort_levels,
}
Expand All @@ -201,24 +201,24 @@ pub fn claude_static_catalog() -> Vec<ModelDescriptor> {
fn claude_model_meta(model: KnownModel) -> (&'static str, &'static str, Vec<EffortLevel>) {
use EffortLevel::{High, Low, Max, Medium, Xhigh};
match model {
KnownModel::ClaudeOpus48 => (
"Claude Opus 4.8",
"Most capable — adaptive reasoning across long-horizon work.",
KnownModel::ClaudeOpus55 => (
"Opus 5.5",
"Best for everyday, complex tasks.",
vec![Low, Medium, High, Xhigh, Max],
),
KnownModel::ClaudeSonnet46 => (
"Claude Sonnet 4.6",
"Balanced speed and depth.",
vec![Low, Medium, High],
KnownModel::ClaudeSonnet5 => (
"Sonnet 5",
"Efficient for routine tasks.",
vec![Low, Medium, High, Xhigh, Max],
),
KnownModel::ClaudeHaiku45 => (
"Claude Haiku 4.5",
"Fastest and most lightweight.",
"Haiku 4.5",
"Fastest for quick answers.",
vec![Low, Medium, High],
),
KnownModel::ClaudeFable5 => (
"Claude Fable 5",
"Creative generalist.",
KnownModel::ClaudeFable51 => (
"Fable 5.1",
"Most capable for your hardest and longest-running tasks.",
vec![Low, Medium, High, Xhigh, Max],
),
}
Expand Down Expand Up @@ -314,21 +314,24 @@ mod tests {
assert_eq!(
ids,
vec![
"claude-opus-4-8",
"claude-sonnet-4-6",
"claude-opus-5-5",
"claude-sonnet-5",
"claude-haiku-4-5",
"claude-fable-5",
"claude-fable-5-1",
]
);
// Every fallback descriptor is effort-capable with a non-empty level set, and the
// premium models unlock the higher levels.
// Every fallback descriptor is effort-capable with a non-empty level set; only
// Haiku stays on the base levels.
assert!(catalog.iter().all(|m| m.supports_effort));
assert!(catalog
.iter()
.all(|m| !m.supported_effort_levels.is_empty()));
let opus = &catalog[0];
assert!(opus.supported_effort_levels.contains(&EffortLevel::Max));
assert!(!catalog[1]
assert!(catalog[1]
.supported_effort_levels
.contains(&EffortLevel::Max));
assert!(!catalog[2]
.supported_effort_levels
.contains(&EffortLevel::Max));
}
Expand Down
15 changes: 8 additions & 7 deletions apps/desktop/src-tauri/src/store/settings/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ pub fn known_model_id(model: KnownModel) -> String {
Ok(serde_json::Value::String(s)) => s,
// Unreachable for a string-valued enum; a defensive fallback keeps the
// resolver total rather than panicking in the settings hot path.
_ => "claude-opus-4-8".to_string(),
_ => "claude-opus-5-5".to_string(),
}
}

/// The default model id for the agent `provider` (issue #79/#80, B2). Provider-aware
/// so a non-Claude provider never silently falls through to a Claude model:
///
/// - `claude` → the first [`KnownModel`], single-sourced from the contract
/// (`claude-opus-4-8`). This is also the arm any UNKNOWN id lands in, because the
/// (`claude-opus-5-5`). This is also the arm any UNKNOWN id lands in, because the
/// provider factory treats an unrecognized id as Claude (it falls back to the
/// Claude backend with a loud warning — see `provider::factory`), so a Claude
/// model is the correct default for it.
Expand All @@ -78,7 +78,7 @@ pub fn default_model_id(provider: &str) -> String {
"codex" => "gpt-5-codex".to_string(),
// `claude` and any unrecognized id (the factory falls back to the Claude
// backend) → the contract's first `KnownModel`.
_ => known_model_id(KnownModel::ClaudeOpus48),
_ => known_model_id(KnownModel::ClaudeOpus55),
}
}

Expand All @@ -88,7 +88,8 @@ pub fn default_model_id(provider: &str) -> String {
/// by family to the matching codegen'd [`KnownModel`] so legacy config still resolves
/// to a valid model. An already-canonical (`claude-…`) or unrecognized id passes
/// through unchanged (the SDK accepts any model string; a custom id is the user's own
/// choice).
/// choice). That includes a canonical id from an earlier model generation
/// (`claude-opus-4-8`): stored canonical ids are never rewritten.
///
/// Only the FAMILY tokens are matched here; the long ids come from the contract via
/// [`known_model_id`], so the catalog itself is single-sourced (this only fires for
Expand All @@ -105,13 +106,13 @@ pub fn canonical_model_id(raw: &str) -> String {
return raw.to_string();
}
let known = if lower.contains("opus") {
KnownModel::ClaudeOpus48
KnownModel::ClaudeOpus55
} else if lower.contains("sonnet") {
KnownModel::ClaudeSonnet46
KnownModel::ClaudeSonnet5
} else if lower.contains("haiku") {
KnownModel::ClaudeHaiku45
} else if lower.contains("fable") {
KnownModel::ClaudeFable5
KnownModel::ClaudeFable51
} else {
return raw.to_string();
};
Expand Down
Loading
Loading