175 lines
6.6 KiB
JavaScript
175 lines
6.6 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.callGeminiChat = callGeminiChat;
|
|
exports.streamGeminiChat = streamGeminiChat;
|
|
const genai_1 = require("@google/genai");
|
|
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY || "";
|
|
const GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || "gemini-3.1-pro-preview";
|
|
// Add a clear visual log so we always know exactly which model is active in the terminal
|
|
console.log(`[GeminiChat Runner] Initialized — Model: ${GEMINI_MODEL}`);
|
|
if (!GEMINI_API_KEY) {
|
|
console.warn(`[GeminiChat] WARNING: GOOGLE_API_KEY is not set. Chat stream will fail with 403 Forbidden.`);
|
|
}
|
|
const ai = new genai_1.GoogleGenAI({ apiKey: GEMINI_API_KEY });
|
|
function toGeminiContents(messages) {
|
|
const contents = [];
|
|
for (const msg of messages) {
|
|
if (msg.role === "user") {
|
|
contents.push({ role: "user", parts: [{ text: msg.content }] });
|
|
}
|
|
else if (msg.role === "assistant") {
|
|
const parts = [];
|
|
if (msg.content)
|
|
parts.push({ text: msg.content });
|
|
if (msg.toolCalls?.length) {
|
|
for (const tc of msg.toolCalls) {
|
|
const part = {
|
|
functionCall: { name: tc.name, args: tc.args },
|
|
};
|
|
if (tc.thoughtSignature) {
|
|
part.thoughtSignature = tc.thoughtSignature;
|
|
}
|
|
parts.push(part);
|
|
}
|
|
}
|
|
if (parts.length)
|
|
contents.push({ role: "model", parts });
|
|
}
|
|
else if (msg.role === "tool") {
|
|
const part = {
|
|
functionResponse: {
|
|
name: msg.toolName || "unknown",
|
|
response: { name: msg.toolName || "unknown", content: msg.content },
|
|
},
|
|
};
|
|
const last = contents[contents.length - 1];
|
|
if (last?.role === "user") {
|
|
last.parts.push(part);
|
|
}
|
|
else {
|
|
contents.push({ role: "user", parts: [part] });
|
|
}
|
|
}
|
|
}
|
|
return contents;
|
|
}
|
|
function toGeminiFunctions(tools) {
|
|
if (!tools.length)
|
|
return undefined;
|
|
return [
|
|
{
|
|
functionDeclarations: tools.map((t) => ({
|
|
name: t.name,
|
|
description: t.description,
|
|
parameters: t.parameters,
|
|
})),
|
|
},
|
|
];
|
|
}
|
|
async function callGeminiChat(opts) {
|
|
try {
|
|
const config = {
|
|
temperature: opts.temperature ?? 0.7,
|
|
maxOutputTokens: 8192,
|
|
};
|
|
if (opts.systemPrompt) {
|
|
config.systemInstruction = opts.systemPrompt;
|
|
}
|
|
const fns = toGeminiFunctions(opts.tools ?? []);
|
|
if (fns)
|
|
config.tools = fns;
|
|
console.log("\n========================================================");
|
|
console.log("➡️ [GEMINI API REQUEST]");
|
|
console.log("========================================================");
|
|
console.log(`System Prompt: ${config.systemInstruction ? config.systemInstruction.slice(0, 1000) + "..." : "None"}`);
|
|
console.log("Contents Payload:", JSON.stringify(toGeminiContents(opts.messages), null, 2));
|
|
console.log("========================================================\n");
|
|
const response = await ai.models.generateContent({
|
|
model: GEMINI_MODEL,
|
|
contents: toGeminiContents(opts.messages),
|
|
config,
|
|
});
|
|
console.log("\n========================================================");
|
|
console.log("⬅️ [GEMINI API RESPONSE]");
|
|
console.log("========================================================");
|
|
console.log("Raw Candidates:", JSON.stringify(response.candidates, null, 2));
|
|
console.log("========================================================\n");
|
|
let text = "";
|
|
let thoughts = "";
|
|
const toolCalls = [];
|
|
const parts = response.candidates?.[0]?.content?.parts ?? [];
|
|
for (const part of parts) {
|
|
if (part.text) {
|
|
if (part.thought)
|
|
thoughts += part.text;
|
|
else
|
|
text += part.text;
|
|
}
|
|
if (part.functionCall) {
|
|
toolCalls.push({
|
|
id: `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
name: part.functionCall.name || "",
|
|
args: part.functionCall.args ?? {},
|
|
thoughtSignature: part.thoughtSignature,
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
text,
|
|
thoughts,
|
|
toolCalls,
|
|
finishReason: response.candidates?.[0]?.finishReason,
|
|
};
|
|
}
|
|
catch (error) {
|
|
return {
|
|
text: "",
|
|
thoughts: "",
|
|
toolCalls: [],
|
|
error: `GoogleGenAI error: ${error instanceof Error ? error.message : String(error)}`,
|
|
};
|
|
}
|
|
}
|
|
async function* streamGeminiChat(opts) {
|
|
try {
|
|
const config = {
|
|
temperature: opts.temperature ?? 0.7,
|
|
maxOutputTokens: 8192,
|
|
};
|
|
if (opts.systemPrompt) {
|
|
config.systemInstruction = opts.systemPrompt;
|
|
}
|
|
const fns = toGeminiFunctions(opts.tools ?? []);
|
|
if (fns)
|
|
config.tools = fns;
|
|
console.log("\n========================================================");
|
|
console.log("➡️ [GEMINI STREAM REQUEST]");
|
|
console.log("========================================================");
|
|
console.log(`System Prompt: ${config.systemInstruction ? config.systemInstruction.slice(0, 1000) + "..." : "None"}`);
|
|
console.log("Contents Payload:", JSON.stringify(toGeminiContents(opts.messages), null, 2));
|
|
console.log("========================================================\n");
|
|
const streamResult = await ai.models.generateContentStream({
|
|
model: GEMINI_MODEL,
|
|
contents: toGeminiContents(opts.messages),
|
|
config,
|
|
});
|
|
for await (const chunk of streamResult) {
|
|
const parts = chunk.candidates?.[0]?.content?.parts ?? [];
|
|
for (const part of parts) {
|
|
if (part.text) {
|
|
yield part.thought
|
|
? { type: "thinking", text: part.text }
|
|
: { type: "text", text: part.text };
|
|
}
|
|
}
|
|
}
|
|
yield { type: "done" };
|
|
}
|
|
catch (error) {
|
|
yield {
|
|
type: "error",
|
|
error: `GoogleGenAI error: ${error instanceof Error ? error.message : String(error)}`,
|
|
};
|
|
}
|
|
}
|