From 85ee71def72ba705955ebd10834114cc98bd0182 Mon Sep 17 00:00:00 2001 From: Aditya Kulraj Date: Tue, 24 Mar 2026 14:54:14 +0530 Subject: [PATCH] Clarify provider setup errors before processing --- .../src/handlers/oagent-sessions.handler.ts | 48 +++++++++++++++++++ electron/src/preload.ts | 1 + .../workspace/hooks/useWorkspaceSessions.ts | 44 ++++++++++++++++- src/types/window.d.ts | 6 +++ 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/electron/src/handlers/oagent-sessions.handler.ts b/electron/src/handlers/oagent-sessions.handler.ts index 104a19d..1f8a671 100644 --- a/electron/src/handlers/oagent-sessions.handler.ts +++ b/electron/src/handlers/oagent-sessions.handler.ts @@ -142,6 +142,26 @@ function inferProvider(llmProvider: StartOptions["llmProvider"], model?: string) return "agent"; } +async function checkOllamaReachability(endpointRaw?: string): Promise<{ ok: true } | { ok: false; error: string }> { + const endpoint = (endpointRaw || "http://localhost:11434").trim().replace(/\/+$/, ""); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 2000); + try { + const res = await fetch(`${endpoint}/api/tags`, { signal: controller.signal }); + if (!res.ok) { + return { ok: false, error: `Ollama endpoint returned ${res.status}. Start Ollama or update Settings → Ollama Endpoint.` }; + } + return { ok: true }; + } catch { + return { + ok: false, + error: `Ollama is not reachable at ${endpoint}. Start Ollama, fix the endpoint, or switch to OpenRouter in Settings.`, + }; + } finally { + clearTimeout(timeout); + } +} + function applyProviderOptions( queryOptions: Record, { @@ -450,6 +470,34 @@ export function register(getMainWindow: () => BrowserWindow | null): void { ipcMain.on(`agent:${suffix}`, handler); }; + handleBoth("check-provider-setup", async (_event, options: { + llmProvider?: "openrouter" | "ollama"; + model?: string; + openRouterKey?: string; + ollamaEndpoint?: string; + }) => { + const provider = inferProvider(options?.llmProvider, options?.model); + if (provider === "openrouter") { + const key = (options?.openRouterKey || "").trim(); + if (!key) { + return { + ok: false, + provider, + error: "OpenRouter API key missing. Add it in Settings → Models → OpenRouter API Key, or switch provider to Ollama.", + }; + } + return { ok: true, provider }; + } + if (provider === "ollama") { + const result = await checkOllamaReachability(options?.ollamaEndpoint); + if (!result.ok) { + return { ok: false, provider, error: result.error }; + } + return { ok: true, provider }; + } + return { ok: true, provider }; + }); + handleBoth("start", async (_event, options: StartOptions = {}) => { const sessionId = options.resume || crypto.randomUUID(); const query = await getSDK(); diff --git a/electron/src/preload.ts b/electron/src/preload.ts index 3af9e9b..3fcd9c5 100644 --- a/electron/src/preload.ts +++ b/electron/src/preload.ts @@ -43,6 +43,7 @@ const clientCoreApi = { ipcRenderer.invoke("oagent:mcp-reconnect", { sessionId, serverName }), restartSession: (sessionId: string, mcpServers?: unknown[]) => ipcRenderer.invoke("oagent:restart-session", { sessionId, mcpServers }), + checkProviderSetup: (options: unknown) => ipcRenderer.invoke("oagent:check-provider-setup", options), readFile: (filePath: string) => ipcRenderer.invoke("file:read", filePath), openInEditor: (filePath: string, line?: number) => ipcRenderer.invoke("file:open-in-editor", { filePath, line }), generateTitle: ( diff --git a/src/core/workspace/hooks/useWorkspaceSessions.ts b/src/core/workspace/hooks/useWorkspaceSessions.ts index 38a1b6a..0996bf5 100644 --- a/src/core/workspace/hooks/useWorkspaceSessions.ts +++ b/src/core/workspace/hooks/useWorkspaceSessions.ts @@ -160,6 +160,29 @@ export function useSessionManager(projects: Project[], settings: Settings) { return projectsRef.current.find((p) => p.id === projectId) ?? null; }, []); + const pushSystemMessage = useCallback((setMessagesFn: (updater: (prev: UIMessage[]) => UIMessage[]) => void, content: string) => { + setMessagesFn((prev) => [ + ...prev, + { + id: `system-setup-${Date.now()}`, + role: "system" as const, + content, + timestamp: Date.now(), + }, + ]); + }, []); + + const checkProviderSetup = useCallback(async (model?: string) => { + const settings = settingsRef.current; + const result = await window.clientCore.checkProviderSetup({ + llmProvider: settings.llmProvider, + model, + openRouterKey: settings.openRouterKey, + ollamaEndpoint: settings.ollamaEndpoint, + }); + return result; + }, []); + // Eagerly start a Agent SDK session for immediate MCP status display const eagerStartSession = useCallback(async (projectId: string, options?: StartOptions) => { const project = projectsRef.current.find((p) => p.id === projectId); @@ -1043,7 +1066,17 @@ export function useSessionManager(projects: Project[], settings: Settings) { ...(inferredProvider === "ollama" ? { ollamaEndpoint: settingsRef.current.ollamaEndpoint } : {}), }; + const setup = await checkProviderSetup(session.model); + if (!setup.ok) { + pushSystemMessage(engine.setMessages, `Setup required: ${setup.error}`); + return; + } + const result = await window.clientCore.start(startPayload); + if ("error" in result && result.error) { + pushSystemMessage(engine.setMessages, `Setup required: ${result.error}`); + return; + } const newSessionId = result.sessionId; if (newSessionId !== oldId) { @@ -1111,7 +1144,7 @@ export function useSessionManager(projects: Project[], settings: Settings) { }, ]); }, - [engine.setMessages, findProject], + [checkProviderSetup, engine.setMessages, findProject, pushSystemMessage], ); const send = useCallback( @@ -1119,6 +1152,13 @@ export function useSessionManager(projects: Project[], settings: Settings) { const activeId = activeSessionIdRef.current; if (activeId === DRAFT_ID) { const draftEngine = startOptionsRef.current.engine ?? "agent"; + if (draftEngine !== "oap") { + const setup = await checkProviderSetup(startOptionsRef.current.model); + if (!setup.ok) { + pushSystemMessage(agent.setMessages, `Setup required: ${setup.error}`); + return; + } + } const sessionId = await materializeDraft(text); if (!sessionId) return; await new Promise((resolve) => setTimeout(resolve, 50)); @@ -1211,7 +1251,7 @@ export function useSessionManager(projects: Project[], settings: Settings) { return; } }, - [agent.send, agent.setMessages, oap.send, oap.setMessages, oap.setIsProcessing, engine.setMessages, materializeDraft, reviveSession], + [agent.send, agent.setMessages, checkProviderSetup, oap.send, oap.setMessages, oap.setIsProcessing, engine.setMessages, materializeDraft, pushSystemMessage, reviveSession], ); const deselectSession = useCallback(async () => { diff --git a/src/types/window.d.ts b/src/types/window.d.ts index cc20cad..4ea790f 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -40,6 +40,12 @@ declare global { mcpStatus: (sessionId: string) => Promise<{ servers: McpServerStatus[]; error?: string }>; mcpReconnect: (sessionId: string, serverName: string) => Promise<{ ok?: boolean; error?: string; restarted?: boolean }>; restartSession: (sessionId: string, mcpServers?: McpServerConfig[]) => Promise<{ ok?: boolean; error?: string; restarted?: boolean }>; + checkProviderSetup: (options: { + llmProvider?: "openrouter" | "ollama"; + model?: string; + openRouterKey?: string; + ollamaEndpoint?: string; + }) => Promise<{ ok: boolean; provider: "openrouter" | "ollama"; error?: string }>; readFile: (filePath: string) => Promise<{ content?: string; error?: string }>; openInEditor: (filePath: string, line?: number) => Promise<{ ok?: boolean; editor?: string; error?: string }>; generateTitle: (