fix(provider): ollama provider fix (#246)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
||||
PROVIDER_TYPE_INFO,
|
||||
type ProviderType,
|
||||
getProviderIconUrl,
|
||||
resolveProviderApiKeyForSave,
|
||||
shouldInvertInDark,
|
||||
} from '@/lib/providers';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -66,6 +67,7 @@ export function ProvidersSettings() {
|
||||
// Only custom supports multiple instances.
|
||||
// Built-in providers remain singleton by type.
|
||||
const id = type === 'custom' ? `custom-${crypto.randomUUID()}` : type;
|
||||
const effectiveApiKey = resolveProviderApiKeyForSave(type, apiKey);
|
||||
try {
|
||||
await addProvider(
|
||||
{
|
||||
@@ -76,7 +78,7 @@ export function ProvidersSettings() {
|
||||
model: options?.model,
|
||||
enabled: true,
|
||||
},
|
||||
apiKey.trim() || undefined
|
||||
effectiveApiKey
|
||||
);
|
||||
|
||||
// Auto-set as default if no default is currently configured
|
||||
@@ -261,6 +263,12 @@ function ProviderCard({
|
||||
}
|
||||
}
|
||||
|
||||
// Keep Ollama key optional in UI, but persist a placeholder when
|
||||
// editing legacy configs that have no stored key.
|
||||
if (provider.type === 'ollama' && !provider.hasKey && !payload.newApiKey) {
|
||||
payload.newApiKey = resolveProviderApiKeyForSave(provider.type, '') as string;
|
||||
}
|
||||
|
||||
if (!payload.newApiKey && !payload.updates) {
|
||||
onCancelEdit();
|
||||
setSaving(false);
|
||||
|
||||
@@ -21,6 +21,8 @@ export const PROVIDER_TYPES = [
|
||||
] as const;
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
export const OLLAMA_PLACEHOLDER_API_KEY = 'ollama-local';
|
||||
|
||||
export interface ProviderConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -77,7 +79,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
|
||||
{ id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.5', apiKeyUrl: 'https://intl.minimaxi.com/' },
|
||||
{ id: 'minimax-portal-cn', name: 'MiniMax (CN)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.5', apiKeyUrl: 'https://platform.minimaxi.com/' },
|
||||
{ id: 'qwen-portal', name: 'Qwen', icon: '☁️', placeholder: 'sk-...', model: 'Qwen', requiresApiKey: false, isOAuth: true, defaultModelId: 'coder-model' },
|
||||
{ id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' },
|
||||
{ id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' },
|
||||
{ id: 'custom', name: 'Custom', icon: '⚙️', placeholder: 'API key...', requiresApiKey: true, showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'your-provider/model-id' },
|
||||
];
|
||||
|
||||
@@ -98,3 +100,12 @@ export const SETUP_PROVIDERS = PROVIDER_TYPE_INFO;
|
||||
export function getProviderTypeInfo(type: ProviderType): ProviderTypeInfo | undefined {
|
||||
return PROVIDER_TYPE_INFO.find((t) => t.id === type);
|
||||
}
|
||||
|
||||
/** Normalize provider API key before saving; Ollama uses a local placeholder when blank. */
|
||||
export function resolveProviderApiKeyForSave(type: ProviderType | string, apiKey: string): string | undefined {
|
||||
const trimmed = apiKey.trim();
|
||||
if (type === 'ollama') {
|
||||
return trimmed || OLLAMA_PLACEHOLDER_API_KEY;
|
||||
}
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ const defaultSkills: DefaultSkill[] = [
|
||||
{ id: 'terminal', name: 'Terminal', description: 'Shell command execution' },
|
||||
];
|
||||
|
||||
import { SETUP_PROVIDERS, type ProviderTypeInfo, getProviderIconUrl, shouldInvertInDark } from '@/lib/providers';
|
||||
import { SETUP_PROVIDERS, type ProviderTypeInfo, getProviderIconUrl, resolveProviderApiKeyForSave, shouldInvertInDark } from '@/lib/providers';
|
||||
import clawxIcon from '@/assets/logo.svg';
|
||||
|
||||
// Use the shared provider registry for setup providers
|
||||
@@ -970,6 +970,8 @@ function ProviderContent({
|
||||
: `custom-${crypto.randomUUID()}`)
|
||||
: selectedProvider;
|
||||
|
||||
const effectiveApiKey = resolveProviderApiKeyForSave(selectedProvider, apiKey);
|
||||
|
||||
// Save provider config + API key, then set as default
|
||||
const saveResult = await window.electron.ipcRenderer.invoke(
|
||||
'provider:save',
|
||||
@@ -983,7 +985,7 @@ function ProviderContent({
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
apiKey || undefined
|
||||
effectiveApiKey
|
||||
) as { success: boolean; error?: string };
|
||||
|
||||
if (!saveResult.success) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { PROVIDER_TYPES, PROVIDER_TYPE_INFO } from '@/lib/providers';
|
||||
import { PROVIDER_TYPES, PROVIDER_TYPE_INFO, resolveProviderApiKeyForSave } from '@/lib/providers';
|
||||
import {
|
||||
BUILTIN_PROVIDER_TYPES,
|
||||
getProviderConfig,
|
||||
@@ -39,4 +39,26 @@ describe('provider metadata', () => {
|
||||
expect.arrayContaining(['anthropic', 'openai', 'google', 'openrouter', 'ark', 'moonshot', 'siliconflow', 'minimax-portal', 'minimax-portal-cn', 'qwen-portal', 'ollama'])
|
||||
);
|
||||
});
|
||||
|
||||
it('uses OpenAI-compatible Ollama default base URL', () => {
|
||||
expect(PROVIDER_TYPE_INFO).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'ollama',
|
||||
defaultBaseUrl: 'http://localhost:11434/v1',
|
||||
requiresApiKey: false,
|
||||
showBaseUrl: true,
|
||||
showModelId: true,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes provider API keys for save flow', () => {
|
||||
expect(resolveProviderApiKeyForSave('ollama', '')).toBe('ollama-local');
|
||||
expect(resolveProviderApiKeyForSave('ollama', ' ')).toBe('ollama-local');
|
||||
expect(resolveProviderApiKeyForSave('ollama', 'real-key')).toBe('real-key');
|
||||
expect(resolveProviderApiKeyForSave('openai', '')).toBeUndefined();
|
||||
expect(resolveProviderApiKeyForSave('openai', ' sk-test ')).toBe('sk-test');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user