From a3e5109107215d28d5046182bb504ba45ee5aa02 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 18:46:55 +1100 Subject: [PATCH 1/4] fix: check providerSettings for Ollama/LMStudio baseUrl configuration --- app/lib/modules/llm/manager.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/lib/modules/llm/manager.ts b/app/lib/modules/llm/manager.ts index bf1e3fa5..aaaca229 100644 --- a/app/lib/modules/llm/manager.ts +++ b/app/lib/modules/llm/manager.ts @@ -113,6 +113,7 @@ export class LLMManager { provider: BaseProvider, apiKeys?: Record, serverEnv?: Record, + providerSettings?: Record, ): boolean { // Check if provider has API key configuration const config = provider.config; @@ -128,7 +129,10 @@ export class LLMManager { // For local providers like Ollama and LMStudio, check if baseUrl is configured if (provider.name === 'Ollama' || provider.name === 'LMStudio') { const baseUrlKey = provider.name === 'Ollama' ? 'OLLAMA_API_BASE_URL' : 'LMSTUDIO_API_BASE_URL'; - const hasBaseUrl = apiKeys?.[baseUrlKey] || serverEnv?.[baseUrlKey]; + const hasBaseUrl = + providerSettings?.[provider.name]?.baseUrl || + apiKeys?.[baseUrlKey] || + serverEnv?.[baseUrlKey]; if (!hasBaseUrl) { return false; @@ -217,7 +221,7 @@ export class LLMManager { // Check if provider has required configuration before attempting fetch const providerConfig = providerSettings?.[provider.name]; - const hasRequiredConfig = this._hasRequiredConfiguration(provider, apiKeys, serverEnv); + const hasRequiredConfig = this._hasRequiredConfiguration(provider, apiKeys, serverEnv, providerSettings); if (!hasRequiredConfig) { logger.debug(`Skipping ${provider.name}: missing required configuration`); From e98393d866f9bc0c16bc2cbb8a487ef2be584a21 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 18:50:18 +1100 Subject: [PATCH 2/4] fix: add error handling and proper Docker host mapping to Ollama provider --- app/lib/modules/llm/providers/ollama.ts | 42 ++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/app/lib/modules/llm/providers/ollama.ts b/app/lib/modules/llm/providers/ollama.ts index 0c82c987..ac31d39b 100644 --- a/app/lib/modules/llm/providers/ollama.ts +++ b/app/lib/modules/llm/providers/ollama.ts @@ -83,18 +83,36 @@ export default class OllamaProvider extends BaseProvider { */ const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse Ollama baseUrl for Docker mapping:', error); + } + } } const response = await fetch(`${baseUrl}/api/tags`); + + if (!response.ok) { + throw new Error( + `Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`, + ); + } + const data = (await response.json()) as OllamaApiResponse; - // console.log({ ollamamodels: data.models }); + if (!data || !Array.isArray(data.models)) { + throw new Error('Invalid response from Ollama API: missing models array'); + } return data.models.map((model: OllamaModel) => ({ name: model.name, - label: `${model.name} (${model.details.parameter_size})`, + label: `${model.name} (${model.details?.parameter_size || 'unknown'})`, provider: this.name, maxTokenAllowed: 8000, maxCompletionTokens: 8000, @@ -119,14 +137,24 @@ export default class OllamaProvider extends BaseProvider { defaultApiTokenKey: '', }); - // Backend: Check if we're running in Docker if (!baseUrl) { throw new Error('No baseUrl found for OLLAMA provider'); } + // Backend: Check if we're running in Docker const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || envRecord.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse Ollama baseUrl for Docker mapping:', error); + } + } logger.debug('Ollama Base Url used: ', baseUrl); From 4826555c7e4c5a04bbd44adebdecf659b9adff10 Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 19:06:20 +1100 Subject: [PATCH 3/4] fix: use provider settings baseUrl in LocalProvidersTab instead of hardcoded URL --- app/components/@settings/core/constants.ts | 4 --- .../providers/cloud/CloudProvidersTab.tsx | 2 +- .../providers/local/LocalProvidersTab.tsx | 25 ++++++++++++++++--- app/lib/modules/llm/manager.ts | 5 +--- app/lib/modules/llm/providers/ollama.ts | 6 ++--- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/components/@settings/core/constants.ts b/app/components/@settings/core/constants.ts index d74c211a..bd0a95ab 100644 --- a/app/components/@settings/core/constants.ts +++ b/app/components/@settings/core/constants.ts @@ -62,13 +62,11 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'local-providers', visible: true, window: 'user' as const, order: 3 }, { id: 'connection', visible: true, window: 'user' as const, order: 4 }, { id: 'notifications', visible: true, window: 'user' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'user' as const, order: 6 }, // User Window Tabs (In dropdown, initially hidden) { id: 'profile', visible: false, window: 'user' as const, order: 7 }, { id: 'settings', visible: false, window: 'user' as const, order: 8 }, { id: 'api-keys', visible: true, window: 'user' as const, order: 9 }, - { id: 'task-manager', visible: false, window: 'user' as const, order: 10 }, { id: 'service-status', visible: false, window: 'user' as const, order: 11 }, // User Window Tabs (Hidden, controlled by TaskManagerTab) @@ -82,11 +80,9 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'local-providers', visible: true, window: 'developer' as const, order: 3 }, { id: 'connection', visible: true, window: 'developer' as const, order: 4 }, { id: 'notifications', visible: true, window: 'developer' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'developer' as const, order: 6 }, { id: 'profile', visible: true, window: 'developer' as const, order: 7 }, { id: 'settings', visible: true, window: 'developer' as const, order: 8 }, { id: 'api-keys', visible: true, window: 'developer' as const, order: 9 }, - { id: 'task-manager', visible: true, window: 'developer' as const, order: 10 }, { id: 'service-status', visible: true, window: 'developer' as const, order: 11 }, { id: 'debug', visible: true, window: 'developer' as const, order: 12 }, { id: 'update', visible: true, window: 'developer' as const, order: 13 }, diff --git a/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx b/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx index 8411ff85..317df5db 100644 --- a/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +++ b/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx @@ -44,7 +44,7 @@ const PROVIDER_ICONS: Record = { // Update PROVIDER_DESCRIPTIONS to use the same type const PROVIDER_DESCRIPTIONS: Partial> = { Anthropic: 'Access Claude and other Anthropic models', - OpenAI: 'Use GPT-4, GPT-3.5, and other OpenAI models', + OpenAI: 'Use GPT-5.2, GPT-4.5, and other OpenAI models', }; const CloudProvidersTab = () => { diff --git a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx index 2e280b38..f0b52bc2 100644 --- a/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +++ b/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx @@ -148,7 +148,15 @@ export default function LocalProvidersTab() { try { setIsLoadingModels(true); - const response = await fetch('http://127.0.0.1:11434/api/tags'); + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/tags`); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + const data = (await response.json()) as { models: OllamaModel[] }; setOllamaModels( @@ -159,6 +167,9 @@ export default function LocalProvidersTab() { ); } catch (error) { console.error('Error fetching Ollama models:', error); + + const errorMsg = error instanceof Error ? error.message : 'Unknown error occurred'; + toast(`Failed to fetch Ollama models: ${errorMsg}`); } finally { setIsLoadingModels(false); } @@ -166,7 +177,10 @@ export default function LocalProvidersTab() { const updateOllamaModel = async (modelName: string): Promise => { try { - const response = await fetch(`${OLLAMA_API_URL}/api/pull`, { + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: modelName }), @@ -218,7 +232,7 @@ export default function LocalProvidersTab() { } } - const updatedResponse = await fetch('http://127.0.0.1:11434/api/tags'); + const updatedResponse = await fetch(`${baseUrl}/api/tags`); const updatedData = (await updatedResponse.json()) as { models: OllamaModel[] }; const updatedModel = updatedData.models.find((m) => m.name === modelName); @@ -275,7 +289,10 @@ export default function LocalProvidersTab() { const handleDeleteOllamaModel = async (modelName: string) => { try { - const response = await fetch(`${OLLAMA_API_URL}/api/delete`, { + const ollamaProvider = filteredProviders.find((p) => p.name === 'Ollama'); + const baseUrl = ollamaProvider?.settings.baseUrl || OLLAMA_API_URL; + + const response = await fetch(`${baseUrl}/api/delete`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/modules/llm/manager.ts b/app/lib/modules/llm/manager.ts index aaaca229..d4488fed 100644 --- a/app/lib/modules/llm/manager.ts +++ b/app/lib/modules/llm/manager.ts @@ -129,10 +129,7 @@ export class LLMManager { // For local providers like Ollama and LMStudio, check if baseUrl is configured if (provider.name === 'Ollama' || provider.name === 'LMStudio') { const baseUrlKey = provider.name === 'Ollama' ? 'OLLAMA_API_BASE_URL' : 'LMSTUDIO_API_BASE_URL'; - const hasBaseUrl = - providerSettings?.[provider.name]?.baseUrl || - apiKeys?.[baseUrlKey] || - serverEnv?.[baseUrlKey]; + const hasBaseUrl = providerSettings?.[provider.name]?.baseUrl || apiKeys?.[baseUrlKey] || serverEnv?.[baseUrlKey]; if (!hasBaseUrl) { return false; diff --git a/app/lib/modules/llm/providers/ollama.ts b/app/lib/modules/llm/providers/ollama.ts index ac31d39b..ae6ccd5c 100644 --- a/app/lib/modules/llm/providers/ollama.ts +++ b/app/lib/modules/llm/providers/ollama.ts @@ -86,6 +86,7 @@ export default class OllamaProvider extends BaseProvider { if (isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); @@ -99,9 +100,7 @@ export default class OllamaProvider extends BaseProvider { const response = await fetch(`${baseUrl}/api/tags`); if (!response.ok) { - throw new Error( - `Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`, - ); + throw new Error(`Failed to fetch Ollama models: HTTP ${response.status} ${response.statusText}`); } const data = (await response.json()) as OllamaApiResponse; @@ -147,6 +146,7 @@ export default class OllamaProvider extends BaseProvider { if (isDocker) { try { const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { url.hostname = 'host.docker.internal'; baseUrl = url.toString().replace(/\/$/, ''); From 3920af6edcf81c19044c9b4d998791b85f58265b Mon Sep 17 00:00:00 2001 From: Gerome-Elassaad Date: Fri, 19 Dec 2025 19:07:55 +1100 Subject: [PATCH 4/4] fix: add error handling and proper Docker host mapping to LMStudio provider --- app/lib/modules/llm/providers/lmstudio.ts | 37 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/app/lib/modules/llm/providers/lmstudio.ts b/app/lib/modules/llm/providers/lmstudio.ts index b592048a..d00af7bb 100644 --- a/app/lib/modules/llm/providers/lmstudio.ts +++ b/app/lib/modules/llm/providers/lmstudio.ts @@ -42,13 +42,33 @@ export default class LMStudioProvider extends BaseProvider { */ const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse LMStudio baseUrl for Docker mapping:', error); + } + } } const response = await fetch(`${baseUrl}/v1/models`); + + if (!response.ok) { + throw new Error( + `Failed to fetch LMStudio models: HTTP ${response.status} ${response.statusText}`, + ); + } + const data = (await response.json()) as { data: Array<{ id: string }> }; + if (!data || !Array.isArray(data.data)) { + throw new Error('Invalid response from LMStudio API: missing data array'); + } + return data.data.map((model) => ({ name: model.id, label: model.id, @@ -78,9 +98,16 @@ export default class LMStudioProvider extends BaseProvider { const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true'; - if (typeof window === 'undefined') { - baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl; - baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl; + if (typeof window === 'undefined' && isDocker) { + try { + const url = new URL(baseUrl); + if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + url.hostname = 'host.docker.internal'; + baseUrl = url.toString().replace(/\/$/, ''); + } + } catch (error) { + logger.warn('Failed to parse LMStudio baseUrl for Docker mapping:', error); + } } logger.debug('LMStudio Base Url used: ', baseUrl);