Merge pull request #356 from kaitranntt/dev

feat(providers): add Ollama support and provider logos
This commit is contained in:
Kai (Tam Nhu) Tran
2026-01-22 22:33:01 -05:00
committed by GitHub
15 changed files with 155 additions and 9 deletions
+9 -1
View File
@@ -95,6 +95,8 @@ The dashboard provides visual management for all account types:
| **Kiro** | OAuth | `ccs kiro` | AWS CodeWhisperer (Claude-powered) |
| **Antigravity** | OAuth | `ccs agy` | Alternative routing |
| **OpenRouter** | API Key | `ccs openrouter` | 300+ models, unified API |
| **Ollama** | Local | `ccs ollama` | Local open-source models, privacy |
| **Ollama Cloud** | API Key | `ccs ollama-cloud` | Cloud-hosted open-source models |
| **GLM** | API Key | `ccs glm` | Cost-optimized execution |
| **Kimi** | API Key | `ccs kimi` | Long-context, thinking mode |
| **Azure Foundry** | API Key | `ccs foundry` | Claude via Microsoft Azure |
@@ -104,6 +106,8 @@ The dashboard provides visual management for all account types:
**OpenRouter Integration** (v7.0.0): CCS v7.0.0 adds OpenRouter with interactive model picker, dynamic discovery, and tier mapping (opus/sonnet/haiku). Create via `ccs api create --preset openrouter` or dashboard.
**Ollama Integration**: Run local open-source models (qwen3-coder, gpt-oss:20b) with full privacy. Use `ccs api create --preset ollama` - requires [Ollama v0.14.0+](https://ollama.com) installed. For cloud models, use `ccs api create --preset ollama-cloud`.
**Azure Foundry**: Use `ccs api create --preset foundry` to set up Claude via Microsoft Azure AI Foundry. Requires Azure resource and API key from [ai.azure.com](https://ai.azure.com).
![OpenRouter API Profiles](assets/screenshots/api-profiles-openrouter.webp)
@@ -131,6 +135,7 @@ ccs codex # OpenAI Codex (OAuth)
ccs kiro # Kiro/AWS CodeWhisperer (OAuth)
ccs ghcp # GitHub Copilot (OAuth device flow)
ccs agy # Antigravity (OAuth)
ccs ollama # Local Ollama (no API key needed)
ccs glm # GLM (API key)
```
@@ -145,7 +150,10 @@ ccs work "design the authentication system"
# Terminal 2: Execution (GLM - cost optimized)
ccs glm "implement the user service from the plan"
# Terminal 3: Review (Gemini)
# Terminal 3: Local testing (Ollama - offline, privacy)
ccs ollama "run tests and generate coverage report"
# Terminal 4: Review (Gemini)
ccs gemini "review the implementation for security issues"
```
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "https://ollama.com",
"ANTHROPIC_AUTH_TOKEN": "YOUR_OLLAMA_CLOUD_API_KEY_HERE",
"ANTHROPIC_MODEL": "glm-4.7:cloud",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3-coder:480b",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.7:cloud",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "minimax-m2.1:cloud"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:11434",
"ANTHROPIC_AUTH_TOKEN": "ollama",
"ANTHROPIC_MODEL": "qwen3-coder",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-oss:120b",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-oss:20b",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3-coder"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "7.25.0",
"version": "7.25.0-dev.2",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+34
View File
@@ -17,6 +17,8 @@ export interface ProviderPreset {
apiKeyPlaceholder: string;
apiKeyHint: string;
category: PresetCategory;
/** Whether API key is required (default: true, set false for local providers) */
requiresApiKey: boolean;
/** Additional env vars for thinking mode, etc. */
extraEnv?: Record<string, string>;
/** Enable always thinking mode */
@@ -42,6 +44,19 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'sk-or-...',
apiKeyHint: 'Get your API key at openrouter.ai/keys',
category: 'recommended',
requiresApiKey: true,
},
{
id: 'ollama',
name: 'Ollama (Local)',
description: 'Local open-source models via Ollama (32K+ context)',
baseUrl: 'http://localhost:11434',
defaultProfileName: 'ollama',
defaultModel: 'qwen3-coder',
apiKeyPlaceholder: 'ollama',
apiKeyHint: 'Install Ollama from ollama.com - no API key needed for local',
category: 'recommended',
requiresApiKey: false,
},
// Alternative providers
{
@@ -54,6 +69,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'ghp_...',
apiKeyHint: 'Get your API key from Z.AI',
category: 'alternative',
requiresApiKey: true,
},
{
id: 'glmt',
@@ -65,6 +81,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'ghp_...',
apiKeyHint: 'Same API key as GLM',
category: 'alternative',
requiresApiKey: true,
extraEnv: {
ANTHROPIC_TEMPERATURE: '0.2',
ANTHROPIC_MAX_TOKENS: '65536',
@@ -85,6 +102,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Moonshot AI',
category: 'alternative',
requiresApiKey: true,
alwaysThinkingEnabled: true,
},
{
@@ -97,6 +115,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'YOUR_AZURE_API_KEY',
apiKeyHint: 'Create resource at ai.azure.com, get API key from Keys tab',
category: 'alternative',
requiresApiKey: true,
},
{
id: 'mm',
@@ -108,6 +127,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY_HERE',
apiKeyHint: 'Get your API key at platform.minimax.io',
category: 'alternative',
requiresApiKey: true,
},
{
id: 'deepseek',
@@ -119,6 +139,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key at platform.deepseek.com',
category: 'alternative',
requiresApiKey: true,
},
{
id: 'qwen',
@@ -130,6 +151,19 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio',
category: 'alternative',
requiresApiKey: true,
},
{
id: 'ollama-cloud',
name: 'Ollama Cloud',
description: 'Ollama cloud models via direct API (glm-4.7:cloud, minimax-m2.1:cloud)',
baseUrl: 'https://ollama.com',
defaultProfileName: 'ollama-cloud',
defaultModel: 'glm-4.7:cloud',
apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY',
apiKeyHint: 'Get your API key at ollama.com',
category: 'alternative',
requiresApiKey: true,
},
];
+21 -3
View File
@@ -181,9 +181,21 @@ async function handleCreate(args: string[]): Promise<void> {
console.log(dim('Note: For OpenRouter, ANTHROPIC_API_KEY should be empty.'));
}
// Step 3: API Key
// Step 3: API Key (skip if preset has requiresApiKey: false)
let apiKey = parsedArgs.apiKey;
if (!apiKey) {
if (preset?.requiresApiKey === false) {
// Preset doesn't require API key (e.g., local Ollama)
if (parsedArgs.apiKey) {
console.log(dim('Note: Using provided API key for local Ollama (optional)'));
apiKey = parsedArgs.apiKey;
} else {
console.log(info('No API key required for local Ollama'));
// Sentinel value 'ollama' matches config/base-ollama.settings.json template
// This is not a valid API key, just a placeholder for local-only providers
apiKey = 'ollama';
}
} else if (!apiKey) {
const keyPrompt = preset?.apiKeyHint ? `API Key (${preset.apiKeyHint})` : 'API Key';
apiKey = await InteractivePrompt.password(keyPrompt);
if (!apiKey) {
@@ -426,7 +438,7 @@ async function showHelp(): Promise<void> {
console.log('');
console.log(subheader('Options'));
console.log(
` ${color('--preset <id>', 'command')} Use provider preset (openrouter, glm, glmt, kimi, foundry, mm, deepseek, qwen)`
` ${color('--preset <id>', 'command')} Use provider preset (openrouter, ollama, ollama-cloud, glm, glmt, kimi, foundry, mm, deepseek, qwen)`
);
console.log(` ${color('--base-url <url>', 'command')} API base URL (create)`);
console.log(` ${color('--api-key <key>', 'command')} API key (create)`);
@@ -438,6 +450,12 @@ async function showHelp(): Promise<void> {
console.log(
` ${color('openrouter', 'command')} OpenRouter - 349+ models (Claude, GPT, Gemini, Llama)`
);
console.log(
` ${color('ollama', 'command')} Ollama - Local open-source models (no API key)`
);
console.log(
` ${color('ollama-cloud', 'command')} Ollama Cloud - glm-4.7:cloud, qwen3-coder:480b`
);
console.log(` ${color('glm', 'command')} GLM - Claude via Z.AI`);
console.log(` ${color('glmt', 'command')} GLMT - GLM with Thinking mode`);
console.log(` ${color('kimi', 'command')} Kimi - Moonshot AI reasoning model`);
+2
View File
@@ -127,6 +127,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
['ccs glm', 'GLM 4.6 (API key required)'],
['ccs glmt', 'GLM with thinking mode'],
['ccs kimi', 'Kimi for Coding (API key)'],
['ccs ollama', 'Local Ollama (http://localhost:11434)'],
['ccs ollama-cloud', 'Ollama Cloud (API key required)'],
['', ''], // Spacer
['ccs api create', 'Create custom API profile'],
['ccs api remove', 'Remove an API profile'],
+1
View File
@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>AzureAI</title><path clip-rule="evenodd" d="M16.233 0c.713 0 1.345.551 1.572 1.329.227.778 1.555 5.59 1.555 5.59v9.562h-4.813L14.645 0h1.588z" fill="url(#lobe-icons-azure-ai-fill-0)" fill-rule="evenodd"></path><path d="M23.298 7.47c0-.34-.275-.6-.6-.6h-2.835a3.617 3.617 0 00-3.614 3.615v5.996h3.436a3.617 3.617 0 003.613-3.614V7.47z" fill="url(#lobe-icons-azure-ai-fill-1)"></path><path clip-rule="evenodd" d="M16.233 0a.982.982 0 00-.989.989l-.097 18.198A4.814 4.814 0 0110.334 24H1.6a.597.597 0 01-.567-.794l7-19.981A4.819 4.819 0 0112.57 0h3.679-.016z" fill="url(#lobe-icons-azure-ai-fill-2)" fill-rule="evenodd"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-ai-fill-0" x1="18.242" x2="14.191" y1="16.837" y2=".616"><stop stop-color="#712575"></stop><stop offset=".09" stop-color="#9A2884"></stop><stop offset=".18" stop-color="#BF2C92"></stop><stop offset=".27" stop-color="#DA2E9C"></stop><stop offset=".34" stop-color="#EB30A2"></stop><stop offset=".4" stop-color="#F131A5"></stop><stop offset=".5" stop-color="#EC30A3"></stop><stop offset=".61" stop-color="#DF2F9E"></stop><stop offset=".72" stop-color="#C92D96"></stop><stop offset=".83" stop-color="#AA2A8A"></stop><stop offset=".95" stop-color="#83267C"></stop><stop offset="1" stop-color="#712575"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-ai-fill-1" x1="19.782" x2="19.782" y1=".34" y2="23.222"><stop stop-color="#DA7ED0"></stop><stop offset=".08" stop-color="#B17BD5"></stop><stop offset=".19" stop-color="#8778DB"></stop><stop offset=".3" stop-color="#6276E1"></stop><stop offset=".41" stop-color="#4574E5"></stop><stop offset=".54" stop-color="#2E72E8"></stop><stop offset=".67" stop-color="#1D71EB"></stop><stop offset=".81" stop-color="#1471EC"></stop><stop offset="1" stop-color="#1171ED"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-ai-fill-2" x1="18.404" x2="3.236" y1=".859" y2="25.183"><stop stop-color="#DA7ED0"></stop><stop offset=".05" stop-color="#B77BD4"></stop><stop offset=".11" stop-color="#9079DA"></stop><stop offset=".18" stop-color="#6E77DF"></stop><stop offset=".25" stop-color="#5175E3"></stop><stop offset=".33" stop-color="#3973E7"></stop><stop offset=".42" stop-color="#2772E9"></stop><stop offset=".54" stop-color="#1A71EB"></stop><stop offset=".68" stop-color="#1371EC"></stop><stop offset="1" stop-color="#1171ED"></stop></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kimi</title><path d="M19.738 5.776c.163-.209.306-.4.457-.585.07-.087.064-.153-.004-.244-.655-.861-.717-1.817-.34-2.787.283-.73.909-1.072 1.674-1.145.477-.045.945.004 1.379.236.57.305.902.77 1.01 1.412.086.512.07 1.012-.075 1.508-.257.878-.888 1.333-1.753 1.448-.718.096-1.446.108-2.17.157-.056.004-.113 0-.178 0z" fill="#027AFF"></path><path d="M17.962 1.844h-4.326l-3.425 7.81H5.369V1.878H1.5V22h3.87v-8.477h6.824a3.025 3.025 0 002.743-1.75V22h3.87v-8.477a3.87 3.87 0 00-3.588-3.86v-.01h-2.125a3.94 3.94 0 002.323-2.12l2.545-5.689z"></path></svg>

After

Width:  |  Height:  |  Size: 711 B

+1
View File
@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><defs><linearGradient id="lobe-icons-minimax-fill" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"></stop><stop offset="100%" stop-color="#FE603C"></stop></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-fill)" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Ollama</title><path d="M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>

After

Width:  |  Height:  |  Size: 319 B

@@ -47,7 +47,7 @@ const schema = z.object({
.min(1, 'Name is required')
.regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Must start with letter, only letters/numbers/.-_'),
baseUrl: z.string().url('Invalid URL format'),
apiKey: z.string().min(1, 'API key is required'),
apiKey: z.string(), // Validation handled conditionally in onSubmit
model: z.string().optional(),
opusModel: z.string().optional(),
sonnetModel: z.string().optional(),
@@ -207,9 +207,16 @@ export function ProfileCreateDialog({
}, [baseUrlValue, selectedPreset]);
const onSubmit = async (data: FormData) => {
// Validate API key - required unless preset has requiresApiKey: false
if (currentPreset?.requiresApiKey !== false && !data.apiKey) {
toast.error('API key is required');
return;
}
// Use user-provided baseUrl (allows customization of preset URLs)
const finalData = {
...data,
// Use provided API key, or empty string if not provided (for optional auth providers)
apiKey: data.apiKey || '',
};
try {
await createMutation.mutateAsync(finalData);
@@ -367,17 +374,27 @@ export function ProfileCreateDialog({
)}
</div>
{/* API Key */}
{/* API Key - optional for presets that don't require it */}
<div className="space-y-1.5">
<Label htmlFor="apiKey">
API Key <span className="text-destructive">*</span>
API Key{' '}
{currentPreset?.requiresApiKey !== false && (
<span className="text-destructive">*</span>
)}
{currentPreset?.requiresApiKey === false && (
<span className="text-muted-foreground text-xs font-normal">(optional)</span>
)}
</Label>
<div className="relative">
<Input
id="apiKey"
type={showApiKey ? 'text' : 'password'}
{...register('apiKey')}
placeholder={currentPreset?.apiKeyPlaceholder ?? 'sk-...'}
placeholder={
currentPreset?.requiresApiKey === false
? 'Optional - only if auth is enabled'
: (currentPreset?.apiKeyPlaceholder ?? 'sk-...')
}
className="pr-10"
/>
<Button
@@ -393,6 +410,10 @@ export function ProfileCreateDialog({
</div>
{errors.apiKey ? (
<p className="text-xs text-destructive">{errors.apiKey.message}</p>
) : currentPreset?.requiresApiKey === false ? (
<p className="text-xs text-muted-foreground">
Only needed if you have configured Ollama authentication
</p>
) : (
currentPreset?.apiKeyHint && (
<p className="text-xs text-muted-foreground">{currentPreset.apiKeyHint}</p>
+37
View File
@@ -44,6 +44,22 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyHint: 'Get your API key at openrouter.ai/keys',
category: 'recommended',
},
// Recommended - Ollama (Local)
{
id: 'ollama',
name: 'Ollama (Local)',
description: 'Local open-source models via Ollama (32K+ context)',
baseUrl: 'http://localhost:11434',
defaultProfileName: 'ollama',
badge: 'Local',
featured: true,
icon: '/icons/ollama.svg',
defaultModel: 'qwen3-coder',
requiresApiKey: false,
apiKeyPlaceholder: '',
apiKeyHint: 'No API key required for local Ollama',
category: 'recommended',
},
// Alternative providers - GLM/GLMT/Kimi
{
id: 'glm',
@@ -52,6 +68,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://api.z.ai/api/anthropic',
defaultProfileName: 'glm',
badge: 'Z.AI',
icon: '/icons/zai.svg',
defaultModel: 'glm-4.7',
requiresApiKey: true,
apiKeyPlaceholder: 'ghp_...',
@@ -65,6 +82,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://api.z.ai/api/coding/paas/v4/chat/completions',
defaultProfileName: 'glmt',
badge: 'Thinking',
icon: '/icons/zai.svg',
defaultModel: 'glm-4.7',
requiresApiKey: true,
apiKeyPlaceholder: 'ghp_...',
@@ -78,6 +96,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://api.kimi.com/coding/',
defaultProfileName: 'kimi',
badge: 'Reasoning',
icon: '/icons/kimi.svg',
defaultModel: 'kimi-k2-thinking-turbo',
requiresApiKey: true,
apiKeyPlaceholder: 'sk-...',
@@ -91,6 +110,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://<your-resource>.services.ai.azure.com/api/anthropic',
defaultProfileName: 'foundry',
badge: 'Azure',
icon: '/icons/azure.svg',
defaultModel: 'claude-sonnet-4-5',
requiresApiKey: true,
apiKeyPlaceholder: 'YOUR_AZURE_API_KEY',
@@ -104,6 +124,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://api.minimax.io/anthropic',
defaultProfileName: 'mm',
badge: '1M context',
icon: '/icons/minimax.svg',
defaultModel: 'MiniMax-M2.1',
requiresApiKey: true,
apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY_HERE',
@@ -117,6 +138,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://api.deepseek.com/anthropic',
defaultProfileName: 'deepseek',
badge: 'Reasoning',
icon: '/icons/deepseek.svg',
defaultModel: 'deepseek-chat',
requiresApiKey: true,
apiKeyPlaceholder: 'sk-...',
@@ -130,12 +152,27 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
baseUrl: 'https://dashscope-intl.aliyuncs.com/apps/anthropic',
defaultProfileName: 'qwen',
badge: 'Alibaba',
icon: '/assets/providers/qwen-color.svg',
defaultModel: 'qwen3-coder-plus',
requiresApiKey: true,
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio',
category: 'alternative',
},
{
id: 'ollama-cloud',
name: 'Ollama Cloud',
description: 'Ollama cloud models via direct API (glm-4.7:cloud, minimax-m2.1:cloud)',
baseUrl: 'https://ollama.com',
defaultProfileName: 'ollama-cloud',
badge: 'Cloud',
icon: '/icons/ollama.svg',
defaultModel: 'glm-4.7:cloud',
requiresApiKey: true,
apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY',
apiKeyHint: 'Get your API key at ollama.com',
category: 'alternative',
},
];
/** Get presets by category */