From 47f2c47a2ff2ede69de9b4853c12cacf9f7f7c3e Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 4 Feb 2026 21:19:13 +0800 Subject: [PATCH] feat(openclaw): add agents.defaults config support - Add types for default model config (primary + fallbacks) - Add types for model catalog/allowlist with aliases - Extend OpenClawModelEntry with cost and contextWindow fields - Add Tauri commands: get/set_openclaw_default_model, get/set_openclaw_model_catalog - Create frontend API (src/lib/api/openclaw.ts) - Add suggestedDefaults to provider presets with model metadata - Add i18n translations (zh/en/ja) for openclawConfig.* --- src-tauri/src/commands/provider.rs | 35 ++++ src-tauri/src/lib.rs | 4 + src-tauri/src/openclaw_config.rs | 168 ++++++++++++++++- src/config/openclawProviderPresets.ts | 249 +++++++++++++++++++++++--- src/i18n/locales/en.json | 29 +++ src/i18n/locales/ja.json | 29 +++ src/i18n/locales/zh.json | 29 +++ src/lib/api/openclaw.ts | 59 ++++++ src/types.ts | 14 ++ 9 files changed, 595 insertions(+), 21 deletions(-) create mode 100644 src/lib/api/openclaw.ts diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index 70ce9048e..d6d51397d 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -342,3 +342,38 @@ pub fn get_openclaw_live_provider_ids() -> Result, String> { .map(|providers| providers.keys().cloned().collect()) .map_err(|e| e.to_string()) } + +// ============================================================================ +// OpenClaw Agents Configuration Commands +// ============================================================================ + +/// 获取 OpenClaw 默认模型配置(agents.defaults.model) +#[tauri::command] +pub fn get_openclaw_default_model( +) -> Result, String> { + crate::openclaw_config::get_default_model().map_err(|e| e.to_string()) +} + +/// 设置 OpenClaw 默认模型配置(agents.defaults.model) +#[tauri::command] +pub fn set_openclaw_default_model( + model: crate::openclaw_config::OpenClawDefaultModel, +) -> Result<(), String> { + crate::openclaw_config::set_default_model(&model).map_err(|e| e.to_string()) +} + +/// 获取 OpenClaw 模型目录/允许列表(agents.defaults.models) +#[tauri::command] +pub fn get_openclaw_model_catalog( +) -> Result>, String> +{ + crate::openclaw_config::get_model_catalog().map_err(|e| e.to_string()) +} + +/// 设置 OpenClaw 模型目录/允许列表(agents.defaults.models) +#[tauri::command] +pub fn set_openclaw_model_catalog( + catalog: std::collections::HashMap, +) -> Result<(), String> { + crate::openclaw_config::set_model_catalog(&catalog).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index dc36d60d9..05cde9676 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1007,6 +1007,10 @@ pub fn run() { // OpenClaw specific commands::import_openclaw_providers_from_live, commands::get_openclaw_live_provider_ids, + commands::get_openclaw_default_model, + commands::set_openclaw_default_model, + commands::get_openclaw_model_catalog, + commands::set_openclaw_model_catalog, // Global upstream proxy commands::get_global_proxy_url, commands::set_global_proxy_url, diff --git a/src-tauri/src/openclaw_config.rs b/src-tauri/src/openclaw_config.rs index 9488216c8..ab6082c1d 100644 --- a/src-tauri/src/openclaw_config.rs +++ b/src-tauri/src/openclaw_config.rs @@ -42,6 +42,7 @@ use crate::settings::get_openclaw_override_dir; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use serde_json::{json, Map, Value}; +use std::collections::HashMap; use std::path::PathBuf; // ============================================================================ @@ -101,6 +102,7 @@ pub struct OpenClawProviderConfig { /// OpenClaw 模型条目 #[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct OpenClawModelEntry { /// 模型 ID pub id: String, @@ -109,11 +111,92 @@ pub struct OpenClawModelEntry { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, - /// 其他自定义字段 + /// 模型别名(用于快捷引用) + #[serde(skip_serializing_if = "Option::is_none")] + pub alias: Option, + + /// 模型成本(输入/输出价格) + #[serde(skip_serializing_if = "Option::is_none")] + pub cost: Option, + + /// 上下文窗口大小 + #[serde(skip_serializing_if = "Option::is_none")] + pub context_window: Option, + + /// 其他自定义字段(保留未知字段) #[serde(flatten)] pub extra: Map, } +/// OpenClaw 模型成本配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenClawModelCost { + /// 输入价格(每百万 token) + pub input: f64, + + /// 输出价格(每百万 token) + pub output: f64, + + /// 其他自定义字段(保留未知字段) + #[serde(flatten)] + pub other: HashMap, +} + +/// OpenClaw 默认模型配置(agents.defaults.model) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenClawDefaultModel { + /// 主模型 ID(格式:provider/model) + pub primary: String, + + /// 回退模型列表 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub fallbacks: Vec, + + /// 其他自定义字段(保留未知字段) + #[serde(flatten)] + pub other: HashMap, +} + +/// OpenClaw 模型目录条目(agents.defaults.models 中的值) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenClawModelCatalogEntry { + /// 模型别名(用于 UI 显示) + #[serde(skip_serializing_if = "Option::is_none")] + pub alias: Option, + + /// 其他自定义字段(保留未知字段) + #[serde(flatten)] + pub other: HashMap, +} + +/// OpenClaw agents.defaults 配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenClawAgentsDefaults { + /// 默认模型配置 + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + + /// 模型目录/允许列表(键为 provider/model 格式) + #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option>, + + /// 其他自定义字段(保留未知字段) + #[serde(flatten)] + pub other: HashMap, +} + +/// OpenClaw agents 顶层配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenClawAgents { + /// 默认配置 + #[serde(skip_serializing_if = "Option::is_none")] + pub defaults: Option, + + /// 其他自定义字段(保留未知字段) + #[serde(flatten)] + pub other: HashMap, +} + // ============================================================================ // Core Read/Write Functions // ============================================================================ @@ -254,3 +337,86 @@ pub fn set_typed_provider(id: &str, config: &OpenClawProviderConfig) -> Result<( let value = serde_json::to_value(config).map_err(|e| AppError::JsonSerialize { source: e })?; set_provider(id, value) } + +// ============================================================================ +// Agents Configuration Functions +// ============================================================================ + +/// 读取默认模型配置(agents.defaults.model) +pub fn get_default_model() -> Result, AppError> { + let config = read_openclaw_config()?; + + let Some(model_value) = config + .get("agents") + .and_then(|a| a.get("defaults")) + .and_then(|d| d.get("model")) + else { + return Ok(None); + }; + + let model = serde_json::from_value(model_value.clone()) + .map_err(|e| AppError::Config(format!("Failed to parse agents.defaults.model: {e}")))?; + + Ok(Some(model)) +} + +/// 设置默认模型配置(agents.defaults.model) +pub fn set_default_model(model: &OpenClawDefaultModel) -> Result<(), AppError> { + let mut config = read_openclaw_config()?; + + // Ensure agents.defaults path exists, preserving unknown fields + ensure_agents_defaults_path(&mut config); + + let model_value = + serde_json::to_value(model).map_err(|e| AppError::JsonSerialize { source: e })?; + + config["agents"]["defaults"]["model"] = model_value; + + write_openclaw_config(&config) +} + +/// 读取模型目录/允许列表(agents.defaults.models) +pub fn get_model_catalog() -> Result>, AppError> { + let config = read_openclaw_config()?; + + let Some(models_value) = config + .get("agents") + .and_then(|a| a.get("defaults")) + .and_then(|d| d.get("models")) + else { + return Ok(None); + }; + + let catalog = serde_json::from_value(models_value.clone()) + .map_err(|e| AppError::Config(format!("Failed to parse agents.defaults.models: {e}")))?; + + Ok(Some(catalog)) +} + +/// 设置模型目录/允许列表(agents.defaults.models) +pub fn set_model_catalog( + catalog: &HashMap, +) -> Result<(), AppError> { + let mut config = read_openclaw_config()?; + + // Ensure agents.defaults path exists, preserving unknown fields + ensure_agents_defaults_path(&mut config); + + let catalog_value = + serde_json::to_value(catalog).map_err(|e| AppError::JsonSerialize { source: e })?; + + config["agents"]["defaults"]["models"] = catalog_value; + + write_openclaw_config(&config) +} + +/// Ensure the `agents.defaults` path exists in the config, +/// preserving any existing unknown fields. +fn ensure_agents_defaults_path(config: &mut Value) { + if config.get("agents").is_none() { + config["agents"] = json!({}); + } + if config["agents"].get("defaults").is_none() { + config["agents"]["defaults"] = json!({}); + } +} diff --git a/src/config/openclawProviderPresets.ts b/src/config/openclawProviderPresets.ts index 91b3fbad3..3ef6e8796 100644 --- a/src/config/openclawProviderPresets.ts +++ b/src/config/openclawProviderPresets.ts @@ -2,9 +2,21 @@ * OpenClaw provider presets configuration * OpenClaw uses models.providers structure with custom provider configs */ -import type { ProviderCategory, OpenClawProviderConfig } from "../types"; +import type { + ProviderCategory, + OpenClawProviderConfig, + OpenClawDefaultModel, +} from "../types"; import type { PresetTheme, TemplateValueConfig } from "./claudeProviderPresets"; +/** Suggested default model configuration for a preset */ +export interface OpenClawSuggestedDefaults { + /** Default model config to apply (agents.defaults.model) */ + model?: OpenClawDefaultModel; + /** Model catalog entries to add (agents.defaults.models) */ + modelCatalog?: Record; +} + export interface OpenClawProviderPreset { name: string; websiteUrl: string; @@ -25,6 +37,8 @@ export interface OpenClawProviderPreset { iconColor?: string; /** Mark as custom template (for UI distinction) */ isCustomTemplate?: boolean; + /** Suggested default model configuration */ + suggestedDefaults?: OpenClawSuggestedDefaults; } /** @@ -50,8 +64,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "openai-completions", models: [ - { id: "deepseek-chat", name: "DeepSeek V3.2" }, - { id: "deepseek-reasoner", name: "DeepSeek R1" }, + { + id: "deepseek-chat", + name: "DeepSeek V3.2", + contextWindow: 64000, + cost: { input: 0.0005, output: 0.002 }, + }, + { + id: "deepseek-reasoner", + name: "DeepSeek R1", + contextWindow: 64000, + cost: { input: 0.0005, output: 0.002 }, + }, ], }, category: "cn_official", @@ -64,6 +88,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "deepseek/deepseek-chat", + fallbacks: ["deepseek/deepseek-reasoner"], + }, + modelCatalog: { + "deepseek/deepseek-chat": { alias: "DeepSeek" }, + "deepseek/deepseek-reasoner": { alias: "R1" }, + }, + }, }, { name: "Zhipu GLM", @@ -73,7 +107,14 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ baseUrl: "https://open.bigmodel.cn/api/paas/v4", apiKey: "", api: "openai-completions", - models: [{ id: "glm-4.7", name: "GLM-4.7" }], + models: [ + { + id: "glm-4.7", + name: "GLM-4.7", + contextWindow: 128000, + cost: { input: 0.001, output: 0.001 }, + }, + ], }, category: "cn_official", isPartner: true, @@ -93,6 +134,10 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { primary: "zhipu/glm-4.7" }, + modelCatalog: { "zhipu/glm-4.7": { alias: "GLM" } }, + }, }, { name: "Qwen Coder", @@ -102,7 +147,14 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", apiKey: "", api: "openai-completions", - models: [{ id: "qwen3-max", name: "Qwen3 Max" }], + models: [ + { + id: "qwen3-max", + name: "Qwen3 Max", + contextWindow: 32000, + cost: { input: 0.002, output: 0.006 }, + }, + ], }, category: "cn_official", icon: "qwen", @@ -120,6 +172,10 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { primary: "qwen/qwen3-max" }, + modelCatalog: { "qwen/qwen3-max": { alias: "Qwen" } }, + }, }, { name: "Kimi k2.5", @@ -129,7 +185,14 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ baseUrl: "https://api.moonshot.cn/v1", apiKey: "", api: "openai-completions", - models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }], + models: [ + { + id: "kimi-k2.5", + name: "Kimi K2.5", + contextWindow: 131072, + cost: { input: 0.002, output: 0.006 }, + }, + ], }, category: "cn_official", icon: "kimi", @@ -147,6 +210,10 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { primary: "kimi/kimi-k2.5" }, + modelCatalog: { "kimi/kimi-k2.5": { alias: "Kimi" } }, + }, }, { name: "MiniMax", @@ -156,7 +223,14 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ baseUrl: "https://api.minimaxi.com/v1", apiKey: "", api: "openai-completions", - models: [{ id: "MiniMax-M2.1", name: "MiniMax M2.1" }], + models: [ + { + id: "MiniMax-M2.1", + name: "MiniMax M2.1", + contextWindow: 200000, + cost: { input: 0.001, output: 0.004 }, + }, + ], }, category: "cn_official", isPartner: true, @@ -174,6 +248,10 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { primary: "minimax/MiniMax-M2.1" }, + modelCatalog: { "minimax/MiniMax-M2.1": { alias: "MiniMax" } }, + }, }, // ========== Aggregators ========== @@ -186,8 +264,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "anthropic", models: [ - { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" }, - { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5" }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "claude-opus-4-5-20251101", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "aggregator", @@ -200,6 +288,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "aihubmix/claude-sonnet-4-5-20250929", + fallbacks: ["aihubmix/claude-opus-4-5-20251101"], + }, + modelCatalog: { + "aihubmix/claude-sonnet-4-5-20250929": { alias: "Sonnet" }, + "aihubmix/claude-opus-4-5-20251101": { alias: "Opus" }, + }, + }, }, { name: "DMXAPI", @@ -210,8 +308,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "anthropic", models: [ - { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" }, - { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5" }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "claude-opus-4-5-20251101", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "aggregator", @@ -224,6 +332,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "dmxapi/claude-sonnet-4-5-20250929", + fallbacks: ["dmxapi/claude-opus-4-5-20251101"], + }, + modelCatalog: { + "dmxapi/claude-sonnet-4-5-20250929": { alias: "Sonnet" }, + "dmxapi/claude-opus-4-5-20251101": { alias: "Opus" }, + }, + }, }, { name: "OpenRouter", @@ -234,8 +352,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "openai-completions", models: [ - { id: "anthropic/claude-sonnet-4.5", name: "Claude Sonnet 4.5" }, - { id: "anthropic/claude-opus-4.5", name: "Claude Opus 4.5" }, + { + id: "anthropic/claude-sonnet-4.5", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "anthropic/claude-opus-4.5", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "aggregator", @@ -248,6 +376,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "openrouter/anthropic/claude-sonnet-4.5", + fallbacks: ["openrouter/anthropic/claude-opus-4.5"], + }, + modelCatalog: { + "openrouter/anthropic/claude-sonnet-4.5": { alias: "Sonnet" }, + "openrouter/anthropic/claude-opus-4.5": { alias: "Opus" }, + }, + }, }, { name: "ModelScope", @@ -257,7 +395,14 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ baseUrl: "https://api-inference.modelscope.cn/v1", apiKey: "", api: "openai-completions", - models: [{ id: "ZhipuAI/GLM-4.7", name: "GLM-4.7" }], + models: [ + { + id: "ZhipuAI/GLM-4.7", + name: "GLM-4.7", + contextWindow: 128000, + cost: { input: 0.001, output: 0.001 }, + }, + ], }, category: "aggregator", icon: "modelscope", @@ -275,6 +420,10 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { primary: "modelscope/ZhipuAI/GLM-4.7" }, + modelCatalog: { "modelscope/ZhipuAI/GLM-4.7": { alias: "GLM" } }, + }, }, // ========== Third Party Partners ========== @@ -287,8 +436,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "anthropic", models: [ - { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" }, - { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5" }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "claude-opus-4-5-20251101", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "third_party", @@ -302,6 +461,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "packycode/claude-sonnet-4-5-20250929", + fallbacks: ["packycode/claude-opus-4-5-20251101"], + }, + modelCatalog: { + "packycode/claude-sonnet-4-5-20250929": { alias: "Sonnet" }, + "packycode/claude-opus-4-5-20251101": { alias: "Opus" }, + }, + }, }, { name: "Cubence", @@ -312,8 +481,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "anthropic", models: [ - { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" }, - { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5" }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "claude-opus-4-5-20251101", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "third_party", @@ -328,6 +507,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "cubence/claude-sonnet-4-5-20250929", + fallbacks: ["cubence/claude-opus-4-5-20251101"], + }, + modelCatalog: { + "cubence/claude-sonnet-4-5-20250929": { alias: "Sonnet" }, + "cubence/claude-opus-4-5-20251101": { alias: "Opus" }, + }, + }, }, { name: "AIGoCode", @@ -338,8 +527,18 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ apiKey: "", api: "anthropic", models: [ - { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" }, - { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5" }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + contextWindow: 200000, + cost: { input: 3, output: 15 }, + }, + { + id: "claude-opus-4-5-20251101", + name: "Claude Opus 4.5", + contextWindow: 200000, + cost: { input: 15, output: 75 }, + }, ], }, category: "third_party", @@ -354,6 +553,16 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ editorValue: "", }, }, + suggestedDefaults: { + model: { + primary: "aigocode/claude-sonnet-4-5-20250929", + fallbacks: ["aigocode/claude-opus-4-5-20251101"], + }, + modelCatalog: { + "aigocode/claude-sonnet-4-5-20250929": { alias: "Sonnet" }, + "aigocode/claude-opus-4-5-20251101": { alias: "Opus" }, + }, + }, }, // ========== Custom Template ========== diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 137f2cf62..b1ab8e460 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1745,5 +1745,34 @@ "unspecifiedHigh": "Uncategorized high-effort task category for tasks that don't fit other categories with large workload. Defaults to Claude Opus 4.6 max variant.", "writing": "Writing category for documentation, prose and technical writing. Defaults to Gemini 3 Flash fast generation model." } + }, + "openclawConfig": { + "defaultModel": { + "title": "Default Model", + "description": "Configure the default primary model and fallback models for OpenClaw", + "primary": "Primary Model", + "primaryPlaceholder": "e.g., deepseek/deepseek-chat", + "fallbacks": "Fallback Models", + "fallbacksPlaceholder": "e.g., openrouter/anthropic/claude-sonnet-4.5", + "addFallback": "Add Fallback Model", + "saved": "Default model configuration saved", + "saveFailed": "Failed to save default model" + }, + "modelCatalog": { + "title": "Model Catalog", + "description": "Configure model aliases and allowlist", + "modelId": "Model ID", + "modelIdPlaceholder": "e.g., deepseek/deepseek-chat", + "alias": "Alias", + "aliasPlaceholder": "e.g., DeepSeek", + "addEntry": "Add Model", + "removeEntry": "Remove", + "saved": "Model catalog saved", + "saveFailed": "Failed to save model catalog", + "empty": "No model catalog entries", + "emptyHint": "Add models to the catalog to configure aliases" + }, + "suggestedDefaults": "Apply Suggested Defaults", + "suggestedDefaultsHint": "Use this preset's recommended default model configuration" } } diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index e458b6f87..3412ee1d5 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -1726,5 +1726,34 @@ "unspecifiedHigh": "未分類の高作業量タスクカテゴリ。他のカテゴリに該当せず作業量が大きいタスクに適用。デフォルトで Claude Opus 4.6 の max バリアントを使用。", "writing": "ライティングカテゴリ。ドキュメント、散文、技術文書に特化。デフォルトで Gemini 3 Flash 高速生成モデルを使用。" } + }, + "openclawConfig": { + "defaultModel": { + "title": "デフォルトモデル", + "description": "OpenClaw のデフォルトのプライマリモデルとフォールバックモデルを設定します", + "primary": "プライマリモデル", + "primaryPlaceholder": "例: deepseek/deepseek-chat", + "fallbacks": "フォールバックモデル", + "fallbacksPlaceholder": "例: openrouter/anthropic/claude-sonnet-4.5", + "addFallback": "フォールバックモデルを追加", + "saved": "デフォルトモデル設定を保存しました", + "saveFailed": "デフォルトモデルの保存に失敗しました" + }, + "modelCatalog": { + "title": "モデルカタログ", + "description": "モデルのエイリアスと許可リストを設定します", + "modelId": "モデル ID", + "modelIdPlaceholder": "例: deepseek/deepseek-chat", + "alias": "エイリアス", + "aliasPlaceholder": "例: DeepSeek", + "addEntry": "モデルを追加", + "removeEntry": "削除", + "saved": "モデルカタログを保存しました", + "saveFailed": "モデルカタログの保存に失敗しました", + "empty": "モデルカタログエントリがありません", + "emptyHint": "エイリアスを設定するにはカタログにモデルを追加してください" + }, + "suggestedDefaults": "推奨デフォルト設定を適用", + "suggestedDefaultsHint": "このプリセットの推奨デフォルトモデル設定を使用します" } } diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 82d36b804..1deeb99c0 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -1745,5 +1745,34 @@ "unspecifiedHigh": "未归类高工作量任务类别,适用于不适合其他类别且工作量较大的任务,默认使用 Claude Opus 4.6 的最大变体。", "writing": "写作类别,专注于文档、散文和技术写作,默认使用 Gemini 3 Flash 快速生成模型。" } + }, + "openclawConfig": { + "defaultModel": { + "title": "默认模型", + "description": "配置 OpenClaw 的默认主模型和回退模型", + "primary": "主模型", + "primaryPlaceholder": "例如: deepseek/deepseek-chat", + "fallbacks": "回退模型", + "fallbacksPlaceholder": "例如: openrouter/anthropic/claude-sonnet-4.5", + "addFallback": "添加回退模型", + "saved": "默认模型配置已保存", + "saveFailed": "保存默认模型失败" + }, + "modelCatalog": { + "title": "模型目录", + "description": "配置可用模型的别名和允许列表", + "modelId": "模型 ID", + "modelIdPlaceholder": "例如: deepseek/deepseek-chat", + "alias": "别名", + "aliasPlaceholder": "例如: DeepSeek", + "addEntry": "添加模型", + "removeEntry": "移除", + "saved": "模型目录已保存", + "saveFailed": "保存模型目录失败", + "empty": "暂无模型目录配置", + "emptyHint": "添加模型到目录以配置别名" + }, + "suggestedDefaults": "应用建议默认配置", + "suggestedDefaultsHint": "使用此预设推荐的默认模型配置" } } diff --git a/src/lib/api/openclaw.ts b/src/lib/api/openclaw.ts new file mode 100644 index 000000000..cfad98ce0 --- /dev/null +++ b/src/lib/api/openclaw.ts @@ -0,0 +1,59 @@ +import { invoke } from "@tauri-apps/api/core"; +import type { + OpenClawDefaultModel, + OpenClawModelCatalogEntry, +} from "@/types"; + +/** + * OpenClaw agents configuration API + * + * Manages agents.defaults configuration in ~/.openclaw/openclaw.json + */ +export const openclawApi = { + /** + * Get default model configuration (agents.defaults.model) + */ + async getDefaultModel(): Promise { + return await invoke("get_openclaw_default_model"); + }, + + /** + * Set default model configuration (agents.defaults.model) + */ + async setDefaultModel(model: OpenClawDefaultModel): Promise { + return await invoke("set_openclaw_default_model", { model }); + }, + + /** + * Get model catalog/allowlist (agents.defaults.models) + */ + async getModelCatalog(): Promise | null> { + return await invoke("get_openclaw_model_catalog"); + }, + + /** + * Set model catalog/allowlist (agents.defaults.models) + */ + async setModelCatalog( + catalog: Record, + ): Promise { + return await invoke("set_openclaw_model_catalog", { catalog }); + }, + + /** + * Import providers from live config (openclaw.json) to database + */ + async importProvidersFromLive(): Promise { + return await invoke("import_openclaw_providers_from_live"); + }, + + /** + * Get provider IDs that exist in live config (openclaw.json) + */ + async getLiveProviderIds(): Promise { + return await invoke("get_openclaw_live_provider_ids"); + }, +}; diff --git a/src/types.ts b/src/types.ts index dba9bace4..f53ae86b9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -434,6 +434,20 @@ export interface OpenCodeMcpServerSpec { export interface OpenClawModel { id: string; name: string; + alias?: string; + cost?: { input: number; output: number }; + contextWindow?: number; +} + +// OpenClaw 默认模型配置(agents.defaults.model) +export interface OpenClawDefaultModel { + primary: string; + fallbacks?: string[]; +} + +// OpenClaw 模型目录条目(agents.defaults.models 中的值) +export interface OpenClawModelCatalogEntry { + alias?: string; } // OpenClaw 供应商配置(settings_config 结构)