diff --git a/src/public/setup.html b/src/public/setup.html index 2375194..ede1ab3 100644 --- a/src/public/setup.html +++ b/src/public/setup.html @@ -21,6 +21,11 @@ authChoices: [], selectedAuth: '', authSecret: '', + customBaseUrl: '', + customModelId: '', + customCompatibility: 'openai', + cloudflareAccountId: '', + cloudflareGatewayId: '', telegramToken: '', discordToken: '', slackBotToken: '', @@ -103,6 +108,11 @@ authChoice: this.selectedAuth, authSecret: this.authSecret, model: this.model, + customBaseUrl: this.customBaseUrl, + customModelId: this.customModelId, + customCompatibility: this.customCompatibility, + cloudflareAccountId: this.cloudflareAccountId, + cloudflareGatewayId: this.cloudflareGatewayId, telegramToken: this.telegramToken, discordToken: this.discordToken, slackBotToken: this.slackBotToken, @@ -521,6 +531,39 @@ + + + +
diff --git a/src/server.js b/src/server.js index 4ccd42b..5793b9c 100644 --- a/src/server.js +++ b/src/server.js @@ -419,14 +419,6 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => { const { version, channelsHelp } = await getOpenclawInfo(); const authGroups = [ - { - value: "openai", - label: "OpenAI", - hint: "API key", - options: [ - { value: "openai-api-key", label: "OpenAI API key" }, - ], - }, { value: "anthropic", label: "Anthropic", @@ -435,12 +427,30 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => { { value: "apiKey", label: "Anthropic API key" }, ], }, + { + value: "openai", + label: "OpenAI", + hint: "API key / Codex", + options: [ + { value: "openai-api-key", label: "OpenAI API key" }, + { value: "openai-codex", label: "OpenAI Codex (ChatGPT OAuth)" }, + ], + }, { value: "google", label: "Google", - hint: "API key", + hint: "API key / CLI", options: [ { value: "gemini-api-key", label: "Google Gemini API key" }, + { value: "google-gemini-cli", label: "Google Gemini CLI (OAuth)" }, + ], + }, + { + value: "deepseek", + label: "DeepSeek", + hint: "API key", + options: [ + { value: "deepseek-api-key", label: "DeepSeek API key" }, ], }, { @@ -450,42 +460,28 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => { options: [{ value: "openrouter-api-key", label: "OpenRouter API key" }], }, { - value: "ai-gateway", - label: "Vercel AI Gateway", + value: "xai", + label: "xAI (Grok)", hint: "API key", - options: [ - { value: "ai-gateway-api-key", label: "Vercel AI Gateway API key" }, - ], + options: [{ value: "xai-api-key", label: "xAI API key" }], }, { - value: "moonshot", - label: "Moonshot AI", - hint: "Kimi K2 + Kimi Code", - options: [ - { value: "moonshot-api-key", label: "Moonshot AI API key" }, - { value: "kimi-code-api-key", label: "Kimi Code API key" }, - ], - }, - { - value: "zai", - label: "Z.AI (GLM 4.7)", + value: "mistral", + label: "Mistral AI", hint: "API key", - options: [{ value: "zai-api-key", label: "Z.AI (GLM 4.7) API key" }], + options: [{ value: "mistral-api-key", label: "Mistral API key" }], }, { - value: "minimax", - label: "MiniMax", - hint: "M2.1 (recommended)", - options: [ - { value: "minimax-global-api", label: "MiniMax M2.1" }, - { value: "minimax-global-api-lightning", label: "MiniMax M2.1 Lightning" }, - ], + value: "together", + label: "Together AI", + hint: "API key", + options: [{ value: "together-api-key", label: "Together AI API key" }], }, { - value: "qwen", - label: "Qwen", - hint: "OAuth", - options: [{ value: "qwen-portal", label: "Qwen OAuth" }], + value: "huggingface", + label: "Hugging Face", + hint: "API key", + options: [{ value: "huggingface-api-key", label: "Hugging Face API key" }], }, { value: "copilot", @@ -499,6 +495,132 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => { { value: "copilot-proxy", label: "Copilot Proxy (local)" }, ], }, + { + value: "moonshot", + label: "Moonshot AI", + hint: "Kimi K2 + Kimi Code", + options: [ + { value: "moonshot-api-key", label: "Moonshot AI API key" }, + { value: "moonshot-api-key-cn", label: "Moonshot AI API key (CN)" }, + { value: "kimi-code-api-key", label: "Kimi Code API key" }, + ], + }, + { + value: "minimax", + label: "MiniMax", + hint: "API key / OAuth", + options: [ + { value: "minimax-global-api", label: "MiniMax API key (Global)" }, + { value: "minimax-global-oauth", label: "MiniMax OAuth (Global)" }, + { value: "minimax-cn-api", label: "MiniMax API key (CN)" }, + { value: "minimax-cn-oauth", label: "MiniMax OAuth (CN)" }, + ], + }, + { + value: "zai", + label: "Z.AI (GLM 4.7)", + hint: "API key / OAuth", + options: [ + { value: "zai-api-key", label: "Z.AI API key" }, + { value: "zai-coding-global", label: "Z.AI Coding (Global)" }, + { value: "zai-coding-cn", label: "Z.AI Coding (CN)" }, + { value: "zai-global", label: "Z.AI (Global)" }, + { value: "zai-cn", label: "Z.AI (CN)" }, + ], + }, + { + value: "qwen", + label: "Qwen", + hint: "OAuth", + options: [{ value: "qwen-portal", label: "Qwen OAuth" }], + }, + { + value: "modelstudio", + label: "Alibaba Model Studio", + hint: "Qwen via Alibaba Cloud", + options: [ + { value: "modelstudio-api-key", label: "Coding Plan (Global)" }, + { value: "modelstudio-api-key-cn", label: "Coding Plan (CN)" }, + { value: "modelstudio-standard-api-key", label: "Standard Plan (Global)" }, + { value: "modelstudio-standard-api-key-cn", label: "Standard Plan (CN)" }, + ], + }, + { + value: "venice", + label: "Venice AI", + hint: "API key", + options: [{ value: "venice-api-key", label: "Venice AI API key" }], + }, + { + value: "chutes", + label: "Chutes", + hint: "OAuth / API key", + options: [ + { value: "chutes", label: "Chutes OAuth" }, + { value: "chutes-api-key", label: "Chutes API key" }, + ], + }, + { + value: "kilocode", + label: "Kilocode", + hint: "API key", + options: [{ value: "kilocode-api-key", label: "Kilocode API key" }], + }, + { + value: "xiaomi", + label: "Xiaomi", + hint: "API key", + options: [{ value: "xiaomi-api-key", label: "Xiaomi API key" }], + }, + { + value: "volcengine", + label: "Volcano Engine (Doubao)", + hint: "API key", + options: [{ value: "volcengine-api-key", label: "Volcano Engine API key" }], + }, + { + value: "byteplus", + label: "BytePlus", + hint: "API key", + options: [{ value: "byteplus-api-key", label: "BytePlus API key" }], + }, + { + value: "qianfan", + label: "Qianfan (Baidu)", + hint: "API key", + options: [{ value: "qianfan-api-key", label: "Qianfan API key" }], + }, + { + value: "ai-gateway", + label: "Vercel AI Gateway", + hint: "API key", + options: [ + { value: "ai-gateway-api-key", label: "Vercel AI Gateway API key" }, + ], + }, + { + value: "cloudflare-ai-gateway", + label: "Cloudflare AI Gateway", + hint: "API key", + options: [ + { value: "cloudflare-ai-gateway-api-key", label: "Cloudflare AI Gateway API key" }, + ], + }, + { + value: "litellm", + label: "LiteLLM", + hint: "Unified gateway", + options: [{ value: "litellm-api-key", label: "LiteLLM API key" }], + }, + { + value: "opencode", + label: "OpenCode", + hint: "Zen / Go", + options: [ + { value: "opencode-zen", label: "OpenCode Zen (multi-model proxy)" }, + { value: "opencode-go", label: "OpenCode Go" }, + ], + }, { value: "synthetic", label: "Synthetic", @@ -506,13 +628,21 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => { options: [{ value: "synthetic-api-key", label: "Synthetic API key" }], }, { - value: "opencode-zen", - label: "OpenCode Zen", - hint: "API key", + value: "self-hosted", + label: "Self-hosted", + hint: "Ollama / vLLM / SGLang", options: [ - { value: "opencode-zen", label: "OpenCode Zen (multi-model proxy)" }, + { value: "ollama", label: "Ollama (local)" }, + { value: "vllm", label: "vLLM" }, + { value: "sglang", label: "SGLang" }, ], }, + { + value: "custom", + label: "Custom endpoint", + hint: "OpenAI / Anthropic compatible", + options: [{ value: "custom-api-key", label: "Custom provider" }], + }, ]; res.json({ @@ -552,24 +682,61 @@ function buildOnboardArgs(payload) { const secret = (payload.authSecret || "").trim(); const map = { - "openai-api-key": "--openai-api-key", apiKey: "--anthropic-api-key", - "openrouter-api-key": "--openrouter-api-key", - "ai-gateway-api-key": "--ai-gateway-api-key", - "moonshot-api-key": "--moonshot-api-key", - "kimi-code-api-key": "--kimi-code-api-key", + "openai-api-key": "--openai-api-key", "gemini-api-key": "--gemini-api-key", - "zai-api-key": "--zai-api-key", + "deepseek-api-key": "--deepseek-api-key", + "openrouter-api-key": "--openrouter-api-key", + "xai-api-key": "--xai-api-key", + "mistral-api-key": "--mistral-api-key", + "together-api-key": "--together-api-key", + "huggingface-api-key": "--huggingface-api-key", + "moonshot-api-key": "--moonshot-api-key", + "moonshot-api-key-cn": "--moonshot-api-key", + "kimi-code-api-key": "--kimi-code-api-key", "minimax-global-api": "--minimax-api-key", - "minimax-global-api-lightning": "--minimax-api-key", - "synthetic-api-key": "--synthetic-api-key", + "minimax-cn-api": "--minimax-api-key", + "zai-api-key": "--zai-api-key", + "modelstudio-api-key": "--modelstudio-api-key", + "modelstudio-api-key-cn": "--modelstudio-api-key-cn", + "modelstudio-standard-api-key": "--modelstudio-standard-api-key", + "modelstudio-standard-api-key-cn": "--modelstudio-standard-api-key-cn", + "venice-api-key": "--venice-api-key", + "chutes-api-key": "--chutes-api-key", + "kilocode-api-key": "--kilocode-api-key", + "xiaomi-api-key": "--xiaomi-api-key", + "volcengine-api-key": "--volcengine-api-key", + "byteplus-api-key": "--byteplus-api-key", + "qianfan-api-key": "--qianfan-api-key", + "ai-gateway-api-key": "--ai-gateway-api-key", + "cloudflare-ai-gateway-api-key": "--cloudflare-ai-gateway-api-key", + "litellm-api-key": "--litellm-api-key", "opencode-zen": "--opencode-zen-api-key", + "opencode-go": "--opencode-go-api-key", + "synthetic-api-key": "--synthetic-api-key", + "custom-api-key": "--custom-api-key", }; const flag = map[payload.authChoice]; if (flag && secret) { args.push(flag, secret); } + if (payload.authChoice === "custom-api-key") { + const baseUrl = (payload.customBaseUrl || "").trim(); + const modelId = (payload.customModelId || "").trim(); + const compat = (payload.customCompatibility || "").trim(); + if (baseUrl) args.push("--custom-base-url", baseUrl); + if (modelId) args.push("--custom-model-id", modelId); + if (compat) args.push("--custom-compatibility", compat); + } + + if (payload.authChoice === "cloudflare-ai-gateway-api-key") { + const accountId = (payload.cloudflareAccountId || "").trim(); + const gatewayId = (payload.cloudflareGatewayId || "").trim(); + if (accountId) args.push("--cloudflare-ai-gateway-account-id", accountId); + if (gatewayId) args.push("--cloudflare-ai-gateway-gateway-id", gatewayId); + } + } return args; @@ -600,21 +767,54 @@ function runCmd(cmd, args, opts = {}) { } const VALID_AUTH_CHOICES = [ - "openai-api-key", "apiKey", + "openai-api-key", + "openai-codex", "gemini-api-key", + "google-gemini-cli", + "deepseek-api-key", "openrouter-api-key", - "ai-gateway-api-key", - "moonshot-api-key", - "kimi-code-api-key", - "zai-api-key", - "minimax-global-api", - "minimax-global-api-lightning", - "qwen-portal", + "xai-api-key", + "mistral-api-key", + "together-api-key", + "huggingface-api-key", "github-copilot", "copilot-proxy", - "synthetic-api-key", + "moonshot-api-key", + "moonshot-api-key-cn", + "kimi-code-api-key", + "minimax-global-api", + "minimax-global-oauth", + "minimax-cn-api", + "minimax-cn-oauth", + "zai-api-key", + "zai-coding-global", + "zai-coding-cn", + "zai-global", + "zai-cn", + "qwen-portal", + "modelstudio-api-key", + "modelstudio-api-key-cn", + "modelstudio-standard-api-key", + "modelstudio-standard-api-key-cn", + "venice-api-key", + "chutes", + "chutes-api-key", + "kilocode-api-key", + "xiaomi-api-key", + "volcengine-api-key", + "byteplus-api-key", + "qianfan-api-key", + "ai-gateway-api-key", + "cloudflare-ai-gateway-api-key", + "litellm-api-key", "opencode-zen", + "opencode-go", + "synthetic-api-key", + "ollama", + "vllm", + "sglang", + "custom-api-key", ]; function validatePayload(payload) { @@ -628,6 +828,11 @@ if (payload.authChoice && !VALID_AUTH_CHOICES.includes(payload.authChoice)) { "slackAppToken", "authSecret", "model", + "customBaseUrl", + "customModelId", + "customCompatibility", + "cloudflareAccountId", + "cloudflareGatewayId", ]; for (const field of stringFields) { if (payload[field] !== undefined && typeof payload[field] !== "string") {