Compare commits

..

7 Commits

Author SHA1 Message Date
Jason db4a1ea907 refactor(preset): remove AiHubMix hardcoded API_KEY in favor of generic auth selector
AiHubMix was the only preset that hardcoded ANTHROPIC_API_KEY before the
generic auth field selector was introduced. Now that users can freely
choose between AUTH_TOKEN and API_KEY via the UI, remove the special-case
and default AiHubMix to the standard ANTHROPIC_AUTH_TOKEN.
2026-02-23 21:52:37 +08:00
Jason 58b7dc32b3 feat(provider): add auth field selector for Claude providers (AUTH_TOKEN / API_KEY)
Allow users to choose between ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY
when creating or editing custom Claude providers, persisted in meta.apiKeyField.
2026-02-23 21:52:37 +08:00
Jason 65ccabd5ad fix(provider): expand partial-merge key fields for Bedrock, Vertex, Foundry and behavior settings
Add missing env/top-level fields to CLAUDE_KEY_ENV_FIELDS and
CLAUDE_KEY_TOP_LEVEL so that provider switching correctly replaces
(and clears) credentials and flags for AWS Bedrock, Google Vertex AI,
Microsoft Foundry, and provider behavior overrides like max output
tokens and prompt caching.
2026-02-23 21:52:37 +08:00
Jason a3a81806f8 fix(provider): add missing key fields to partial-merge constants
Add provider-specific fields verified against official docs to prevent
key residue or loss during provider switching:

- Claude: CLAUDE_CODE_SUBAGENT_MODEL (env), model (top-level)
- Codex: review_model, plan_mode_reasoning_effort
- Gemini: GOOGLE_API_KEY (official alternative to GEMINI_API_KEY)
2026-02-23 21:52:37 +08:00
Jason 5104045ffb feat(claude): add Quick Toggles for common Claude Code preferences
Add checkbox toggles for hideAttribution, alwaysThinking, and
enableTeammates that write directly to the live settings file via
RFC 7396 JSON Merge Patch. Mirror changes to the form editor using
form.watch for reactive updates.
2026-02-23 21:52:37 +08:00
Jason 7898096de3 refactor(cleanup): remove dead code and redundant MCP sync after partial-merge refactor
- Remove ConfigService legacy full-overwrite sync methods (~150 lines)
- Remove redundant McpService::sync_all_enabled from switch_normal
- Switch proxy fallback recovery from write_live_snapshot to write_live_partial
- Remove dead ProviderService::write_gemini_live wrapper
- Update tests to reflect partial-merge behavior (MCP preserved, not re-synced)
2026-02-23 21:52:37 +08:00
Jason cc235c6c63 refactor(provider): switch from full config overwrite to partial key-field merging
Replace the provider switching mechanism for Claude/Codex/Gemini from
full settings_config overwrite to partial key-field replacement, preserving
user's non-provider settings (plugins, MCP, permissions, etc.) across switches.

- Add write_live_partial() with per-app implementations for Claude (JSON env
  merge), Codex (auth replace + TOML partial merge), and Gemini (env merge)
- Add backfill_key_fields() to extract only provider-specific fields when
  saving live config back to provider entries
- Update switch_normal, sync_current_to_live, add, update to use partial merge
- Remove common config snippet feature for Claude/Codex/Gemini (no longer
  needed with partial merging); preserve OMO common config
- Delete 6 frontend files (3 components + 3 hooks), clean up 11 modified files
- Remove backend extract_common_config_* methods, 3 Tauri commands,
  CommonConfigSnippets struct, and related migration code
