Skip to content
Open
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: 2 additions & 0 deletions rust/crates/rusty-claude-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ serde_json.workspace = true
syntect = "5"
tokio = { version = "1", features = ["rt-multi-thread", "signal", "time"] }
tools = { path = "../tools" }
log = "0.4"


[lints]
workspace = true
Expand Down
57 changes: 53 additions & 4 deletions rust/crates/rusty-claude-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant, UNIX_EPOCH};

use log::debug;

use api::{
detect_provider_kind, model_family_identity_for, resolve_startup_auth_source, AnthropicClient,
AuthSource, ContentBlockDelta, InputContentBlock, InputMessage, MessageRequest,
Expand Down Expand Up @@ -718,15 +720,19 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
let value = args
.get(index + 1)
.ok_or_else(|| "missing value for --model".to_string())?;
validate_model_syntax(value)?;
model = resolve_model_alias_with_config(value);
let resolved = resolve_model_alias_with_config(value);
debug!("Resolved --model '{}' -> '{}'", value, resolved);
validate_model_syntax(&resolved)?;
model = resolved;
model_flag_raw = Some(value.clone()); // #148
index += 2;
}
flag if flag.starts_with("--model=") => {
let value = &flag[8..];
validate_model_syntax(value)?;
model = resolve_model_alias_with_config(value);
let resolved = resolve_model_alias_with_config(value);
debug!("Resolved --model='{}' -> '{}'", value, resolved);
validate_model_syntax(&resolved)?;
model = resolved;
model_flag_raw = Some(value.to_string()); // #148
index += 1;
}
Expand Down Expand Up @@ -15055,3 +15061,46 @@ mod dump_manifests_tests {
let _ = fs::remove_dir_all(&root);
}
}

#[cfg(test)]
mod alias_resolution_tests {
use super::{resolve_model_alias_with_config, validate_model_syntax};

#[test]
fn test_alias_resolution_builtin() {
// Built-in aliases should resolve to their full IDs
assert_eq!(resolve_model_alias_with_config("opus"), "claude-opus-4-6");
assert_eq!(resolve_model_alias_with_config("sonnet"), "claude-sonnet-4-6");
assert_eq!(resolve_model_alias_with_config("haiku"), "claude-haiku-4-5-20251213");
}

#[test]
fn test_alias_resolution_syntax_validation() {
// Resolved aliases should pass syntax validation
let resolved = resolve_model_alias_with_config("opus");
assert!(validate_model_syntax(&resolved).is_ok());

// Raw aliases should FAIL syntax validation (this is why we resolve first!)
assert!(validate_model_syntax("opus").is_err());
}

#[test]
fn test_unknown_alias_fails_validation() {
// Unknown aliases resolve to themselves
let resolved = resolve_model_alias_with_config("unknown-alias");
assert_eq!(resolved, "unknown-alias");

// And then fail validation with a helpful error
let result = validate_model_syntax(&resolved);
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid model syntax"));
}

#[test]
fn test_direct_provider_model_passes() {
// Direct provider/model strings should remain unchanged and pass
let model = "openai/gpt-4o";
assert_eq!(resolve_model_alias_with_config(model), model);
assert!(validate_model_syntax(model).is_ok());
}
}
16 changes: 10 additions & 6 deletions rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use std::fs;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;

use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
Expand Down Expand Up @@ -426,11 +426,15 @@ fn prepare_plugin_fixture(workspace: &HarnessWorkspace) {
"#!/bin/sh\nINPUT=$(cat)\nprintf '{\"plugin\":\"%s\",\"tool\":\"%s\",\"input\":%s}\\n' \"$CLAWD_PLUGIN_ID\" \"$CLAWD_TOOL_NAME\" \"$INPUT\"\n",
)
.expect("plugin script should write");
let mut permissions = fs::metadata(&script_path)
.expect("plugin script metadata")
.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&script_path, permissions).expect("plugin script should be executable");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(&script_path)
.expect("plugin script metadata")
.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&script_path, permissions).expect("plugin script should be executable");
}

fs::write(
manifest_dir.join("plugin.json"),
Expand Down