feat: expose modelOverrides setting in ConfigSidebar
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m0s
CI / Lint & Test (pull_request) Successful in 17m34s
CI / Build Linux (pull_request) Successful in 25m55s
CI / Build Windows (cross-compile) (pull_request) Has been cancelled

This commit is contained in:
2026-03-13 00:33:19 -07:00
committed by Naomi Carrigan
parent d8af929758
commit cadbf73d80
13 changed files with 203 additions and 12 deletions
+20
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -46,6 +48,9 @@ pub struct ClaudeStartOptions {
#[serde(default)]
pub auto_memory_directory: Option<String>,
#[serde(default)]
pub model_overrides: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -192,6 +197,9 @@ pub struct HikariConfig {
#[serde(default)]
pub auto_memory_directory: Option<String>,
#[serde(default)]
pub model_overrides: Option<HashMap<String, String>>,
}
impl Default for HikariConfig {
@@ -242,6 +250,7 @@ impl Default for HikariConfig {
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: None,
model_overrides: None,
}
}
}
@@ -392,6 +401,7 @@ mod tests {
assert!(config.include_git_instructions);
assert!(config.enable_claudeai_mcp_servers);
assert!(config.auto_memory_directory.is_none());
assert!(config.model_overrides.is_none());
}
#[test]
@@ -442,6 +452,10 @@ mod tests {
include_git_instructions: false,
enable_claudeai_mcp_servers: false,
auto_memory_directory: Some("/custom/memory".to_string()),
model_overrides: Some(HashMap::from([(
"claude-opus-4-6".to_string(),
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1".to_string(),
)])),
};
let json = serde_json::to_string(&config).unwrap();
@@ -466,6 +480,12 @@ mod tests {
deserialized.auto_memory_directory,
Some("/custom/memory".to_string())
);
assert!(deserialized.model_overrides.is_some());
let overrides = deserialized.model_overrides.unwrap();
assert_eq!(
overrides.get("claude-opus-4-6").map(String::as_str),
Some("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1")
);
}
#[test]
+128 -12
View File
@@ -291,10 +291,39 @@ impl WslBridge {
cmd.arg("--worktree");
}
// Pass auto-memory directory via settings if specified
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
cmd.args(["--settings", &format!(r#"{{"autoMemoryDirectory":"{}"}}"#, dir)]);
// Pass combined settings via --settings flag if any settings are specified
{
let has_memory_dir = options
.auto_memory_directory
.as_deref()
.map(|d| !d.is_empty())
.unwrap_or(false);
let has_overrides = options
.model_overrides
.as_ref()
.map(|m| !m.is_empty())
.unwrap_or(false);
if has_memory_dir || has_overrides {
let mut settings = serde_json::Map::new();
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
settings.insert(
"autoMemoryDirectory".to_string(),
serde_json::Value::String(dir.clone()),
);
}
}
if let Some(ref overrides) = options.model_overrides {
if !overrides.is_empty() {
if let Ok(val) = serde_json::to_value(overrides) {
settings.insert("modelOverrides".to_string(), val);
}
}
}
if let Ok(settings_json) = serde_json::to_string(&settings) {
cmd.args(["--settings", &settings_json]);
}
}
}
@@ -441,14 +470,40 @@ impl WslBridge {
claude_cmd.push_str(" --worktree");
}
// Pass auto-memory directory via settings if specified
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
let escaped_dir = dir.replace('\'', "'\\''");
claude_cmd.push_str(&format!(
" --settings '{{\"autoMemoryDirectory\":\"{}\"}}'",
escaped_dir
));
// Pass combined settings via --settings flag if any settings are specified
{
let has_memory_dir = options
.auto_memory_directory
.as_deref()
.map(|d| !d.is_empty())
.unwrap_or(false);
let has_overrides = options
.model_overrides
.as_ref()
.map(|m| !m.is_empty())
.unwrap_or(false);
if has_memory_dir || has_overrides {
let mut settings = serde_json::Map::new();
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
settings.insert(
"autoMemoryDirectory".to_string(),
serde_json::Value::String(dir.clone()),
);
}
}
if let Some(ref overrides) = options.model_overrides {
if !overrides.is_empty() {
if let Ok(val) = serde_json::to_value(overrides) {
settings.insert("modelOverrides".to_string(), val);
}
}
}
if let Ok(settings_json) = serde_json::to_string(&settings) {
let escaped = settings_json.replace('\'', "'\\''");
claude_cmd.push_str(&format!(" --settings '{}'", escaped));
}
}
}
@@ -3075,4 +3130,65 @@ mod tests {
let dir = "";
assert!(dir.is_empty(), "Empty directory should be skipped");
}
/// Build the combined settings JSON for both memory directory and model overrides (for testing)
#[cfg(test)]
fn build_combined_settings_arg(
memory_dir: Option<&str>,
model_overrides: Option<&std::collections::HashMap<String, String>>,
) -> String {
let mut settings = serde_json::Map::new();
if let Some(dir) = memory_dir {
if !dir.is_empty() {
settings.insert(
"autoMemoryDirectory".to_string(),
serde_json::Value::String(dir.to_string()),
);
}
}
if let Some(overrides) = model_overrides {
if !overrides.is_empty() {
if let Ok(val) = serde_json::to_value(overrides) {
settings.insert("modelOverrides".to_string(), val);
}
}
}
serde_json::to_string(&settings).unwrap_or_default()
}
#[test]
fn test_e2e_combined_settings_memory_only() {
let result = build_combined_settings_arg(Some("/custom/dir"), None);
assert_eq!(result, r#"{"autoMemoryDirectory":"/custom/dir"}"#);
}
#[test]
fn test_e2e_combined_settings_overrides_only() {
let mut overrides = std::collections::HashMap::new();
overrides.insert(
"claude-opus-4-6".to_string(),
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1".to_string(),
);
let result = build_combined_settings_arg(None, Some(&overrides));
assert!(result.contains("modelOverrides"));
assert!(result.contains("claude-opus-4-6"));
assert!(result.contains("arn:aws:bedrock"));
}
#[test]
fn test_e2e_combined_settings_both_fields() {
let mut overrides = std::collections::HashMap::new();
overrides.insert("claude-opus-4-6".to_string(), "custom-model-id".to_string());
let result = build_combined_settings_arg(Some("/mem/dir"), Some(&overrides));
assert!(result.contains("autoMemoryDirectory"));
assert!(result.contains("modelOverrides"));
assert!(result.contains("/mem/dir"));
assert!(result.contains("custom-model-id"));
}
#[test]
fn test_e2e_combined_settings_empty_produces_empty_object() {
let result = build_combined_settings_arg(Some(""), None);
assert_eq!(result, "{}");
}
}
+1
View File
@@ -68,6 +68,7 @@ vi.mock("$lib/stores/config", () => ({
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
}),
},
}));
+2
View File
@@ -69,6 +69,7 @@ async function changeDirectory(path: string): Promise<void> {
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
},
});
@@ -149,6 +150,7 @@ async function startNewConversation(): Promise<void> {
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
},
});
+38
View File
@@ -62,6 +62,7 @@
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
max_output_tokens: null,
trusted_workspaces: [],
background_image_path: null,
@@ -82,6 +83,8 @@
let customUiFontPathInput = $state("");
let customUiFontFamilyInput = $state("");
let customUiFontStatus: string | null = $state(null);
let modelOverridesJson = $state("");
let modelOverridesError: string | null = $state(null);
interface AuthStatus {
is_logged_in: boolean;
@@ -111,6 +114,7 @@
customFontFamilyInput = c.custom_font_family ?? "";
customUiFontPathInput = c.custom_ui_font_path ?? "";
customUiFontFamilyInput = c.custom_ui_font_family ?? "";
modelOverridesJson = c.model_overrides ? JSON.stringify(c.model_overrides, null, 2) : "";
});
configStore.isSidebarOpen.subscribe((open) => {
@@ -196,6 +200,18 @@
async function handleSave() {
isSaving = true;
saveError = null;
modelOverridesError = null;
try {
if (modelOverridesJson.trim()) {
config.model_overrides = JSON.parse(modelOverridesJson) as Record<string, string>;
} else {
config.model_overrides = null;
}
} catch {
modelOverridesError = "Invalid JSON — please check your model overrides.";
isSaving = false;
return;
}
try {
await configStore.saveConfig(config);
configStore.closeSidebar();
@@ -622,6 +638,28 @@
default (working directory).
</p>
</div>
<!-- Model Overrides -->
<div class="mb-4">
<label for="model-overrides" class="block text-sm text-[var(--text-primary)] mb-1">
Model overrides <span class="text-[var(--text-tertiary)]">(optional)</span>
</label>
<textarea
id="model-overrides"
rows={4}
placeholder={'{\n "claude-opus-4-6": "arn:aws:bedrock:..."\n}'}
bind:value={modelOverridesJson}
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)] font-mono resize-y"
></textarea>
{#if modelOverridesError}
<p class="text-xs text-red-500 mt-1">{modelOverridesError}</p>
{/if}
<p class="text-xs text-[var(--text-tertiary)] mt-1">
JSON map of model names to provider-specific IDs (for AWS Bedrock, Google Vertex, etc.).
Passed via <code class="font-mono">--settings modelOverrides</code>. Leave blank to use
defaults.
</p>
</div>
</section>
<!-- Greeting Section -->
+1
View File
@@ -405,6 +405,7 @@ User: ${formattedMessage}`;
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
},
});
@@ -92,6 +92,7 @@
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
},
});
+3
View File
@@ -92,6 +92,7 @@
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
});
let streamerModeActive = $state(false);
@@ -175,6 +176,7 @@
include_git_instructions: currentConfig.include_git_instructions ?? true,
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
},
});
@@ -335,6 +337,7 @@
include_git_instructions: currentConfig.include_git_instructions ?? true,
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
},
});
+1
View File
@@ -221,6 +221,7 @@
include_git_instructions: cfg.include_git_instructions ?? true,
enable_claudeai_mcp_servers: cfg.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: cfg.auto_memory_directory || null,
model_overrides: cfg.model_overrides || null,
},
});
} catch (error) {
@@ -111,6 +111,7 @@
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
},
});
+3
View File
@@ -224,6 +224,7 @@ describe("config store", () => {
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
};
expect(config.model).toBe("claude-sonnet-4");
@@ -287,6 +288,7 @@ describe("config store", () => {
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
};
expect(config.model).toBeNull();
@@ -905,6 +907,7 @@ describe("config store", () => {
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
};
const mockInvokeImpl = vi.mocked(invoke);
+3
View File
@@ -89,6 +89,8 @@ export interface HikariConfig {
enable_claudeai_mcp_servers: boolean;
// Auto-memory directory
auto_memory_directory: string | null;
// Model overrides for provider-specific model IDs (AWS Bedrock, Google Vertex, etc.)
model_overrides: Record<string, string> | null;
}
const defaultConfig: HikariConfig = {
@@ -146,6 +148,7 @@ const defaultConfig: HikariConfig = {
include_git_instructions: true,
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
};
function createConfigStore() {
+1
View File
@@ -50,6 +50,7 @@ vi.mock("@tauri-apps/api/core", () => ({
profile_bio: null,
custom_theme_colors: {},
auto_memory_directory: null,
model_overrides: null,
});
case "list_quick_actions":
return Promise.resolve([]);