Skip to content

Commit 5b241ed

Browse files
committed
refactor: enhance error handling and normalization across AI engines
This update introduces a centralized error handling mechanism for various AI engines, improving the consistency and clarity of error messages. The new `normalizeEngineError` function standardizes error responses, allowing for better user feedback and recovery suggestions. Additionally, specific error classes for insufficient credits, rate limits, and service availability have been implemented, along with user-friendly formatting for error messages. This refactor aims to enhance the overall user experience when interacting with the AI services.
1 parent 8b0ee25 commit 5b241ed

File tree

16 files changed

+1081
-412
lines changed

16 files changed

+1081
-412
lines changed

out/cli.cjs

Lines changed: 367 additions & 112 deletions
Large diffs are not rendered by default.

out/github-action.cjs

Lines changed: 170 additions & 109 deletions
Large diffs are not rendered by default.

src/commands/commit.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {
1111
import chalk from 'chalk';
1212
import { execa } from 'execa';
1313
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
14+
import {
15+
formatUserFriendlyError,
16+
printFormattedError
17+
} from '../utils/errors';
1418
import {
1519
assertGitRepo,
1620
getChangedFiles,
@@ -211,10 +215,11 @@ ${chalk.grey('——————————————————')}`
211215
`${chalk.red('✖')} Failed to generate the commit message`
212216
);
213217

214-
console.log(error);
218+
const errorConfig = getConfig();
219+
const provider = errorConfig.OCO_AI_PROVIDER || 'openai';
220+
const formatted = formatUserFriendlyError(error, provider);
221+
outro(printFormattedError(formatted));
215222

216-
const err = error as Error;
217-
outro(`${chalk.red('✖')} ${err?.message || err}`);
218223
process.exit(1);
219224
}
220225
};

src/engine/aimlapi.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import OpenAI from 'openai';
22
import axios, { AxiosInstance } from 'axios';
3+
import { normalizeEngineError } from '../utils/engineErrorHandler';
34
import { AiEngine, AiEngineConfig } from './Engine';
45

56
interface AimlApiConfig extends AiEngineConfig {}
@@ -32,16 +33,7 @@ export class AimlApiEngine implements AiEngine {
3233
const message = response.data.choices?.[0]?.message;
3334
return message?.content ?? null;
3435
} catch (error) {
35-
const err = error as Error;
36-
if (
37-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
38-
error.response?.status === 401
39-
) {
40-
const apiError = error.response.data.error;
41-
if (apiError) throw new Error(apiError.message);
42-
}
43-
44-
throw err;
36+
throw normalizeEngineError(error, 'aimlapi', this.config.model);
4537
}
4638
};
4739
}

src/engine/anthropic.ts

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ import {
33
MessageCreateParamsNonStreaming,
44
MessageParam
55
} from '@anthropic-ai/sdk/resources/messages.mjs';
6-
import { outro } from '@clack/prompts';
7-
import axios from 'axios';
8-
import chalk from 'chalk';
96
import { OpenAI } from 'openai';
107
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
11-
import { ModelNotFoundError } from '../utils/errors';
8+
import { normalizeEngineError } from '../utils/engineErrorHandler';
129
import { removeContentTags } from '../utils/removeContentTags';
1310
import { tokenCount } from '../utils/tokenCount';
1411
import { AiEngine, AiEngineConfig } from './Engine';
@@ -59,41 +56,7 @@ export class AnthropicEngine implements AiEngine {
5956
let content = message;
6057
return removeContentTags(content, 'think');
6158
} catch (error) {
62-
const err = error as Error;
63-
64-
// Check for model not found errors
65-
if (err.message?.toLowerCase().includes('model') &&
66-
(err.message?.toLowerCase().includes('not found') ||
67-
err.message?.toLowerCase().includes('does not exist') ||
68-
err.message?.toLowerCase().includes('invalid'))) {
69-
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
70-
}
71-
72-
// Check for 404 errors
73-
if ('status' in (error as any) && (error as any).status === 404) {
74-
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
75-
}
76-
77-
outro(`${chalk.red('✖')} ${err?.message || err}`);
78-
79-
if (
80-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
81-
error.response?.status === 401
82-
) {
83-
const anthropicAiError = error.response.data.error;
84-
85-
if (anthropicAiError?.message) outro(anthropicAiError.message);
86-
outro(
87-
'For help look into README https://github.com/di-sukharev/opencommit#setup'
88-
);
89-
}
90-
91-
// Check axios 404 errors
92-
if (axios.isAxiosError(error) && error.response?.status === 404) {
93-
throw new ModelNotFoundError(this.config.model, 'anthropic', 404);
94-
}
95-
96-
throw err;
59+
throw normalizeEngineError(error, 'anthropic', this.config.model);
9760
}
9861
};
9962
}

src/engine/azure.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import {
22
AzureKeyCredential,
33
OpenAIClient as AzureOpenAIClient
44
} from '@azure/openai';
5-
import { outro } from '@clack/prompts';
6-
import axios from 'axios';
7-
import chalk from 'chalk';
85
import { OpenAI } from 'openai';
96
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
7+
import { normalizeEngineError } from '../utils/engineErrorHandler';
108
import { removeContentTags } from '../utils/removeContentTags';
119
import { tokenCount } from '../utils/tokenCount';
1210
import { AiEngine, AiEngineConfig } from './Engine';
@@ -57,24 +55,7 @@ export class AzureEngine implements AiEngine {
5755
let content = message?.content;
5856
return removeContentTags(content, 'think');
5957
} catch (error) {
60-
outro(`${chalk.red('✖')} ${this.config.model}`);
61-
62-
const err = error as Error;
63-
outro(`${chalk.red('✖')} ${JSON.stringify(error)}`);
64-
65-
if (
66-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
67-
error.response?.status === 401
68-
) {
69-
const openAiError = error.response.data.error;
70-
71-
if (openAiError?.message) outro(openAiError.message);
72-
outro(
73-
'For help look into README https://github.com/di-sukharev/opencommit#setup'
74-
);
75-
}
76-
77-
throw err;
58+
throw normalizeEngineError(error, 'azure', this.config.model);
7859
}
7960
};
8061
}

src/engine/deepseek.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import axios from 'axios';
21
import { OpenAI } from 'openai';
32
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
3+
import { normalizeEngineError } from '../utils/engineErrorHandler';
44
import { removeContentTags } from '../utils/removeContentTags';
55
import { tokenCount } from '../utils/tokenCount';
66
import { OpenAiEngine, OpenAiConfig } from './openAi';
@@ -45,17 +45,7 @@ export class DeepseekEngine extends OpenAiEngine {
4545
let content = message?.content;
4646
return removeContentTags(content, 'think');
4747
} catch (error) {
48-
const err = error as Error;
49-
if (
50-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
51-
error.response?.status === 401
52-
) {
53-
const openAiError = error.response.data.error;
54-
55-
if (openAiError) throw new Error(openAiError.message);
56-
}
57-
58-
throw err;
48+
throw normalizeEngineError(error, 'deepseek', this.config.model);
5949
}
6050
};
6151
}

src/engine/flowise.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios, { AxiosInstance } from 'axios';
22
import { OpenAI } from 'openai';
3+
import { normalizeEngineError } from '../utils/engineErrorHandler';
34
import { removeContentTags } from '../utils/removeContentTags';
45
import { AiEngine, AiEngineConfig } from './Engine';
56

@@ -39,9 +40,8 @@ export class FlowiseEngine implements AiEngine {
3940
const message = response.data;
4041
let content = message?.text;
4142
return removeContentTags(content, 'think');
42-
} catch (err: any) {
43-
const message = err.response?.data?.error ?? err.message;
44-
throw new Error('local model issues. details: ' + message);
43+
} catch (error) {
44+
throw normalizeEngineError(error, 'flowise', this.config.model);
4545
}
4646
}
4747
}

src/engine/gemini.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {
55
HarmCategory,
66
Part
77
} from '@google/generative-ai';
8-
import axios from 'axios';
98
import { OpenAI } from 'openai';
10-
import { ModelNotFoundError } from '../utils/errors';
9+
import { normalizeEngineError } from '../utils/engineErrorHandler';
1110
import { removeContentTags } from '../utils/removeContentTags';
1211
import { AiEngine, AiEngineConfig } from './Engine';
1312

@@ -76,30 +75,7 @@ export class GeminiEngine implements AiEngine {
7675
const content = result.response.text();
7776
return removeContentTags(content, 'think');
7877
} catch (error) {
79-
const err = error as Error;
80-
81-
// Check for model not found errors
82-
if (err.message?.toLowerCase().includes('model') &&
83-
(err.message?.toLowerCase().includes('not found') ||
84-
err.message?.toLowerCase().includes('does not exist') ||
85-
err.message?.toLowerCase().includes('invalid'))) {
86-
throw new ModelNotFoundError(this.config.model, 'gemini', 404);
87-
}
88-
89-
if (
90-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
91-
error.response?.status === 401
92-
) {
93-
const geminiError = error.response.data.error;
94-
if (geminiError) throw new Error(geminiError?.message);
95-
}
96-
97-
// Check axios 404 errors
98-
if (axios.isAxiosError(error) && error.response?.status === 404) {
99-
throw new ModelNotFoundError(this.config.model, 'gemini', 404);
100-
}
101-
102-
throw err;
78+
throw normalizeEngineError(error, 'gemini', this.config.model);
10379
}
10480
}
10581
}

src/engine/mistral.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import axios from 'axios';
21
import { OpenAI } from 'openai';
32
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
3+
import { normalizeEngineError } from '../utils/engineErrorHandler';
44
import { removeContentTags } from '../utils/removeContentTags';
55
import { tokenCount } from '../utils/tokenCount';
66
import { AiEngine, AiEngineConfig } from './Engine';
@@ -63,17 +63,7 @@ export class MistralAiEngine implements AiEngine {
6363
let content = message.content as string;
6464
return removeContentTags(content, 'think');
6565
} catch (error) {
66-
const err = error as Error;
67-
if (
68-
axios.isAxiosError<{ error?: { message: string } }>(error) &&
69-
error.response?.status === 401
70-
) {
71-
const mistralError = error.response.data.error;
72-
73-
if (mistralError) throw new Error(mistralError.message);
74-
}
75-
76-
throw err;
66+
throw normalizeEngineError(error, 'mistral', this.config.model);
7767
}
7868
};
7969
}

0 commit comments

Comments
 (0)