Files
obsidian_ollama/src/error-handler.ts
T
fegger 138890b9d2 feat: improve UX for Ollama 404 errors (missing model)
- src/ollama-client.ts: Detect HTTP 404 on /api/chat and throw a descriptive
  ApiError with the model name and the exact ollama pull command needed.

- src/error-handler.ts: For API_ERROR type, return the error message directly
  instead of prefixing with 'API error: ', so the user-friendly 404 message
  is shown cleanly in the Obsidian notice.

- tests/ollama-client.test.ts: Update 404 assertions to match the new
  descriptive error message.
2026-05-19 21:13:08 +02:00

136 lines
4.5 KiB
TypeScript

// src/error-handler.ts
import { Notice } from 'obsidian';
import {
OllamaError,
ErrorType,
NetworkError,
ApiError,
ValidationError,
StreamingError,
ToolExecutionError,
PathValidationError,
} from './types';
export class ErrorHandler {
static handleError(error: unknown, context?: string): void {
const message = this.getUserFriendlyMessage(error);
new Notice(message);
if (error instanceof Error) {
const ctx = context ? ` [${context}]` : '';
// Use console.error instead of ErrorHandler.error for fatal errors
console.error(`Ollama Plugin Error${ctx}: ${error.message}`);
if (error.stack) {
console.error(error.stack);
}
} else {
const ctx = context ? ` [${context}]` : '';
console.error(`Ollama Plugin Error${ctx}:`, error);
}
}
private static getUserFriendlyMessage(error: unknown): string {
if (error instanceof OllamaError) {
return this.getUserFriendlyMessageFromOllamaError(error);
}
if (error instanceof Error) {
return this.getUserFriendlyMessageFromError(error);
}
return 'An unexpected error occurred';
}
private static getUserFriendlyMessageFromOllamaError(error: OllamaError): string {
switch (error.type) {
case ErrorType.NETWORK_ERROR:
return 'Connection error. Please check if Ollama is running.';
case ErrorType.API_ERROR:
return error.message;
case ErrorType.VALIDATION_ERROR:
return this.getUserFriendlyValidationMessage(error);
case ErrorType.STREAMING_ERROR:
return 'Response too long. Please try a shorter request.';
case ErrorType.TOOL_EXECUTION_ERROR:
return `Tool error for ${(error as ToolExecutionError).toolName}. ${error.message}`;
case ErrorType.PATH_VALIDATION_ERROR:
return `Invalid file path: ${(error as PathValidationError).path}`;
case ErrorType.UNKNOWN_ERROR:
return 'An unexpected error occurred';
default:
return 'An unexpected error occurred';
}
}
private static getUserFriendlyValidationMessage(error: OllamaError): string {
if (error instanceof ValidationError && error.details?.field) {
const fieldMsg = error.details.field.charAt(0).toUpperCase() + error.details.field.slice(1);
return `Invalid ${fieldMsg.toLowerCase()}. ${error.details.message ?? error.message}`;
}
return 'Input validation error. Please correct your input.';
}
private static getUserFriendlyMessageFromError(error: Error): string {
const msg = error.message.toLowerCase();
// Check timeout BEFORE network (more specific matches first)
if (msg.includes('timeout') || msg.includes('timed out') || msg.includes('time out')) {
return 'Request timed out. Please check your Ollama connection.';
}
if (msg.includes('network') || msg.includes('connection') || msg.includes('fetch')) {
return 'Connection error. Please check if Ollama is running.';
}
if (msg.includes('validation') || msg.includes('invalid')) {
return 'Invalid input. Please correct your input.';
}
if (msg.includes('stream') || msg.includes('chunk')) {
return 'Response too long. Please try a shorter request.';
}
if (msg.includes('tool') || msg.includes('function')) {
return 'Tool error. Please try again.';
}
if (msg.includes('path') || msg.includes('file')) {
return 'Invalid file path. Please check the path and try again.';
}
return 'An unexpected error occurred';
}
// -- Factory methods --
static createNetworkError(message: string, statusCode?: number): NetworkError {
return new NetworkError(message, statusCode);
}
static createApiError(message: string, statusCode?: number): ApiError {
return new ApiError(message, statusCode ?? 500);
}
static createValidationError(message: string, field?: string): ValidationError {
const details = field ? { field, message } : undefined;
return new ValidationError(message, details);
}
static createStreamingError(message: string): StreamingError {
return new StreamingError(message);
}
static createToolExecutionError(message: string, toolName?: string): ToolExecutionError {
return new ToolExecutionError(message, toolName ?? 'unknown');
}
static createPathValidationError(message: string, path?: string): PathValidationError {
return new PathValidationError(message, path ?? '');
}
static createUnknownError(message: string): OllamaError {
return new OllamaError(message, ErrorType.UNKNOWN_ERROR);
}
}