Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/dev-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const HERMES_PROVIDER_REGISTRY = [
hermesProvider('anthropic', 'Anthropic', 'api_key', 'https://api.anthropic.com', '', ['ANTHROPIC_API_KEY', 'ANTHROPIC_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN'], 'anthropic_messages', 'anthropic', ['claude-opus-4-7', 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-opus-4-5-20251101', 'claude-sonnet-4-5-20250929', 'claude-opus-4-20250514', 'claude-sonnet-4-20250514', 'claude-haiku-4-5-20251001']),
hermesProvider('gemini', 'Google AI Studio', 'api_key', 'https://generativelanguage.googleapis.com/v1beta/openai', 'GEMINI_BASE_URL', ['GOOGLE_API_KEY', 'GEMINI_API_KEY'], 'openai_chat', 'openai', ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-3.1-flash-lite-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemma-4-31b-it', 'gemma-4-26b-it']),
hermesProvider('deepseek', 'DeepSeek', 'api_key', 'https://api.deepseek.com', 'DEEPSEEK_BASE_URL', ['DEEPSEEK_API_KEY'], 'openai_chat', 'openai', ['deepseek-chat', 'deepseek-reasoner']),
hermesProvider('atlascloud', 'Atlas Cloud', 'api_key', 'https://api.atlascloud.ai/v1', '', ['ATLASCLOUD_API_KEY'], 'openai_chat', 'openai', ['deepseek-ai/deepseek-v4-pro'], true),
hermesProvider('xai', 'xAI', 'api_key', 'https://api.x.ai/v1', 'XAI_BASE_URL', ['XAI_API_KEY'], 'openai_chat', 'openai', ['grok-4.20-reasoning', 'grok-4-1-fast-reasoning']),
hermesProvider('minimax', 'MiniMax (International)', 'api_key', 'https://api.minimax.io/anthropic/v1', 'MINIMAX_BASE_URL', ['MINIMAX_API_KEY'], 'anthropic_messages', 'anthropic', ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed']),
hermesProvider('huggingface', 'Hugging Face', 'api_key', 'https://router.huggingface.co/v1', 'HF_BASE_URL', ['HF_TOKEN'], 'openai_chat', 'openai', ['Qwen/Qwen3.5-397B-A17B', 'Qwen/Qwen3.5-35B-A3B', 'deepseek-ai/DeepSeek-V3.2', 'moonshotai/Kimi-K2.5', 'MiniMaxAI/MiniMax-M2.5', 'zai-org/GLM-5', 'XiaomiMiMo/MiMo-V2-Flash', 'moonshotai/Kimi-K2-Thinking'], true),
Expand Down
30 changes: 29 additions & 1 deletion src-tauri/src/commands/hermes_providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ const P_DEEPSEEK: HermesProvider = HermesProvider {
cli_auth_hint: "",
};

const P_ATLASCLOUD: HermesProvider = HermesProvider {
id: "atlascloud",
name: "Atlas Cloud",
auth_type: AUTH_API_KEY,
base_url: "https://api.atlascloud.ai/v1",
base_url_env_var: "",
api_key_env_vars: &["ATLASCLOUD_API_KEY"],
transport: TRANSPORT_OPENAI_CHAT,
models_probe: PROBE_OPENAI,
models: &["deepseek-ai/deepseek-v4-pro"],
is_aggregator: true,
cli_auth_hint: "",
};

const P_ZAI: HermesProvider = HermesProvider {
id: "zai",
name: "Z.AI / GLM",
Expand Down Expand Up @@ -729,6 +743,7 @@ pub const ALL_PROVIDERS: &[HermesProvider] = &[
P_ANTHROPIC,
P_GEMINI,
P_DEEPSEEK,
P_ATLASCLOUD,
P_XAI,
P_MINIMAX,
P_HUGGINGFACE,
Expand Down Expand Up @@ -880,9 +895,10 @@ mod tests {

#[test]
fn registry_has_expected_providers() {
assert_eq!(ALL_PROVIDERS.len(), 35);
assert_eq!(ALL_PROVIDERS.len(), 36);
assert!(get_provider("anthropic").is_some());
assert!(get_provider("gemini").is_some());
assert!(get_provider("atlascloud").is_some());
assert!(get_provider("alibaba-coding-plan").is_some());
assert!(get_provider("bedrock").is_some());
assert!(get_provider("vertex").is_some());
Expand All @@ -902,6 +918,10 @@ mod tests {
assert_eq!(primary_api_key_env("zai"), Some("GLM_API_KEY"));
assert_eq!(primary_api_key_env("novita"), Some("NOVITA_API_KEY"));
assert_eq!(primary_api_key_env("stepfun"), Some("STEPFUN_API_KEY"));
assert_eq!(
primary_api_key_env("atlascloud"),
Some("ATLASCLOUD_API_KEY")
);
assert_eq!(primary_api_key_env("bedrock"), None);
assert_eq!(primary_api_key_env("vertex"), None);
assert_eq!(primary_api_key_env("nous"), None);
Expand All @@ -920,6 +940,7 @@ mod tests {
assert!(keys.contains(&"NOVITA_API_KEY"));
assert!(keys.contains(&"NOVITA_BASE_URL"));
assert!(keys.contains(&"STEPFUN_API_KEY"));
assert!(keys.contains(&"ATLASCLOUD_API_KEY"));
assert!(keys.contains(&"GATEWAY_ALLOW_ALL_USERS"));
assert!(keys.contains(&"API_SERVER_ENABLED"));
assert!(keys.contains(&"API_SERVER_KEY"));
Expand All @@ -942,6 +963,9 @@ mod tests {
let keys = vec!["DEEPSEEK_API_KEY"];
assert_eq!(infer_provider_from_env_keys(&keys), Some("deepseek"));

let keys = vec!["ATLASCLOUD_API_KEY"];
assert_eq!(infer_provider_from_env_keys(&keys), Some("atlascloud"));

// Secondary anthropic env var still matches.
let keys = vec!["ANTHROPIC_TOKEN"];
assert_eq!(infer_provider_from_env_keys(&keys), Some("anthropic"));
Expand All @@ -954,6 +978,10 @@ mod tests {
#[test]
fn find_provider_by_model_is_unambiguous() {
assert_eq!(find_provider_by_model("deepseek-chat"), Some("deepseek"));
assert_eq!(
find_provider_by_model("deepseek-ai/deepseek-v4-pro"),
Some("atlascloud")
);
assert_eq!(find_provider_by_model("kimi-for-coding"), None);
assert_eq!(find_provider_by_model("nonexistent"), None);
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/model-presets.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ export function modelApiTypeOptions(value) {
return [{ value: normalized, label: `${normalized} (OpenClaw)` }, ...API_TYPES]
}

export function splitModelReference(value) {
const [provider, ...modelParts] = String(value || '').split('/')
return [provider, modelParts.join('/')]
}

// 服务商快捷预设
export const PROVIDER_PRESETS = [
{ key: 'qtcool', label: '晴辰云', badge: '免费测试', baseUrl: 'https://gpt.qt.cool/v1', api: 'openai-completions', site: 'https://gpt.qt.cool/', desc: 'ClawPanel 配套免费签到测试平台,适合体验和功能验证' },
{ key: 'atlascloud', label: 'Atlas Cloud', baseUrl: 'https://api.atlascloud.ai/v1', api: 'openai-completions', site: 'https://www.atlascloud.ai/', desc: 'OpenAI-compatible model API' },
{ key: 'ciyapi', label: '词元 API', badge: '赞助', sponsored: true, baseUrl: 'https://ciyapi.79tian.com/v1', api: 'openai-completions', site: 'https://ciyapi.79tian.com/', desc: '支持 GPT、Claude 等主流前沿模型;充值 ¥1 到账 $1 平台额度,部分线路按折扣计费' },
{ key: 'shengsuanyun', label: '胜算云', baseUrl: 'https://router.shengsuanyun.com/api/v1', api: 'openai-completions', site: 'https://www.shengsuanyun.com/?from=CH_4BVI0BM2', desc: '国内知名 AI 模型聚合平台,支持多种主流模型' },
{ key: 'siliconflow', label: '硅基流动', baseUrl: 'https://api.siliconflow.cn/v1', api: 'openai-completions', site: 'https://cloud.siliconflow.cn/i/PFrw2an5', desc: '高性价比推理平台,支持 DeepSeek、Qwen 等开源模型' },
Expand Down Expand Up @@ -108,6 +114,9 @@ export const SHENGSUANYUN = {

// 常用模型预设(按服务商分组)
export const MODEL_PRESETS = {
atlascloud: [
{ id: 'deepseek-ai/deepseek-v4-pro', name: 'DeepSeek V4 Pro', reasoning: true },
],
openai: [
{ id: 'gpt-4o', name: 'GPT-4o', contextWindow: 128000 },
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', contextWindow: 128000 },
Expand Down
3 changes: 2 additions & 1 deletion src/pages/assistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
API_TYPES as SHARED_API_TYPES,
fetchQtcoolModels,
fetchCiyapiModels,
splitModelReference,
} from '../lib/model-presets.js'
import { t } from '../lib/i18n.js'
import { getActiveEngineId } from '../lib/engine-manager.js'
Expand Down Expand Up @@ -1760,7 +1761,7 @@ function createOpenClawFindings(ctx, component = 'all') {
const providers = ctx.providers || []
const providerMap = new Map(providers.map(p => [p.key, p]))
const primary = ctx.primaryModel || ''
const [primaryProvider, primaryModel] = primary.split('/')
const [primaryProvider, primaryModel] = splitModelReference(primary)
if (want('gateway')) {
if (!ctx.gateway) findings.push({ level: 'warn', component: 'gateway', title: '无法读取 Gateway 服务状态', evidence: 'get_services_status 未返回可识别的 Gateway 项', suggestion: '在服务管理页或使用 openclaw gateway status --deep 进一步确认。' })
else if (!ctx.gateway.running) findings.push({ level: 'warn', component: 'gateway', title: 'Gateway 当前未运行', evidence: `${ctx.gateway.label}: ${ctx.gateway.status || 'not running'}`, suggestion: '可在服务管理页启动 Gateway,或先检查端口/日志定位启动失败原因。' })
Expand Down
18 changes: 18 additions & 0 deletions tests/model-presets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,26 @@ test('编辑未知 OpenClaw API 类型时保留原值供用户选择', () => {
assert.ok(options.some(item => item.value === 'openai-completions'))
})

test('模型引用保留 provider 后带斜杠的完整模型 ID', () => {
assert.deepEqual(
presets.splitModelReference('atlascloud/deepseek-ai/deepseek-v4-pro'),
['atlascloud', 'deepseek-ai/deepseek-v4-pro'],
)
})

// ===== Provider Presets =====

test('Atlas Cloud 预设使用 OpenAI-compatible 端点和嵌套模型 ID', () => {
const atlascloud = PROVIDER_PRESETS.find(p => p.key === 'atlascloud')
assert.ok(atlascloud)
assert.equal(atlascloud.label, 'Atlas Cloud')
assert.equal(atlascloud.baseUrl, 'https://api.atlascloud.ai/v1')
assert.equal(atlascloud.api, 'openai-completions')
assert.deepEqual(MODEL_PRESETS.atlascloud.map(m => m.id), [
'deepseek-ai/deepseek-v4-pro',
])
})

test('词元 API 赞助预设使用独立 provider 与大陆加速地址', () => {
const ciyapi = PROVIDER_PRESETS.find(p => p.key === 'ciyapi')
assert.ok(ciyapi)
Expand Down