feat: add interactive setup wizard and model error handling

Add comprehensive setup command with provider selection, API key
configuration, and model selection. Include error recovery for
model-not-found scenarios with suggested alternatives and automatic
retry functionality. Update Anthropic model list with latest versions
and add provider metadata for better user experience.
This commit is contained in:
di-sukharev
2026-01-17 23:04:43 +03:00
parent ebbaff0628
commit d70797b864
13 changed files with 7301 additions and 4500 deletions

View File

@@ -8,6 +8,7 @@ import axios from 'axios';
import chalk from 'chalk';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { ModelNotFoundError } from '../utils/errors';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
@@ -59,6 +60,20 @@ export class AnthropicEngine implements AiEngine {
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
// Check for model not found errors
if (err.message?.toLowerCase().includes('model') &&
(err.message?.toLowerCase().includes('not found') ||
err.message?.toLowerCase().includes('does not exist') ||
err.message?.toLowerCase().includes('invalid'))) {
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
}
// Check for 404 errors
if ('status' in (error as any) && (error as any).status === 404) {
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
}
outro(`${chalk.red('✖')} ${err?.message || err}`);
if (
@@ -73,6 +88,11 @@ export class AnthropicEngine implements AiEngine {
);
}
// Check axios 404 errors
if (axios.isAxiosError(error) && error.response?.status === 404) {
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
}
throw err;
}
};

View File

@@ -7,6 +7,7 @@ import {
} from '@google/generative-ai';
import axios from 'axios';
import { OpenAI } from 'openai';
import { ModelNotFoundError } from '../utils/errors';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
@@ -76,6 +77,15 @@ export class GeminiEngine implements AiEngine {
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
// Check for model not found errors
if (err.message?.toLowerCase().includes('model') &&
(err.message?.toLowerCase().includes('not found') ||
err.message?.toLowerCase().includes('does not exist') ||
err.message?.toLowerCase().includes('invalid'))) {
throw new ModelNotFoundError(this.config.model, 'gemini', 404);
}
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
@@ -84,6 +94,11 @@ export class GeminiEngine implements AiEngine {
if (geminiError) throw new Error(geminiError?.message);
}
// Check axios 404 errors
if (axios.isAxiosError(error) && error.response?.status === 404) {
throw new ModelNotFoundError(this.config.model, 'gemini', 404);
}
throw err;
}
}

View File

@@ -1,5 +1,6 @@
import axios, { AxiosInstance } from 'axios';
import { OpenAI } from 'openai';
import { ModelNotFoundError } from '../utils/errors';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
@@ -46,6 +47,20 @@ export class OllamaEngine implements AiEngine {
return removeContentTags(content, 'think');
} catch (err: any) {
const message = err.response?.data?.error ?? err.message;
// Check for model not found errors
if (message?.toLowerCase().includes('model') &&
(message?.toLowerCase().includes('not found') ||
message?.toLowerCase().includes('does not exist') ||
message?.toLowerCase().includes('pull'))) {
throw new ModelNotFoundError(this.config.model, 'ollama', 404);
}
// Check for 404 status
if (err.response?.status === 404) {
throw new ModelNotFoundError(this.config.model, 'ollama', 404);
}
throw new Error(`Ollama provider error: ${message}`);
}
}

View File

@@ -2,6 +2,7 @@ import axios from 'axios';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { parseCustomHeaders } from '../utils/engine';
import { ModelNotFoundError } from '../utils/errors';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
@@ -62,6 +63,20 @@ export class OpenAiEngine implements AiEngine {
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
// Check for model not found errors
if (err.message?.toLowerCase().includes('model') &&
(err.message?.toLowerCase().includes('not found') ||
err.message?.toLowerCase().includes('does not exist') ||
err.message?.toLowerCase().includes('invalid'))) {
throw new ModelNotFoundError(this.config.model, 'openai', 404);
}
// Check for 404 errors from API
if ('status' in (error as any) && (error as any).status === 404) {
throw new ModelNotFoundError(this.config.model, 'openai', 404);
}
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
@@ -71,6 +86,11 @@ export class OpenAiEngine implements AiEngine {
if (openAiError) throw new Error(openAiError.message);
}
// Check axios 404 errors
if (axios.isAxiosError(error) && error.response?.status === 404) {
throw new ModelNotFoundError(this.config.model, 'openai', 404);
}
throw err;
}
};