Skip to content

Commit 462c7cd

Browse files
committed
fix(services): improve API error handling
- Added retry logic for API key issues. - Improved error messages for better UX. - Handle rate limits and server errors. - Added connection error handling. - Improved configuration error handling.
1 parent 07c17f4 commit 462c7cd

File tree

3 files changed

+111
-53
lines changed

3 files changed

+111
-53
lines changed

src/services/codestralService.ts

+49-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios';
22
import { Logger } from '../utils/logger';
33
import { CommitMessage, ProgressReporter } from '../models/types';
44
import { ConfigService } from '../utils/configService';
5+
import { ConfigurationError } from '../models/errors';
56

67
interface CodestralResponse {
78
choices: Array<{
@@ -29,23 +30,23 @@ export class CodestralService {
2930
progress: ProgressReporter,
3031
attempt: number = 1
3132
): Promise<CommitMessage> {
32-
const apiKey = await ConfigService.getCodestralApiKey();
33-
const model = ConfigService.getCodestralModel();
34-
35-
const requestConfig = {
36-
headers: {
37-
'content-type': 'application/json',
38-
'Authorization': `Bearer ${apiKey}`
39-
},
40-
timeout: 30000
41-
};
33+
try {
34+
const apiKey = await ConfigService.getCodestralApiKey();
35+
const model = ConfigService.getCodestralModel();
36+
37+
const requestConfig = {
38+
headers: {
39+
'content-type': 'application/json',
40+
'Authorization': `Bearer ${apiKey}`
41+
},
42+
timeout: 30000
43+
};
4244

43-
const payload = {
44-
model: model,
45-
messages: [{ role: "user", content: prompt }]
46-
};
45+
const payload = {
46+
model: model,
47+
messages: [{ role: "user", content: prompt }]
48+
};
4749

48-
try {
4950
void Logger.log(`Attempt ${attempt}: Sending request to Codestral API`);
5051
await this.updateProgressForAttempt(progress, attempt);
5152

@@ -58,7 +59,39 @@ export class CodestralService {
5859
void Logger.log(`Commit message generated using ${model} model`);
5960
return { message: commitMessage, model };
6061
} catch (error) {
61-
return await this.handleGenerationError(error as ErrorWithResponse, prompt, progress, attempt);
62+
const axiosError = error as ErrorWithResponse;
63+
if (axiosError.response) {
64+
const { status } = axiosError.response;
65+
const responseData = JSON.stringify(axiosError.response.data);
66+
67+
switch (status) {
68+
case 401:
69+
if (attempt === 1) {
70+
await ConfigService.removeCodestralApiKey();
71+
await ConfigService.promptForCodestralApiKey();
72+
return this.generateCommitMessage(prompt, progress, attempt + 1);
73+
}
74+
throw new Error('Invalid API key. Please check your Codestral API key.');
75+
case 429:
76+
throw new Error('Rate limit exceeded. Please try again later.');
77+
case 500:
78+
throw new Error('Server error. Please try again later.');
79+
default:
80+
throw new Error(`API returned status ${status}. ${responseData}`);
81+
}
82+
}
83+
84+
if (axiosError.message.includes('ECONNREFUSED') || axiosError.message.includes('ETIMEDOUT')) {
85+
throw new Error('Could not connect to Codestral API. Please check your internet connection.');
86+
}
87+
88+
// Если ключ не установлен и это первая попытка
89+
if (error instanceof ConfigurationError && attempt === 1) {
90+
await ConfigService.promptForCodestralApiKey();
91+
return this.generateCommitMessage(prompt, progress, attempt + 1);
92+
}
93+
94+
throw error;
6295
}
6396
}
6497

src/services/geminiService.ts

+33-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Logger } from '../utils/logger';
33
import { ConfigService } from '../utils/configService';
44
import { ProgressReporter, CommitMessage } from '../models/types';
55
import { errorMessages } from '../utils/constants';
6+
import { ConfigurationError } from '../models/errors';
67

78
interface GeminiResponse {
89
candidates: Array<{
@@ -18,29 +19,30 @@ export class GeminiService {
1819
static async generateCommitMessage(
1920
prompt: string,
2021
progress: ProgressReporter,
22+
attempt: number = 1
2123
): Promise<CommitMessage> {
22-
const apiKey = await ConfigService.getApiKey();
23-
const model = ConfigService.getGeminiModel();
24-
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
24+
try {
25+
const apiKey = await ConfigService.getApiKey();
26+
const model = ConfigService.getGeminiModel();
27+
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
2528

26-
const requestConfig = {
27-
headers: {
28-
'content-type': 'application/json'
29-
},
30-
timeout: 30000
31-
};
29+
const requestConfig = {
30+
headers: {
31+
'content-type': 'application/json'
32+
},
33+
timeout: 30000
34+
};
3235

33-
const payload = {
34-
contents: [{ parts: [{ text: prompt }] }],
35-
generationConfig: {
36-
temperature: 0.7,
37-
topK: 40,
38-
topP: 0.95,
39-
maxOutputTokens: 1024
40-
}
41-
};
36+
const payload = {
37+
contents: [{ parts: [{ text: prompt }] }],
38+
generationConfig: {
39+
temperature: 0.7,
40+
topK: 40,
41+
topP: 0.95,
42+
maxOutputTokens: 1024
43+
}
44+
};
4245

43-
try {
4446
progress.report({ message: "Generating commit message...", increment: 50 });
4547

4648
const response = await axios.post<GeminiResponse>(apiUrl, payload, requestConfig);
@@ -57,6 +59,12 @@ export class GeminiService {
5759

5860
switch (status) {
5961
case 401:
62+
if (attempt === 1) {
63+
// Если это первая попытка и ключ неверный, запросим новый ключ и попробуем снова
64+
await ConfigService.removeApiKey();
65+
await ConfigService.promptForApiKey();
66+
return this.generateCommitMessage(prompt, progress, attempt + 1);
67+
}
6068
throw new Error(errorMessages.authenticationError);
6169
case 402:
6270
throw new Error(errorMessages.paymentRequired);
@@ -81,6 +89,12 @@ export class GeminiService {
8189
);
8290
}
8391

92+
// Если ключ не установлен и это первая попытка
93+
if (error instanceof ConfigurationError && attempt === 1) {
94+
await ConfigService.promptForApiKey();
95+
return this.generateCommitMessage(prompt, progress, attempt + 1);
96+
}
97+
8498
throw new Error(
8599
errorMessages.networkError.replace('{0}', axiosError.message)
86100
);

src/services/openaiService.ts

+29-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Logger } from '../utils/logger';
33
import { ConfigService } from '../utils/configService';
44
import { ProgressReporter, CommitMessage } from '../models/types';
55
import { errorMessages } from '../utils/constants';
6-
import { OpenAIError } from '../models/errors';
6+
import { OpenAIError, ConfigurationError } from '../models/errors';
77

88
interface OpenAIResponse {
99
choices: Array<{
@@ -28,25 +28,26 @@ export class OpenAIService {
2828

2929
static async generateCommitMessage(
3030
prompt: string,
31-
progress: ProgressReporter
31+
progress: ProgressReporter,
32+
attempt: number = 1
3233
): Promise<CommitMessage> {
33-
const apiKey = await ConfigService.getOpenAIApiKey();
34-
const model = ConfigService.getOpenAIModel();
35-
const baseUrl = ConfigService.getOpenAIBaseUrl();
36-
37-
const payload = {
38-
model,
39-
messages: [{ role: "user", content: prompt }],
40-
temperature: 0.7,
41-
maxTokens: 1024
42-
};
34+
try {
35+
const apiKey = await ConfigService.getOpenAIApiKey();
36+
const model = ConfigService.getOpenAIModel();
37+
const baseUrl = ConfigService.getOpenAIBaseUrl();
38+
39+
const payload = {
40+
model,
41+
messages: [{ role: "user", content: prompt }],
42+
temperature: 0.7,
43+
maxTokens: 1024
44+
};
4345

44-
const headers = {
45-
'Authorization': `Bearer ${apiKey}`,
46-
'content-type': 'application/json'
47-
};
46+
const headers = {
47+
'Authorization': `Bearer ${apiKey}`,
48+
'content-type': 'application/json'
49+
};
4850

49-
try {
5051
progress.report({ message: "Generating commit message...", increment: 50 });
5152

5253
const response = await axios.post<OpenAIResponse>(
@@ -66,9 +67,13 @@ export class OpenAIService {
6667
const status = axiosError.response.status;
6768
const data = axiosError.response.data as { error?: { message?: string } };
6869

69-
// Handle specific OpenAI error cases
7070
switch (status) {
7171
case 401:
72+
if (attempt === 1) {
73+
await ConfigService.removeOpenAIApiKey();
74+
await ConfigService.promptForOpenAIApiKey();
75+
return this.generateCommitMessage(prompt, progress, attempt + 1);
76+
}
7277
throw new OpenAIError(errorMessages.authenticationError);
7378
case 402:
7479
throw new OpenAIError(errorMessages.paymentRequired);
@@ -93,6 +98,12 @@ export class OpenAIService {
9398
);
9499
}
95100

101+
// Если ключ не установлен и это первая попытка
102+
if (error instanceof ConfigurationError && attempt === 1) {
103+
await ConfigService.promptForOpenAIApiKey();
104+
return this.generateCommitMessage(prompt, progress, attempt + 1);
105+
}
106+
96107
throw new OpenAIError(
97108
errorMessages.networkError.replace('{0}', axiosError.message)
98109
);

0 commit comments

Comments
 (0)