From cadbf73d803b95055d3e692a363c2cc17f4d64ff Mon Sep 17 00:00:00 2001
From: Hikari
Date: Fri, 13 Mar 2026 00:33:19 -0700
Subject: [PATCH] feat: expose modelOverrides setting in ConfigSidebar
---
src-tauri/src/config.rs | 20 +++
src-tauri/src/wsl_bridge.rs | 140 ++++++++++++++++++--
src/lib/commands/slashCommands.test.ts | 1 +
src/lib/commands/slashCommands.ts | 2 +
src/lib/components/ConfigSidebar.svelte | 38 ++++++
src/lib/components/InputBar.svelte | 1 +
src/lib/components/PermissionModal.svelte | 1 +
src/lib/components/StatusBar.svelte | 3 +
src/lib/components/TaskLoopPanel.svelte | 1 +
src/lib/components/UserQuestionModal.svelte | 1 +
src/lib/stores/config.test.ts | 3 +
src/lib/stores/config.ts | 3 +
vitest.setup.ts | 1 +
13 files changed, 203 insertions(+), 12 deletions(-)
diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs
index e4268a3..ab26da7 100644
--- a/src-tauri/src/config.rs
+++ b/src-tauri/src/config.rs
@@ -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,
+
+ #[serde(default)]
+ pub model_overrides: Option>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -192,6 +197,9 @@ pub struct HikariConfig {
#[serde(default)]
pub auto_memory_directory: Option,
+
+ #[serde(default)]
+ pub model_overrides: Option>,
}
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]
diff --git a/src-tauri/src/wsl_bridge.rs b/src-tauri/src/wsl_bridge.rs
index 0ea63bc..452d5b3 100644
--- a/src-tauri/src/wsl_bridge.rs
+++ b/src-tauri/src/wsl_bridge.rs
@@ -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 {
+ 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, "{}");
+ }
}
diff --git a/src/lib/commands/slashCommands.test.ts b/src/lib/commands/slashCommands.test.ts
index 4b48783..a333df2 100644
--- a/src/lib/commands/slashCommands.test.ts
+++ b/src/lib/commands/slashCommands.test.ts
@@ -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,
}),
},
}));
diff --git a/src/lib/commands/slashCommands.ts b/src/lib/commands/slashCommands.ts
index c4fe697..79145b3 100644
--- a/src/lib/commands/slashCommands.ts
+++ b/src/lib/commands/slashCommands.ts
@@ -69,6 +69,7 @@ async function changeDirectory(path: string): Promise {
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 {
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,
},
});
diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte
index c07528d..e7b17d2 100644
--- a/src/lib/components/ConfigSidebar.svelte
+++ b/src/lib/components/ConfigSidebar.svelte
@@ -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;
+ } 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).
+
+
+
+
+ Model overrides (optional)
+
+
+ {#if modelOverridesError}
+
{modelOverridesError}
+ {/if}
+
+ JSON map of model names to provider-specific IDs (for AWS Bedrock, Google Vertex, etc.).
+ Passed via --settings modelOverrides. Leave blank to use
+ defaults.
+
+
diff --git a/src/lib/components/InputBar.svelte b/src/lib/components/InputBar.svelte
index 221a189..fbb799f 100644
--- a/src/lib/components/InputBar.svelte
+++ b/src/lib/components/InputBar.svelte
@@ -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,
},
});
diff --git a/src/lib/components/PermissionModal.svelte b/src/lib/components/PermissionModal.svelte
index ce240d0..f4035f3 100644
--- a/src/lib/components/PermissionModal.svelte
+++ b/src/lib/components/PermissionModal.svelte
@@ -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,
},
});
diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte
index 9c0d8fb..9b69a63 100644
--- a/src/lib/components/StatusBar.svelte
+++ b/src/lib/components/StatusBar.svelte
@@ -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,
},
});
diff --git a/src/lib/components/TaskLoopPanel.svelte b/src/lib/components/TaskLoopPanel.svelte
index c5ab836..cacd72a 100644
--- a/src/lib/components/TaskLoopPanel.svelte
+++ b/src/lib/components/TaskLoopPanel.svelte
@@ -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) {
diff --git a/src/lib/components/UserQuestionModal.svelte b/src/lib/components/UserQuestionModal.svelte
index 96dc7f1..bcaf500 100644
--- a/src/lib/components/UserQuestionModal.svelte
+++ b/src/lib/components/UserQuestionModal.svelte
@@ -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,
},
});
diff --git a/src/lib/stores/config.test.ts b/src/lib/stores/config.test.ts
index 3cf9ccb..018c240 100644
--- a/src/lib/stores/config.test.ts
+++ b/src/lib/stores/config.test.ts
@@ -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);
diff --git a/src/lib/stores/config.ts b/src/lib/stores/config.ts
index 6d2b102..6977cdd 100644
--- a/src/lib/stores/config.ts
+++ b/src/lib/stores/config.ts
@@ -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 | 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() {
diff --git a/vitest.setup.ts b/vitest.setup.ts
index 204b9ac..7e80f5a 100644
--- a/vitest.setup.ts
+++ b/vitest.setup.ts
@@ -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([]);