|
|
|
|
|
|
|
import { fetchAndEncode } from './remoteimage'; |
|
import { z } from 'zod'; |
|
import { ToolRegistry } from '@google/gemini-cli-core/dist/src/tools/tool-registry.js'; |
|
|
|
|
|
type Part = { text?: string; inlineData?: { mimeType: string; data: string } }; |
|
|
|
|
|
async function callLocalFunction(_name: string, _args: unknown) { |
|
return { ok: true }; |
|
} |
|
|
|
|
|
|
|
|
|
export async function mapRequest(body: any) { |
|
const parts: Part[] = []; |
|
|
|
|
|
for (const m of body.messages) { |
|
if (Array.isArray(m.content)) { |
|
for (const item of m.content) { |
|
if (item.type === 'image_url') { |
|
parts.push({ inlineData: await fetchAndEncode(item.image_url.url) }); |
|
} else if (item.type === 'text') { |
|
parts.push({ text: item.text }); |
|
} |
|
} |
|
} else { |
|
parts.push({ text: m.content }); |
|
} |
|
} |
|
|
|
|
|
const generationConfig: Record<string, unknown> = { |
|
temperature: body.temperature, |
|
maxOutputTokens: body.max_tokens, |
|
topP: body.top_p, |
|
...(body.generationConfig ?? {}), |
|
}; |
|
if (body.include_reasoning === true) { |
|
generationConfig.enable_thoughts = true; |
|
generationConfig.thinking_budget ??= 2048; |
|
} |
|
|
|
|
|
if (body.include_reasoning === true && generationConfig.thinking !== true) { |
|
generationConfig.thinking = true; |
|
generationConfig.thinking_budget ??= 2048; |
|
} |
|
generationConfig.maxInputTokens ??= 1_000_000; |
|
|
|
const geminiReq = { |
|
contents: [{ role: 'user', parts }], |
|
generationConfig, |
|
stream: body.stream, |
|
}; |
|
|
|
|
|
const tools = new ToolRegistry({} as any); |
|
|
|
if (body.functions?.length) { |
|
const reg = tools as any; |
|
body.functions.forEach((fn: any) => |
|
reg.registerTool( |
|
fn.name, |
|
{ |
|
title: fn.name, |
|
description: fn.description ?? '', |
|
inputSchema: z.object(fn.parameters?.properties ?? {}), |
|
}, |
|
async (args: unknown) => callLocalFunction(fn.name, args), |
|
), |
|
); |
|
} |
|
|
|
return { geminiReq, tools }; |
|
} |
|
|
|
|
|
|
|
|
|
export function mapResponse(gResp: any) { |
|
const usage = gResp.usageMetadata ?? {}; |
|
return { |
|
id: `chatcmpl-${Date.now()}`, |
|
object: 'chat.completion', |
|
created: Math.floor(Date.now() / 1000), |
|
model: 'gemini-2.5-pro-latest', |
|
choices: [ |
|
{ |
|
index: 0, |
|
message: { role: 'assistant', content: gResp.text }, |
|
finish_reason: 'stop', |
|
}, |
|
], |
|
usage: { |
|
prompt_tokens: usage.promptTokens ?? 0, |
|
completion_tokens: usage.candidatesTokens ?? 0, |
|
total_tokens: usage.totalTokens ?? 0, |
|
}, |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function mapStreamChunk(chunk: any) { |
|
const part = chunk?.candidates?.[0]?.content?.parts?.[0] ?? {}; |
|
const delta: any = { role: 'assistant' }; |
|
|
|
if (part.thought === true) { |
|
delta.content = `<think>${part.text ?? ''}`; |
|
} else if (typeof part.text === 'string') { |
|
delta.content = part.text; |
|
} |
|
return { choices: [ { delta, index: 0 } ] }; |
|
} |
|
|
|
|
|
|