Skip to content

Commit e09c32e

Browse files
committed
fix: Fix gemini stream route
1 parent f73eb25 commit e09c32e

5 files changed

Lines changed: 28 additions & 23 deletions

File tree

src/controllers/geminiQuery.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,10 @@ export const handleGeminiStream = async (req: Request, res: Response) => {
212212
try {
213213
await generateText({
214214
model: model,
215-
onChunk: (chunk: LLMStreamChunk) => {
216-
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
215+
// The onChunk callback now receives a raw SSE line string from llmWrapper.ts
216+
onChunk: (rawSseLine: string) => {
217+
// Pass the raw SSE line, followed by a single newline, as per SSE spec.
218+
res.write(`${rawSseLine}\n`);
217219
},
218220
query: augmentedPrompt,
219221
stream: true,

src/routers/geminiQuery.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,21 @@ import { queryApiKeyAuth } from '../middleware/auth';
55

66
const geminiRouter = Router();
77

8-
// Route for batch processing
9-
geminiRouter.post('/gemini/models/:model:generateContent', queryApiKeyAuth, handleGeminiBatch);
8+
// gemini proxy endpoints
9+
geminiRouter.post('/gemini/models/:model', (req, res) => {
10+
const model = req.params.model;
1011

11-
// Route for streaming
12-
geminiRouter.post('/gemini/models/:model:streamGenerateContent', queryApiKeyAuth, handleGeminiStream);
12+
if (model.endsWith(':generateContent')) {
13+
req.params.model = model.replace(':generateContent', '');
14+
return handleGeminiBatch(req, res);
15+
}
16+
17+
if (model.endsWith(':streamGenerateContent')) {
18+
req.params.model = model.replace(':streamGenerateContent', '');
19+
return handleGeminiStream(req, res);
20+
}
21+
22+
return res.status(404).json({ error: 'Unsupported Gemini operation' });
23+
});
1324

1425
export { geminiRouter };

src/services/gemini.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const ensureModelPrefixed = (modelId: string): string => {
3131
const streamGemini = async (
3232
modelId: GeminiChatModel | string,
3333
contents: GeminiContent[],
34-
onChunk: (chunk: GeminiStreamChunk) => void
34+
onChunk: (rawSseLine: string) => void
3535
): Promise<void> => {
3636
if (!config.geminiApiKey) {
3737
throw new Error('Gemini API key is not configured.');
@@ -69,17 +69,8 @@ const streamGemini = async (
6969
buffer = lines.pop()!; // Keep the last partial line in buffer
7070

7171
for (const line of lines) {
72-
const trimmedLine = line.trim();
73-
if (trimmedLine.startsWith('data: ')) {
74-
const jsonData = trimmedLine.substring('data: '.length);
75-
try {
76-
const chunk = JSON.parse(jsonData) as GeminiStreamChunk;
77-
onChunk(chunk);
78-
} catch (error) {
79-
console.error('Failed to parse Gemini stream chunk:', error, jsonData);
80-
// Decide on error handling: re-throw, or pass error to onChunk, or ignore.
81-
}
82-
}
72+
// Pass through every line from the source SSE stream to the callback.
73+
onChunk(line);
8374
}
8475
}
8576
};

src/services/llmWrapper.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ export const generateText = async (
4242
if (!onChunk) {
4343
throw new Error('onChunk callback is required for streaming responses.');
4444
}
45-
await streamGeminiGenerate(geminiModelId as GeminiModelName, geminiContents, (chunk: GeminiStreamChunk) => {
46-
// LLMStreamChunk is now GeminiStreamChunk, so direct pass is fine.
47-
onChunk(chunk as LLMStreamChunk);
48-
});
45+
// gemini.ts's streamGenerateContent now provides raw SSE lines to its onChunk callback.
46+
// The onChunk from LLMChatRequestOptions (options.onChunk) will also expect raw SSE lines
47+
// (this type will be updated in a subsequent step in types.ts).
48+
// Therefore, we can pass options.onChunk directly.
49+
await streamGeminiGenerate(geminiModelId as GeminiModelName, geminiContents, onChunk);
4950
return;
5051
} else {
5152
const response = await batchGeminiGenerate(geminiModelId as GeminiModelName, geminiContents);

src/types/llmWrapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type LLMChatRequestOptions = {
77
query: string;
88
stream?: boolean;
99
model?: string; // Optional model override
10-
onChunk?: (chunk: Gemini.GeminiStreamChunk) => void; // For streaming, now only Gemini
10+
onChunk?: (rawSseLine: string) => void; // For streaming, expects a raw SSE line string
1111
// Add other common parameters like temperature, max_tokens if they are to be abstracted.
1212
};
1313

0 commit comments

Comments
 (0)