- Update integration tests to validate key-field-only backfill behavior
2026-02-23 21:52:37 +08:00
21 changed files with 131 additions and 976 deletions
-1
View File
@@ -156,7 +156,6 @@ Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original pric
**Core Capabilities**
- **Provider Management**: One-click switching between Claude Code, Codex, and Gemini API configurations
- **AWS Bedrock Support**: Built-in AWS Bedrock provider presets with AKSK and API Key authentication, cross-region inference support (global/us/eu/apac), covering Claude Code and OpenCode
- **Speed Testing**: Measure API endpoint latency with visual quality indicators
- **Import/Export**: Backup and restore configs with auto-rotation (keep 10 most recent)
- **i18n Support**: Complete Chinese/English localization (UI, errors, tray)
-1
View File
@@ -156,7 +156,6 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
**コア機能**
- **プロバイダ管理**Claude Code、Codex、Gemini の API 設定をワンクリックで切り替え
- **AWS Bedrock 対応**AWS Bedrock プロバイダプリセットを内蔵、AKSK および API Key 認証に対応、クロスリージョン推論(global/us/eu/apac)をサポート、Claude Code と OpenCode に対応
- **速度テスト**:エンドポイント遅延を計測し、品質を可視化
- **インポート/エクスポート**:設定をバックアップ・復元(最新 10 件を自動ローテーション)
- **多言語対応**:UI/エラー/トレイを含む中国語・英語・日本語ローカライズ
-1
View File
@@ -157,7 +157,6 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
**核心功能**
- **供应商管理**:一键切换 Claude Code、Codex 与 Gemini 的 API 配置
- **AWS Bedrock 支持**:内置 AWS Bedrock 供应商预设,支持 AKSK 和 API Key 两种认证方式,支持跨区域推理(global/us/eu/apac),覆盖 Claude Code 和 OpenCode
- **速度测试**:测量 API 端点延迟,可视化连接质量指示器
- **导入导出**:备份和恢复配置,自动轮换(保留最近 10 个)
- **国际化支持**:完整的中英文本地化(UI、错误、托盘)
-159
View File
@@ -1,7 +1,5 @@
use regex::Regex;
use std::sync::LazyLock;
use tauri::AppHandle;
use tauri_plugin_opener::OpenerExt;
use crate::config::write_text_file;
use crate::openclaw_config::get_openclaw_dir;
@@ -149,141 +147,6 @@ pub async fn write_daily_memory_file(filename: String, content: String) -> Resul
.map_err(|e| format!("Failed to write daily memory file {filename}: {e}"))
}
/// Find the largest index `<= i` that is a valid UTF-8 char boundary.
/// Equivalent to the unstable `str::floor_char_boundary` (stabilized in 1.91).
fn floor_char_boundary(s: &str, mut i: usize) -> usize {
if i >= s.len() {
return s.len();
}
while !s.is_char_boundary(i) {
i -= 1;
}
i
}
/// Find the smallest index `>= i` that is a valid UTF-8 char boundary.
/// Equivalent to the unstable `str::ceil_char_boundary` (stabilized in 1.91).
fn ceil_char_boundary(s: &str, mut i: usize) -> usize {
if i >= s.len() {
return s.len();
}
while !s.is_char_boundary(i) {
i += 1;
}
i
}
/// Search result for daily memory full-text search.
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DailyMemorySearchResult {
pub filename: String,
pub date: String,
pub size_bytes: u64,
pub modified_at: u64,
pub snippet: String,
pub match_count: usize,
}
/// Full-text search across all daily memory files.
///
/// Performs case-insensitive search on both the date field and file content.
/// Returns results sorted by filename descending (newest first), each with a
/// snippet showing ~120 characters of context around the first match.
#[tauri::command]
pub async fn search_daily_memory_files(
query: String,
) -> Result<Vec<DailyMemorySearchResult>, String> {
let memory_dir = get_openclaw_dir().join("workspace").join("memory");
if !memory_dir.exists() || query.is_empty() {
return Ok(Vec::new());
}
let query_lower = query.to_lowercase();
let mut results: Vec<DailyMemorySearchResult> = Vec::new();
let entries = std::fs::read_dir(&memory_dir)
.map_err(|e| format!("Failed to read memory directory: {e}"))?;
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if !name.ends_with(".md") {
continue;
}
let meta = match entry.metadata() {
Ok(m) if m.is_file() => m,
_ => continue,
};
let date = name.trim_end_matches(".md").to_string();
let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
let content_lower = content.to_lowercase();
// Count matches in content
let content_matches: Vec<usize> = content_lower
.match_indices(&query_lower)
.map(|(i, _)| i)
.collect();
// Also check date field
let date_matches = date.to_lowercase().contains(&query_lower);
if content_matches.is_empty() && !date_matches {
continue;
}
// Build snippet around first content match (~120 chars of context)
let snippet = if let Some(&first_pos) = content_matches.first() {
let start = if first_pos > 50 {
floor_char_boundary(&content, first_pos - 50)
} else {
0
};
let end = ceil_char_boundary(&content, (first_pos + 70).min(content.len()));
let mut s = String::new();
if start > 0 {
s.push_str("...");
}
s.push_str(&content[start..end]);
if end < content.len() {
s.push_str("...");
}
s
} else {
// Date-only match — use beginning of file as preview
let end = ceil_char_boundary(&content, 120.min(content.len()));
let mut s = content[..end].to_string();
if end < content.len() {
s.push_str("...");
}
s
};
let size_bytes = meta.len();
let modified_at = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.unwrap_or(0);
results.push(DailyMemorySearchResult {
filename: name,
date,
size_bytes,
modified_at,
snippet,
match_count: content_matches.len(),
});
}
// Sort by filename descending (newest date first)
results.sort_by(|a, b| b.filename.cmp(&a.filename));
Ok(results)
}
/// Delete a daily memory file (idempotent).
#[tauri::command]
pub async fn delete_daily_memory_file(filename: String) -> Result<(), String> {
@@ -338,25 +201,3 @@ pub async fn write_workspace_file(filename: String, content: String) -> Result<(
write_text_file(&path, &content)
.map_err(|e| format!("Failed to write workspace file {filename}: {e}"))
}
/// Open the workspace or memory directory in the system file manager.
/// `subdir`: "workspace" opens `~/.openclaw/workspace/`,
/// "memory" opens `~/.openclaw/workspace/memory/`.
#[tauri::command]
pub async fn open_workspace_directory(handle: AppHandle, subdir: String) -> Result<bool, String> {
let dir = match subdir.as_str() {
"memory" => get_openclaw_dir().join("workspace").join("memory"),
_ => get_openclaw_dir().join("workspace"),
};
if !dir.exists() {
std::fs::create_dir_all(&dir).map_err(|e| format!("Failed to create directory: {e}"))?;
}
handle
.opener()
.open_path(dir.to_string_lossy().to_string(), None::<String>)
.map_err(|e| format!("Failed to open directory: {e}"))?;
Ok(true)
}
-2
View File
@@ -1051,8 +1051,6 @@ pub fn run() {
commands::read_daily_memory_file,
commands::write_daily_memory_file,
commands::delete_daily_memory_file,
commands::search_daily_memory_files,
commands::open_workspace_directory,
]);
let app = builder
@@ -198,8 +198,8 @@ export function ClaudeFormFields({
/>
)}
{/* API 格式选择(仅非官方、非云服务商显示) */}
{shouldShowModelSelector && category !== "cloud_provider" && (
{/* API 格式选择(仅非官方供应商显示) */}
{shouldShowModelSelector && (
<div className="space-y-2">
<FormLabel htmlFor="apiFormat">
{t("providerForm.apiFormat", { defaultValue: "API 格式" })}
@@ -40,10 +40,7 @@ import {
import { OpenCodeFormFields } from "./OpenCodeFormFields";
import { OpenClawFormFields } from "./OpenClawFormFields";
import type { UniversalProviderPreset } from "@/config/universalProviderPresets";
import {
applyTemplateValues,
hasApiKeyField,
} from "@/utils/providerConfigUtils";
import { applyTemplateValues } from "@/utils/providerConfigUtils";
import { mergeProviderMeta } from "@/utils/providerMetaUtils";
import { getCodexCustomTemplate } from "@/config/codexTemplates";
import CodexConfigEditor from "./CodexConfigEditor";
@@ -597,8 +594,7 @@ export function ProviderForm({
}
// 非官方供应商必填校验:端点和 API Key
// cloud_provider(如 Bedrock)通过模板变量处理认证,跳过通用校验
if (category !== "official" && category !== "cloud_provider") {
if (category !== "official") {
if (appId === "claude") {
if (!baseUrl.trim()) {
toast.error(
@@ -847,8 +843,7 @@ export function ProviderForm({
);
}, [groupedPresets]);
const shouldShowSpeedTest =
category !== "official" && category !== "cloud_provider";
const shouldShowSpeedTest = category !== "official";
const {
shouldShowApiKeyLink: shouldShowClaudeApiKeyLink,
@@ -1238,13 +1233,10 @@ export function ProviderForm({
{appId === "claude" && (
<ClaudeFormFields
providerId={providerId}
shouldShowApiKey={
hasApiKeyField(form.getValues("settingsConfig"), "claude") &&
shouldShowApiKey(
form.getValues("settingsConfig"),
isEditMode,
)
}
shouldShowApiKey={shouldShowApiKey(
form.getValues("settingsConfig"),
isEditMode,
)}
apiKey={apiKey}
onApiKeyChange={handleApiKeyChange}
category={category}
+8 -7
View File
@@ -525,14 +525,15 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
{isLoadingTools || loadingTools[toolName] ? (
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
) : tool?.version ? (
tool.latest_version &&
tool.version !== tool.latest_version ? (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border border-yellow-500/20">
{tool.latest_version}
</span>
) : (
<div className="flex items-center gap-1.5">
{tool.latest_version &&
tool.version !== tool.latest_version && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border border-yellow-500/20">
{tool.latest_version}
</span>
)}
<CheckCircle2 className="h-4 w-4 text-green-500" />
)
</div>
) : (
<AlertCircle className="h-4 w-4 text-yellow-500" />
)}
+21 -275
View File
@@ -1,24 +1,12 @@
import React, {
useState,
useEffect,
useCallback,
useRef,
useMemo,
} from "react";
import React, { useState, useEffect, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Calendar, Trash2, Plus, Search, X, FolderOpen } from "lucide-react";
import { AnimatePresence, motion } from "framer-motion";
import { Calendar, Trash2, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FullScreenPanel } from "@/components/common/FullScreenPanel";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import MarkdownEditor from "@/components/MarkdownEditor";
import {
workspaceApi,
type DailyMemoryFileInfo,
type DailyMemorySearchResult,
} from "@/lib/api/workspace";
import { workspaceApi, type DailyMemoryFileInfo } from "@/lib/api/workspace";
interface DailyMemoryPanelProps {
isOpen: boolean;
@@ -58,16 +46,6 @@ const DailyMemoryPanel: React.FC<DailyMemoryPanelProps> = ({
// Delete state
const [deletingFile, setDeletingFile] = useState<string | null>(null);
// Search state
const [searchTerm, setSearchTerm] = useState("");
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchResults, setSearchResults] = useState<DailyMemorySearchResult[]>(
[],
);
const [searching, setSearching] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Dark mode
const [isDarkMode, setIsDarkMode] = useState(false);
@@ -83,100 +61,6 @@ const DailyMemoryPanel: React.FC<DailyMemoryPanelProps> = ({
return () => observer.disconnect();
}, []);
// Whether we are in active search mode (search open with a non-empty term)
const isActiveSearch = useMemo(
() => isSearchOpen && searchTerm.trim().length > 0,
[isSearchOpen, searchTerm],
);
// Debounced search execution
const executeSearch = useCallback(
async (query: string) => {
if (!query.trim()) {
setSearchResults([]);
setSearching(false);
return;
}
setSearching(true);
try {
const results = await workspaceApi.searchDailyMemoryFiles(query.trim());
setSearchResults(results);
} catch (err) {
console.error("Failed to search daily memory files:", err);
toast.error(t("workspace.dailyMemory.searchFailed"));
} finally {
setSearching(false);
}
},
[t],
);
// Handle search input change with debounce
const handleSearchChange = useCallback(
(value: string) => {
setSearchTerm(value);
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
debounceTimerRef.current = setTimeout(() => {
void executeSearch(value);
}, 300);
},
[executeSearch],
);
// Open search bar
const openSearch = useCallback(() => {
setIsSearchOpen(true);
// Focus input on next frame
requestAnimationFrame(() => {
searchInputRef.current?.focus();
});
}, []);
// Close search bar and clear state
const closeSearch = useCallback(() => {
setIsSearchOpen(false);
setSearchTerm("");
setSearchResults([]);
setSearching(false);
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
}, []);
// Keyboard shortcut: Cmd/Ctrl+F to open search, Escape to close
useEffect(() => {
if (!isOpen || editingFile) return;
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
e.preventDefault();
if (!isSearchOpen) {
openSearch();
} else {
searchInputRef.current?.focus();
}
}
if (e.key === "Escape" && isSearchOpen) {
e.preventDefault();
closeSearch();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isOpen, editingFile, isSearchOpen, openSearch, closeSearch]);
// Clean up debounce timer on unmount
useEffect(() => {
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, []);
// Load file list
const loadFiles = useCallback(async () => {
setLoadingList(true);
@@ -258,44 +142,24 @@ const DailyMemoryPanel: React.FC<DailyMemoryPanelProps> = ({
setEditingFile(null);
}
await loadFiles();
// Re-trigger search if active
if (isSearchOpen && searchTerm.trim()) {
void executeSearch(searchTerm);
}
} catch (err) {
console.error("Failed to delete daily memory file:", err);
toast.error(t("workspace.dailyMemory.deleteFailed"));
setDeletingFile(null);
}
}, [
deletingFile,
editingFile,
loadFiles,
t,
isSearchOpen,
searchTerm,
executeSearch,
]);
}, [deletingFile, editingFile, loadFiles, t]);
// Back from edit mode to list mode — preserve search state
// Back from edit mode to list mode
const handleBackToList = useCallback(() => {
setEditingFile(null);
setContent("");
void loadFiles();
// Re-trigger search if active (file content may have changed)
if (isSearchOpen && searchTerm.trim()) {
void executeSearch(searchTerm);
}
}, [loadFiles, isSearchOpen, searchTerm, executeSearch]);
}, [loadFiles]);
// Close panel entirely — clear search state
// Close panel entirely
const handleClose = useCallback(() => {
setEditingFile(null);
setContent("");
setIsSearchOpen(false);
setSearchTerm("");
setSearchResults([]);
setSearching(false);
onClose();
}, [onClose]);
@@ -350,142 +214,24 @@ const DailyMemoryPanel: React.FC<DailyMemoryPanelProps> = ({
onClose={handleClose}
>
<div className="space-y-4">
{/* Header with path, search, and create button */}
<div className="flex items-center justify-between gap-2">
<p
className="text-sm text-muted-foreground shrink-0 cursor-pointer hover:text-foreground transition-colors inline-flex items-center gap-1"
onClick={() => workspaceApi.openDirectory("memory")}
title={t("workspace.openDirectory")}
>
{/* Header with path and create button */}
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
~/.openclaw/workspace/memory/
<FolderOpen className="w-3.5 h-3.5" />
</p>
<div className="flex items-center gap-1.5">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={isSearchOpen ? closeSearch : openSearch}
title={t("workspace.dailyMemory.searchScopeHint")}
>
<Search className="w-4 h-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handleCreateToday}
className="gap-1.5"
>
<Plus className="w-3.5 h-3.5" />
{t("workspace.dailyMemory.createToday")}
</Button>
</div>
<Button
variant="outline"
size="sm"
onClick={handleCreateToday}
className="gap-1.5"
>
<Plus className="w-3.5 h-3.5" />
{t("workspace.dailyMemory.createToday")}
</Button>
</div>
{/* Search bar */}
<AnimatePresence>
{isSearchOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.15 }}
className="overflow-hidden"
>
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground pointer-events-none" />
<Input
ref={searchInputRef}
value={searchTerm}
onChange={(e) => handleSearchChange(e.target.value)}
placeholder={t("workspace.dailyMemory.searchPlaceholder")}
className="pl-8 pr-8 h-8 text-sm"
/>
{searchTerm && (
<button
onClick={() => handleSearchChange("")}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
>
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={closeSearch}
className="text-xs text-muted-foreground h-8 px-2 shrink-0"
>
{t("workspace.dailyMemory.searchCloseHint")}
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Content: search results or normal file list */}
{isActiveSearch ? (
// --- Search results ---
searching ? (
<div className="flex items-center justify-center h-48 text-muted-foreground">
{t("workspace.dailyMemory.searching")}
</div>
) : searchResults.length === 0 ? (
<div className="flex flex-col items-center justify-center h-48 text-muted-foreground gap-3 border-2 border-dashed border-border rounded-xl">
<Search className="w-10 h-10 opacity-40" />
<p className="text-sm">
{t("workspace.dailyMemory.noSearchResults")}
</p>
</div>
) : (
<div className="space-y-2">
{searchResults.map((result) => (
<button
key={result.filename}
onClick={() => openFile(result.filename)}
className="w-full flex items-start gap-3 p-4 rounded-xl border border-border bg-card hover:bg-accent/50 transition-colors text-left group"
>
<div className="mt-0.5 text-muted-foreground group-hover:text-foreground transition-colors">
<Calendar className="w-4 h-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-medium text-sm text-foreground">
{result.date}
</span>
<span className="text-xs text-muted-foreground">
{formatFileSize(result.sizeBytes)}
</span>
{result.matchCount > 0 && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-primary/10 text-primary font-medium">
{t("workspace.dailyMemory.matchCount", {
count: result.matchCount,
})}
</span>
)}
</div>
{result.snippet && (
<p className="text-xs text-muted-foreground mt-1 line-clamp-2 whitespace-pre-line">
{result.snippet}
</p>
)}
</div>
<div
className="opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0"
onClick={(e) => {
e.stopPropagation();
setDeletingFile(result.filename);
}}
>
<Trash2 className="w-4 h-4 text-muted-foreground hover:text-destructive transition-colors" />
</div>
</button>
))}
</div>
)
) : // --- Normal file list ---
loadingList ? (
{/* File list */}
{loadingList ? (
<div className="flex items-center justify-center h-48 text-muted-foreground">
{t("prompts.loading")}
</div>
@@ -14,7 +14,6 @@ import {
Circle,
Calendar,
ChevronRight,
FolderOpen,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { workspaceApi } from "@/lib/api/workspace";
@@ -84,13 +83,8 @@ const WorkspaceFilesPanel: React.FC = () => {
return (
<div className="px-6 pt-4 pb-8">
<p
className="text-sm text-muted-foreground mb-6 cursor-pointer hover:text-foreground transition-colors inline-flex items-center gap-1"
onClick={() => workspaceApi.openDirectory("workspace")}
title={t("workspace.openDirectory")}
>
<p className="text-sm text-muted-foreground mb-6">
~/.openclaw/workspace/
<FolderOpen className="w-3.5 h-3.5" />
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
+36 -105
View File
@@ -92,10 +92,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://open.bigmodel.cn/api/anthropic",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "glm-5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
ANTHROPIC_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
},
},
category: "cn_official",
@@ -110,10 +110,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "glm-5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
ANTHROPIC_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7",
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
},
},
category: "cn_official",
@@ -170,10 +170,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://api-inference.modelscope.cn",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "ZhipuAI/GLM-5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "ZhipuAI/GLM-5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "ZhipuAI/GLM-5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "ZhipuAI/GLM-5",
ANTHROPIC_MODEL: "ZhipuAI/GLM-4.7",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "ZhipuAI/GLM-4.7",
ANTHROPIC_DEFAULT_SONNET_MODEL: "ZhipuAI/GLM-4.7",
ANTHROPIC_DEFAULT_OPUS_MODEL: "ZhipuAI/GLM-4.7",
},
},
category: "aggregator",
@@ -236,10 +236,10 @@ export const providerPresets: ProviderPreset[] = [
ANTHROPIC_AUTH_TOKEN: "",
API_TIMEOUT_MS: "3000000",
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
ANTHROPIC_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.5",
ANTHROPIC_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.1",
},
},
category: "cn_official",
@@ -262,10 +262,10 @@ export const providerPresets: ProviderPreset[] = [
ANTHROPIC_AUTH_TOKEN: "",
API_TIMEOUT_MS: "3000000",
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
ANTHROPIC_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.5",
ANTHROPIC_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2.1",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2.1",
},
},
category: "cn_official",
@@ -287,10 +287,10 @@ export const providerPresets: ProviderPreset[] = [
ANTHROPIC_BASE_URL: "https://ark.cn-beijing.volces.com/api/coding",
ANTHROPIC_AUTH_TOKEN: "",
API_TIMEOUT_MS: "3000000",
ANTHROPIC_MODEL: "doubao-seed-2-0-code-preview-latest",
ANTHROPIC_DEFAULT_SONNET_MODEL: "doubao-seed-2-0-code-preview-latest",
ANTHROPIC_DEFAULT_OPUS_MODEL: "doubao-seed-2-0-code-preview-latest",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "doubao-seed-2-0-code-preview-latest",
ANTHROPIC_MODEL: "doubao-seed-code-preview-latest",
ANTHROPIC_DEFAULT_SONNET_MODEL: "doubao-seed-code-preview-latest",
ANTHROPIC_DEFAULT_OPUS_MODEL: "doubao-seed-code-preview-latest",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "doubao-seed-code-preview-latest",
},
},
category: "cn_official",
@@ -304,10 +304,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://api.tbox.cn/api/anthropic",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "Ling-2.5-1T",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "Ling-2.5-1T",
ANTHROPIC_DEFAULT_SONNET_MODEL: "Ling-2.5-1T",
ANTHROPIC_DEFAULT_OPUS_MODEL: "Ling-2.5-1T",
ANTHROPIC_MODEL: "Ling-1T",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "Ling-1T",
ANTHROPIC_DEFAULT_SONNET_MODEL: "Ling-1T",
ANTHROPIC_DEFAULT_OPUS_MODEL: "Ling-1T",
},
},
category: "cn_official",
@@ -336,10 +336,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://api.siliconflow.cn",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "Pro/MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "Pro/MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "Pro/MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "Pro/MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_MODEL: "Pro/MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "Pro/MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_SONNET_MODEL: "Pro/MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_OPUS_MODEL: "Pro/MiniMaxAI/MiniMax-M2.1",
},
},
category: "aggregator",
@@ -354,10 +354,10 @@ export const providerPresets: ProviderPreset[] = [
env: {
ANTHROPIC_BASE_URL: "https://api.siliconflow.com",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMaxAI/MiniMax-M2.5",
ANTHROPIC_MODEL: "MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMaxAI/MiniMax-M2.1",
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMaxAI/MiniMax-M2.1",
},
},
category: "aggregator",
@@ -531,73 +531,4 @@ export const providerPresets: ProviderPreset[] = [
icon: "xiaomimimo",
iconColor: "#000000",
},
{
name: "AWS Bedrock (AKSK)",
websiteUrl: "https://aws.amazon.com/bedrock/",
settingsConfig: {
env: {
ANTHROPIC_BASE_URL:
"https://bedrock-runtime.${AWS_REGION}.amazonaws.com",
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}",
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}",
AWS_REGION: "${AWS_REGION}",
ANTHROPIC_MODEL: "global.anthropic.claude-opus-4-6-v1",
ANTHROPIC_DEFAULT_HAIKU_MODEL:
"global.anthropic.claude-haiku-4-5-20251001-v1:0",
ANTHROPIC_DEFAULT_SONNET_MODEL:
"global.anthropic.claude-sonnet-4-6",
ANTHROPIC_DEFAULT_OPUS_MODEL: "global.anthropic.claude-opus-4-6-v1",
CLAUDE_CODE_USE_BEDROCK: "1",
},
},
category: "cloud_provider",
templateValues: {
AWS_REGION: {
label: "AWS Region",
placeholder: "us-west-2",
editorValue: "us-west-2",
},
AWS_ACCESS_KEY_ID: {
label: "Access Key ID",
placeholder: "AKIA...",
editorValue: "",
},
AWS_SECRET_ACCESS_KEY: {
label: "Secret Access Key",
placeholder: "your-secret-key",
editorValue: "",
},
},
icon: "aws",
iconColor: "#FF9900",
},
{
name: "AWS Bedrock (API Key)",
websiteUrl: "https://aws.amazon.com/bedrock/",
settingsConfig: {
apiKey: "",
env: {
ANTHROPIC_BASE_URL:
"https://bedrock-runtime.${AWS_REGION}.amazonaws.com",
AWS_REGION: "${AWS_REGION}",
ANTHROPIC_MODEL: "global.anthropic.claude-opus-4-6-v1",
ANTHROPIC_DEFAULT_HAIKU_MODEL:
"global.anthropic.claude-haiku-4-5-20251001-v1:0",
ANTHROPIC_DEFAULT_SONNET_MODEL:
"global.anthropic.claude-sonnet-4-6",
ANTHROPIC_DEFAULT_OPUS_MODEL: "global.anthropic.claude-opus-4-6-v1",
CLAUDE_CODE_USE_BEDROCK: "1",
},
},
category: "cloud_provider",
templateValues: {
AWS_REGION: {
label: "AWS Region",
placeholder: "us-west-2",
editorValue: "us-west-2",
},
},
icon: "aws",
iconColor: "#FF9900",
},
];
+39 -74
View File
@@ -112,8 +112,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "glm-5",
name: "GLM-5",
id: "glm-4.7",
name: "GLM-4.7",
contextWindow: 128000,
cost: { input: 0.001, output: 0.001 },
},
@@ -138,8 +138,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "zhipu/glm-5" },
modelCatalog: { "zhipu/glm-5": { alias: "GLM" } },
model: { primary: "zhipu/glm-4.7" },
modelCatalog: { "zhipu/glm-4.7": { alias: "GLM" } },
},
},
{
@@ -152,8 +152,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "glm-5",
name: "GLM-5",
id: "glm-4.7",
name: "GLM-4.7",
contextWindow: 128000,
cost: { input: 0.001, output: 0.001 },
},
@@ -178,8 +178,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "zhipu-en/glm-5" },
modelCatalog: { "zhipu-en/glm-5": { alias: "GLM" } },
model: { primary: "zhipu-en/glm-4.7" },
modelCatalog: { "zhipu-en/glm-4.7": { alias: "GLM" } },
},
},
{
@@ -192,8 +192,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "qwen3.5-plus",
name: "Qwen3.5 Plus",
id: "qwen3-max",
name: "Qwen3 Max",
contextWindow: 32000,
cost: { input: 0.002, output: 0.006 },
},
@@ -216,8 +216,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "qwen/qwen3.5-plus" },
modelCatalog: { "qwen/qwen3.5-plus": { alias: "Qwen" } },
model: { primary: "qwen/qwen3-max" },
modelCatalog: { "qwen/qwen3-max": { alias: "Qwen" } },
},
},
{
@@ -306,8 +306,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "MiniMax-M2.5",
name: "MiniMax M2.5",
id: "MiniMax-M2.1",
name: "MiniMax M2.1",
contextWindow: 200000,
cost: { input: 0.001, output: 0.004 },
},
@@ -330,8 +330,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "minimax/MiniMax-M2.5" },
modelCatalog: { "minimax/MiniMax-M2.5": { alias: "MiniMax" } },
model: { primary: "minimax/MiniMax-M2.1" },
modelCatalog: { "minimax/MiniMax-M2.1": { alias: "MiniMax" } },
},
},
{
@@ -344,8 +344,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "MiniMax-M2.5",
name: "MiniMax M2.5",
id: "MiniMax-M2.1",
name: "MiniMax M2.1",
contextWindow: 200000,
cost: { input: 0.001, output: 0.004 },
},
@@ -368,8 +368,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "minimax-en/MiniMax-M2.5" },
modelCatalog: { "minimax-en/MiniMax-M2.5": { alias: "MiniMax" } },
model: { primary: "minimax-en/MiniMax-M2.1" },
modelCatalog: { "minimax-en/MiniMax-M2.1": { alias: "MiniMax" } },
},
},
{
@@ -465,7 +465,7 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "doubao-seed-2-0-code-preview-latest",
id: "doubao-seed-code-preview-latest",
name: "DouBao Seed Code Preview",
contextWindow: 128000,
cost: { input: 0.002, output: 0.006 },
@@ -483,9 +483,9 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "doubaoseed/doubao-seed-2-0-code-preview-latest" },
model: { primary: "doubaoseed/doubao-seed-code-preview-latest" },
modelCatalog: {
"doubaoseed/doubao-seed-2-0-code-preview-latest": { alias: "DouBao" },
"doubaoseed/doubao-seed-code-preview-latest": { alias: "DouBao" },
},
},
},
@@ -498,8 +498,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "Ling-2.5-1T",
name: "Ling 2.5 1T",
id: "Ling-1T",
name: "Ling 1T",
contextWindow: 128000,
cost: { input: 0.001, output: 0.004 },
},
@@ -514,8 +514,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "bailing/Ling-2.5-1T" },
modelCatalog: { "bailing/Ling-2.5-1T": { alias: "BaiLing" } },
model: { primary: "bailing/Ling-1T" },
modelCatalog: { "bailing/Ling-1T": { alias: "BaiLing" } },
},
},
{
@@ -694,8 +694,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "ZhipuAI/GLM-5",
name: "GLM-5",
id: "ZhipuAI/GLM-4.7",
name: "GLM-4.7",
contextWindow: 128000,
cost: { input: 0.001, output: 0.001 },
},
@@ -718,8 +718,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "modelscope/ZhipuAI/GLM-5" },
modelCatalog: { "modelscope/ZhipuAI/GLM-5": { alias: "GLM" } },
model: { primary: "modelscope/ZhipuAI/GLM-4.7" },
modelCatalog: { "modelscope/ZhipuAI/GLM-4.7": { alias: "GLM" } },
},
},
{
@@ -732,8 +732,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "Pro/MiniMaxAI/MiniMax-M2.5",
name: "MiniMax M2.5",
id: "Pro/MiniMaxAI/MiniMax-M2.1",
name: "MiniMax M2.1",
contextWindow: 200000,
cost: { input: 0.001, output: 0.004 },
},
@@ -750,9 +750,9 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "siliconflow/Pro/MiniMaxAI/MiniMax-M2.5" },
model: { primary: "siliconflow/Pro/MiniMaxAI/MiniMax-M2.1" },
modelCatalog: {
"siliconflow/Pro/MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax" },
"siliconflow/Pro/MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax" },
},
},
},
@@ -766,8 +766,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
api: "openai-completions",
models: [
{
id: "MiniMaxAI/MiniMax-M2.5",
name: "MiniMax M2.5",
id: "MiniMaxAI/MiniMax-M2.1",
name: "MiniMax M2.1",
contextWindow: 200000,
cost: { input: 0.001, output: 0.004 },
},
@@ -784,9 +784,9 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
suggestedDefaults: {
model: { primary: "siliconflow-en/MiniMaxAI/MiniMax-M2.5" },
model: { primary: "siliconflow-en/MiniMaxAI/MiniMax-M2.1" },
modelCatalog: {
"siliconflow-en/MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax" },
"siliconflow-en/MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax" },
},
},
},
@@ -1054,41 +1054,6 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [
},
},
// ========== Cloud Providers ==========
{
name: "AWS Bedrock",
websiteUrl: "https://aws.amazon.com/bedrock/",
settingsConfig: {
// 请将 us-west-2 替换为你的 AWS Region
baseUrl: "https://bedrock-runtime.us-west-2.amazonaws.com",
apiKey: "",
api: "bedrock-converse-stream",
models: [
{
id: "anthropic.claude-opus-4-6-20250514-v1:0",
name: "Claude Opus 4.6",
contextWindow: 200000,
cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
},
{
id: "anthropic.claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
contextWindow: 200000,
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
},
{
id: "anthropic.claude-haiku-4-5-20251022-v1:0",
name: "Claude Haiku 4.5",
contextWindow: 200000,
cost: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
},
],
},
category: "cloud_provider",
icon: "aws",
iconColor: "#FF9900",
},
// ========== Custom Template ==========
{
name: "OpenAI Compatible",
+11 -106
View File
@@ -21,7 +21,6 @@ export const opencodeNpmPackages = [
{ value: "@ai-sdk/openai", label: "OpenAI" },
{ value: "@ai-sdk/openai-compatible", label: "OpenAI Compatible" },
{ value: "@ai-sdk/anthropic", label: "Anthropic" },
{ value: "@ai-sdk/amazon-bedrock", label: "Amazon Bedrock" },
{ value: "@ai-sdk/google", label: "Google (Gemini)" },
] as const;
@@ -41,15 +40,15 @@ export const OPENCODE_PRESET_MODEL_VARIANTS: Record<
> = {
"@ai-sdk/openai-compatible": [
{
id: "MiniMax-M2.5",
name: "MiniMax M2.5",
id: "MiniMax-M2.1",
name: "MiniMax M2.1",
contextLimit: 204800,
outputLimit: 131072,
modalities: { input: ["text"], output: ["text"] },
},
{
id: "glm-5",
name: "GLM 5",
id: "glm-4.7",
name: "GLM 4.7",
contextLimit: 204800,
outputLimit: 131072,
modalities: { input: ["text"], output: ["text"] },
@@ -316,50 +315,6 @@ export const OPENCODE_PRESET_MODEL_VARIANTS: Record<
},
},
],
"@ai-sdk/amazon-bedrock": [
{
id: "global.anthropic.claude-opus-4-6-v1",
name: "Claude Opus 4.6",
contextLimit: 1000000,
outputLimit: 128000,
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
},
{
id: "global.anthropic.claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
contextLimit: 200000,
outputLimit: 64000,
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
},
{
id: "global.anthropic.claude-haiku-4-5-20251001-v1:0",
name: "Claude Haiku 4.5",
contextLimit: 200000,
outputLimit: 64000,
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
},
{
id: "us.amazon.nova-pro-v1:0",
name: "Amazon Nova Pro",
contextLimit: 300000,
outputLimit: 5000,
modalities: { input: ["text", "image"], output: ["text"] },
},
{
id: "us.meta.llama4-maverick-17b-instruct-v1:0",
name: "Meta Llama 4 Maverick",
contextLimit: 131072,
outputLimit: 131072,
modalities: { input: ["text"], output: ["text"] },
},
{
id: "us.deepseek.r1-v1:0",
name: "DeepSeek R1",
contextLimit: 131072,
outputLimit: 131072,
modalities: { input: ["text"], output: ["text"] },
},
],
"@ai-sdk/anthropic": [
{
id: "claude-sonnet-4-5-20250929",
@@ -486,7 +441,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"glm-5": { name: "GLM-5" },
"glm-4.7": { name: "GLM-4.7" },
},
},
category: "cn_official",
@@ -518,7 +473,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"glm-5": { name: "GLM-5" },
"glm-4.7": { name: "GLM-4.7" },
},
},
category: "cn_official",
@@ -644,7 +599,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"ZhipuAI/GLM-5": { name: "GLM-5" },
"ZhipuAI/GLM-4.7": { name: "GLM-4.7" },
},
},
category: "aggregator",
@@ -748,7 +703,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"MiniMax-M2.5": { name: "MiniMax M2.5" },
"MiniMax-M2.1": { name: "MiniMax M2.1" },
},
},
category: "cn_official",
@@ -780,7 +735,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"MiniMax-M2.5": { name: "MiniMax M2.5" },
"MiniMax-M2.1": { name: "MiniMax M2.1" },
},
},
category: "cn_official",
@@ -812,9 +767,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"doubao-seed-2-0-code-preview-latest": {
name: "Doubao Seed Code Preview",
},
"doubao-seed-code-preview-latest": { name: "Doubao Seed Code Preview" },
},
},
category: "cn_official",
@@ -839,7 +792,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
apiKey: "",
},
models: {
"Ling-2.5-1T": { name: "Ling 2.5-1T" },
"Ling-1T": { name: "Ling 1T" },
},
},
category: "cn_official",
@@ -1134,54 +1087,6 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
},
},
{
name: "AWS Bedrock",
websiteUrl: "https://aws.amazon.com/bedrock/",
settingsConfig: {
npm: "@ai-sdk/amazon-bedrock",
name: "AWS Bedrock",
options: {
region: "${region}",
accessKeyId: "${accessKeyId}",
secretAccessKey: "${secretAccessKey}",
},
models: {
"global.anthropic.claude-opus-4-6-v1": { name: "Claude Opus 4.6" },
"global.anthropic.claude-sonnet-4-6": {
name: "Claude Sonnet 4.6",
},
"global.anthropic.claude-haiku-4-5-20251001-v1:0": {
name: "Claude Haiku 4.5",
},
"us.amazon.nova-pro-v1:0": { name: "Amazon Nova Pro" },
"us.meta.llama4-maverick-17b-instruct-v1:0": {
name: "Meta Llama 4 Maverick",
},
"us.deepseek.r1-v1:0": { name: "DeepSeek R1" },
},
},
category: "cloud_provider",
icon: "aws",
iconColor: "#FF9900",
templateValues: {
region: {
label: "AWS Region",
placeholder: "us-west-2",
defaultValue: "us-west-2",
editorValue: "us-west-2",
},
accessKeyId: {
label: "Access Key ID",
placeholder: "AKIA...",
editorValue: "",
},
secretAccessKey: {
label: "Secret Access Key",
placeholder: "your-secret-key",
editorValue: "",
},
},
},
{
name: "OpenAI Compatible",
websiteUrl: "",
+2 -10
View File
@@ -1193,27 +1193,19 @@
"saveSuccess": "Saved successfully",
"saveFailed": "Failed to save",
"loadFailed": "Failed to load",
"openDirectory": "Open in file manager",
"dailyMemory": {
"title": "Daily Memory",
"sectionTitle": "Daily Memory",
"cardTitle": "Daily Memory Files",
"cardDescription": "Browse & manage daily memories",
"createToday": "Add Memory",
"createToday": "Today's Note",
"empty": "No daily memory files yet",
"loadFailed": "Failed to load daily memory files",
"createFailed": "Failed to create daily memory file",
"deleteSuccess": "Daily memory file deleted",
"deleteFailed": "Failed to delete daily memory file",
"confirmDeleteTitle": "Delete Daily Memory",
"confirmDeleteMessage": "Delete the daily memory for {{date}}? This cannot be undone.",
"searchPlaceholder": "Search full content...",
"searchScopeHint": "Full-text search across all daily memories. ⌘F",
"searchCloseHint": "Esc to close",
"noSearchResults": "No daily memories match your search.",
"searching": "Searching...",
"searchFailed": "Search failed",
"matchCount": "{{count}} match(es)"
"confirmDeleteMessage": "Delete the daily memory for {{date}}? This cannot be undone."
}
},
"openclaw": {
+2 -10
View File
@@ -1193,27 +1193,19 @@
"saveSuccess": "保存しました",
"saveFailed": "保存に失敗しました",
"loadFailed": "読み込みに失敗しました",
"openDirectory": "ファイルマネージャーで開く",
"dailyMemory": {
"title": "デイリーメモリー",
"sectionTitle": "デイリーメモリー",
"cardTitle": "デイリーメモリーファイル",
"cardDescription": "デイリーメモリーの閲覧・管理",
"createToday": "メモリーを追加",
"createToday": "今日のノート",
"empty": "デイリーメモリーファイルはまだありません",
"loadFailed": "デイリーメモリーファイルの読み込みに失敗しました",
"createFailed": "デイリーメモリーファイルの作成に失敗しました",
"deleteSuccess": "デイリーメモリーファイルを削除しました",
"deleteFailed": "デイリーメモリーファイルの削除に失敗しました",
"confirmDeleteTitle": "デイリーメモリーを削除",
"confirmDeleteMessage": "{{date}} のデイリーメモリーを削除しますか?この操作は取り消せません。",
"searchPlaceholder": "全文検索...",
"searchScopeHint": "すべてのデイリーメモリーを全文検索 ⌘F",
"searchCloseHint": "Escで閉じる",
"noSearchResults": "検索に一致するデイリーメモリーはありません。",
"searching": "検索中...",
"searchFailed": "検索に失敗しました",
"matchCount": "{{count}}件一致"
"confirmDeleteMessage": "{{date}} のデイリーメモリーを削除しますか?この操作は取り消せません。"
}
},
"openclaw": {
+2 -10
View File
@@ -1193,27 +1193,19 @@
"saveSuccess": "保存成功",
"saveFailed": "保存失败",
"loadFailed": "读取失败",
"openDirectory": "在文件管理器中打开",
"dailyMemory": {
"title": "每日记忆",
"sectionTitle": "每日记忆",
"cardTitle": "每日记忆文件",
"cardDescription": "浏览管理每日记忆",
"createToday": "添加记忆",
"createToday": "今日笔记",
"empty": "暂无每日记忆文件",
"loadFailed": "加载每日记忆文件失败",
"createFailed": "创建每日记忆文件失败",
"deleteSuccess": "每日记忆文件已删除",
"deleteFailed": "删除每日记忆文件失败",
"confirmDeleteTitle": "删除每日记忆",
"confirmDeleteMessage": "确定删除 {{date}} 的每日记忆吗?此操作不可撤销。",
"searchPlaceholder": "搜索全文内容...",
"searchScopeHint": "全文搜索所有每日记忆 ⌘F",
"searchCloseHint": "Esc 关闭",
"noSearchResults": "没有找到匹配的每日记忆。",
"searching": "搜索中...",
"searchFailed": "搜索失败",
"matchCount": "{{count}} 处匹配"
"confirmDeleteMessage": "确定删除 {{date}} 的每日记忆吗?此操作不可撤销。"
}
},
"openclaw": {
-21
View File
@@ -8,15 +8,6 @@ export interface DailyMemoryFileInfo {
preview: string;
}
export interface DailyMemorySearchResult {
filename: string;
date: string;
sizeBytes: number;
modifiedAt: number;
snippet: string;
matchCount: number;
}
export const workspaceApi = {
async readFile(filename: string): Promise<string | null> {
return invoke<string | null>("read_workspace_file", { filename });
@@ -41,16 +32,4 @@ export const workspaceApi = {
async deleteDailyMemoryFile(filename: string): Promise<void> {
return invoke<void>("delete_daily_memory_file", { filename });
},
async searchDailyMemoryFiles(
query: string,
): Promise<DailyMemorySearchResult[]> {
return invoke<DailyMemorySearchResult[]>("search_daily_memory_files", {
query,
});
},
async openDirectory(subdir: "workspace" | "memory"): Promise<void> {
await invoke("open_workspace_directory", { subdir });
},
};
-1
View File
@@ -1,7 +1,6 @@
export type ProviderCategory =
| "official" // 官方
| "cn_official" // 开源官方(原"国产官方")
| "cloud_provider" // 云服务商(AWS Bedrock 等)
| "aggregator" // 聚合网站
| "third_party" // 第三方供应商
| "custom" // 自定义
-23
View File
@@ -29,16 +29,6 @@ export const getApiKeyFromConfig = (
): string => {
try {
const config = JSON.parse(jsonString);
// 优先检查顶层 apiKey 字段(用于 Bedrock API Key 等预设)
if (
typeof config?.apiKey === "string" &&
config.apiKey &&
!config.apiKey.includes("${")
) {
return config.apiKey;
}
const env = config?.env;
if (!env) return "";
@@ -122,12 +112,6 @@ export const hasApiKeyField = (
): boolean => {
try {
const config = JSON.parse(jsonString);
// 检查顶层 apiKey 字段(用于 Bedrock API Key 等预设)
if (Object.prototype.hasOwnProperty.call(config, "apiKey")) {
return true;
}
const env = config?.env ?? {};
if (appType === "gemini") {
@@ -160,13 +144,6 @@ export const setApiKeyInConfig = (
const { createIfMissing = false, appType, apiKeyField } = options;
try {
const config = JSON.parse(jsonString);
// 优先检查顶层 apiKey 字段(用于 Bedrock API Key 等预设)
if (Object.prototype.hasOwnProperty.call(config, "apiKey")) {
config.apiKey = apiKey;
return JSON.stringify(config, null, 2);
}
if (!config.env) {
if (!createIfMissing) return jsonString;
config.env = {};
@@ -1,78 +0,0 @@
import { describe, expect, it } from "vitest";
import { providerPresets } from "@/config/claudeProviderPresets";
describe("AWS Bedrock Provider Presets", () => {
const bedrockAksk = providerPresets.find(
(p) => p.name === "AWS Bedrock (AKSK)",
);
it("should include AWS Bedrock (AKSK) preset", () => {
expect(bedrockAksk).toBeDefined();
});
it("AKSK preset should have required AWS env variables", () => {
const env = (bedrockAksk!.settingsConfig as any).env;
expect(env).toHaveProperty("AWS_ACCESS_KEY_ID");
expect(env).toHaveProperty("AWS_SECRET_ACCESS_KEY");
expect(env).toHaveProperty("AWS_REGION");
expect(env).toHaveProperty("CLAUDE_CODE_USE_BEDROCK", "1");
});
it("AKSK preset should have template values for AWS credentials", () => {
expect(bedrockAksk!.templateValues).toBeDefined();
expect(bedrockAksk!.templateValues!.AWS_ACCESS_KEY_ID).toBeDefined();
expect(bedrockAksk!.templateValues!.AWS_SECRET_ACCESS_KEY).toBeDefined();
expect(bedrockAksk!.templateValues!.AWS_REGION).toBeDefined();
expect(bedrockAksk!.templateValues!.AWS_REGION.editorValue).toBe(
"us-west-2",
);
});
it("AKSK preset should have correct base URL template", () => {
const env = (bedrockAksk!.settingsConfig as any).env;
expect(env.ANTHROPIC_BASE_URL).toContain("bedrock-runtime");
expect(env.ANTHROPIC_BASE_URL).toContain("${AWS_REGION}");
});
it("AKSK preset should have cloud_provider category", () => {
expect(bedrockAksk!.category).toBe("cloud_provider");
});
it("AKSK preset should have Bedrock model as default", () => {
const env = (bedrockAksk!.settingsConfig as any).env;
expect(env.ANTHROPIC_MODEL).toContain("anthropic.claude");
});
const bedrockApiKey = providerPresets.find(
(p) => p.name === "AWS Bedrock (API Key)",
);
it("should include AWS Bedrock (API Key) preset", () => {
expect(bedrockApiKey).toBeDefined();
});
it("API Key preset should have apiKey field and AWS env variables", () => {
const config = bedrockApiKey!.settingsConfig as any;
expect(config).toHaveProperty("apiKey", "");
expect(config.env).toHaveProperty("AWS_REGION");
expect(config.env).toHaveProperty("CLAUDE_CODE_USE_BEDROCK", "1");
});
it("API Key preset should NOT have AKSK env variables", () => {
const env = (bedrockApiKey!.settingsConfig as any).env;
expect(env).not.toHaveProperty("AWS_ACCESS_KEY_ID");
expect(env).not.toHaveProperty("AWS_SECRET_ACCESS_KEY");
});
it("API Key preset should have template values for region only", () => {
expect(bedrockApiKey!.templateValues).toBeDefined();
expect(bedrockApiKey!.templateValues!.AWS_REGION).toBeDefined();
expect(bedrockApiKey!.templateValues!.AWS_REGION.editorValue).toBe(
"us-west-2",
);
});
it("API Key preset should have cloud_provider category", () => {
expect(bedrockApiKey!.category).toBe("cloud_provider");
});
});
@@ -1,68 +0,0 @@
import { describe, expect, it } from "vitest";
import {
opencodeProviderPresets,
opencodeNpmPackages,
OPENCODE_PRESET_MODEL_VARIANTS,
} from "@/config/opencodeProviderPresets";
describe("AWS Bedrock OpenCode Provider Presets", () => {
it("should include @ai-sdk/amazon-bedrock in npm packages", () => {
const bedrockPkg = opencodeNpmPackages.find(
(p) => p.value === "@ai-sdk/amazon-bedrock",
);
expect(bedrockPkg).toBeDefined();
expect(bedrockPkg!.label).toBe("Amazon Bedrock");
});
it("should include Bedrock model variants", () => {
const variants = OPENCODE_PRESET_MODEL_VARIANTS["@ai-sdk/amazon-bedrock"];
expect(variants).toBeDefined();
expect(variants.length).toBeGreaterThan(0);
const opusModel = variants.find((v) =>
v.id.includes("anthropic.claude-opus-4-6"),
);
expect(opusModel).toBeDefined();
});
const bedrockPreset = opencodeProviderPresets.find(
(p) => p.name === "AWS Bedrock",
);
it("should include AWS Bedrock preset", () => {
expect(bedrockPreset).toBeDefined();
});
it("Bedrock preset should use @ai-sdk/amazon-bedrock npm package", () => {
expect(bedrockPreset!.settingsConfig.npm).toBe(
"@ai-sdk/amazon-bedrock",
);
});
it("Bedrock preset should have region in options", () => {
expect(bedrockPreset!.settingsConfig.options).toHaveProperty("region");
});
it("Bedrock preset should have cloud_provider category", () => {
expect(bedrockPreset!.category).toBe("cloud_provider");
});
it("Bedrock preset should have template values for AWS credentials", () => {
expect(bedrockPreset!.templateValues).toBeDefined();
expect(bedrockPreset!.templateValues!.region).toBeDefined();
expect(bedrockPreset!.templateValues!.region.editorValue).toBe(
"us-west-2",
);
expect(bedrockPreset!.templateValues!.accessKeyId).toBeDefined();
expect(bedrockPreset!.templateValues!.secretAccessKey).toBeDefined();
});
it("Bedrock preset should include Claude models", () => {
const models = bedrockPreset!.settingsConfig.models;
expect(models).toBeDefined();
const modelIds = Object.keys(models!);
expect(
modelIds.some((id) => id.includes("anthropic.claude")),
).toBe(true);
});
});