Skip to content

Commit 4ade3da

Browse files
feat: Kiro Gemini integration
1 parent 152e2ce commit 4ade3da

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

.kiro/specs/api-integrations/tasks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
- Implement OpenAI-specific error handling (rate limits, authentication, etc.)
1313
- _Requirements: 1.1, 2.1, 4.1_
1414

15-
- [ ] 3. Implement direct Gemini API integration
15+
- [x] 3. Implement direct Gemini API integration
1616
- Replace proxy API call with direct Google Gemini API call
1717
- Add proper request formatting for Gemini's API structure
1818
- Implement Gemini-specific error handling and response parsing

lib/ai-providers.ts

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,67 @@ export class GeminiProvider implements AIProvider {
195195
): Promise<string> {
196196
this.validateRequest(question, correctAnswers, apiKey);
197197

198+
const prompt = `Question: ${question}\n\nCorrect answers: ${correctAnswers.join(
199+
", ",
200+
)}\n\nPlease provide a clear and concise explanation of why these answers are correct. Focus on the key concepts and reasoning.`;
201+
198202
try {
199-
const response = await fetch("/api/ai/gemini", {
200-
method: "POST",
201-
headers: { "Content-Type": "application/json" },
202-
body: JSON.stringify({
203-
question,
204-
correctAnswers,
205-
apiKey,
206-
}),
207-
});
203+
const response = await fetch(
204+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${apiKey}`,
205+
{
206+
method: "POST",
207+
headers: {
208+
"Content-Type": "application/json",
209+
},
210+
body: JSON.stringify({
211+
contents: [
212+
{
213+
parts: [
214+
{
215+
text: prompt,
216+
},
217+
],
218+
},
219+
],
220+
generationConfig: {
221+
maxOutputTokens: 500,
222+
temperature: 0.7,
223+
},
224+
}),
225+
}
226+
);
208227

209228
if (!response.ok) {
210-
const errorType = response.status === 401 ? 'auth' :
211-
response.status === 429 ? 'rate_limit' : 'network';
212-
throw new AIProviderError(this.name, errorType, `Gemini API error: ${response.status}`);
229+
const errorData = await response.json().catch(() => ({}));
230+
const errorMessage = errorData.error?.message || `HTTP ${response.status}`;
231+
232+
// Gemini-specific error handling
233+
if (response.status === 400) {
234+
// Check for specific Gemini error types
235+
if (errorMessage.includes('API_KEY_INVALID') || errorMessage.includes('invalid API key')) {
236+
throw new AIProviderError(this.name, 'auth', `Invalid Gemini API key: ${errorMessage}`);
237+
} else {
238+
throw new AIProviderError(this.name, 'validation', `Invalid request: ${errorMessage}`);
239+
}
240+
} else if (response.status === 403) {
241+
throw new AIProviderError(this.name, 'auth', `Access denied: ${errorMessage}`);
242+
} else if (response.status === 429) {
243+
throw new AIProviderError(this.name, 'rate_limit', `Rate limit exceeded: ${errorMessage}`);
244+
} else if (response.status >= 500) {
245+
throw new AIProviderError(this.name, 'network', `Gemini server error: ${errorMessage}`);
246+
} else {
247+
throw new AIProviderError(this.name, 'network', `Gemini API error: ${errorMessage}`);
248+
}
213249
}
214250

215251
const data = await response.json();
216252

217-
if (!data.explanation) {
218-
throw new AIProviderError(this.name, 'validation', 'Invalid response from Gemini');
253+
// Parse Gemini response structure
254+
if (!data.candidates?.[0]?.content?.parts?.[0]?.text) {
255+
throw new AIProviderError(this.name, 'validation', 'Invalid response structure from Gemini');
219256
}
220257

221-
return data.explanation;
258+
return data.candidates[0].content.parts[0].text;
222259
} catch (error) {
223260
if (error instanceof AIProviderError) throw error;
224261
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -231,7 +268,8 @@ export class GeminiProvider implements AIProvider {
231268
}
232269

233270
validateConfig(apiKey?: string): boolean {
234-
return !!(apiKey && apiKey.length > 10); // Basic validation for Gemini keys
271+
// Gemini API keys are typically 39 characters long and start with "AIza"
272+
return !!(apiKey && apiKey.length > 30 && apiKey.startsWith('AIza'));
235273
}
236274

237275
private validateRequest(question: string, correctAnswers: string[], apiKey: string): void {
@@ -242,7 +280,7 @@ export class GeminiProvider implements AIProvider {
242280
throw new AIProviderError(this.name, 'validation', 'At least one correct answer is required');
243281
}
244282
if (!this.validateConfig(apiKey)) {
245-
throw new AIProviderError(this.name, 'auth', 'Invalid Gemini API key');
283+
throw new AIProviderError(this.name, 'auth', 'Invalid Gemini API key format');
246284
}
247285
}
248286
}

0 commit comments

Comments
 (0)