118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.runAgent = runAgent;
|
|
const genai_1 = require("@google/genai");
|
|
const tools_1 = require("./tools");
|
|
const job_store_1 = require("./job-store");
|
|
const MAX_TURNS = 40; // safety cap — prevents infinite loops
|
|
/**
|
|
* Core Gemini agent loop.
|
|
*
|
|
* Sends the task to Gemini with the agent's system prompt and tools,
|
|
* then loops: execute tool calls → send results back → repeat until
|
|
* the model stops calling tools or MAX_TURNS is reached.
|
|
*/
|
|
async function runAgent(job, config, task, ctx) {
|
|
const apiKey = process.env.GOOGLE_API_KEY;
|
|
if (!apiKey) {
|
|
throw new Error('GOOGLE_API_KEY environment variable is not set');
|
|
}
|
|
const genai = new genai_1.GoogleGenAI({ apiKey });
|
|
// Build Gemini function declarations from our tool definitions
|
|
const functionDeclarations = config.tools.map(tool => ({
|
|
name: tool.name,
|
|
description: tool.description,
|
|
parameters: tool.parameters
|
|
}));
|
|
const tools = functionDeclarations.length > 0
|
|
? [{ functionDeclarations }]
|
|
: [];
|
|
const model = genai.models;
|
|
// Build conversation history
|
|
const history = [];
|
|
// Initial user message
|
|
let currentMessage = {
|
|
role: 'user',
|
|
parts: [{ text: task }]
|
|
};
|
|
let toolCallCount = 0;
|
|
let turn = 0;
|
|
let finalText = '';
|
|
(0, job_store_1.updateJob)(job.id, { status: 'running', progress: `Starting ${config.name} agent...` });
|
|
while (turn < MAX_TURNS) {
|
|
turn++;
|
|
// Add current message to history
|
|
history.push(currentMessage);
|
|
// Call Gemini
|
|
const response = await model.generateContent({
|
|
model: config.model || 'gemini-2.0-flash',
|
|
contents: history,
|
|
config: {
|
|
systemInstruction: config.systemPrompt,
|
|
tools: tools.length > 0 ? tools : undefined,
|
|
temperature: 0.2,
|
|
maxOutputTokens: 8192
|
|
}
|
|
});
|
|
const candidate = response.candidates?.[0];
|
|
if (!candidate) {
|
|
throw new Error('No response from Gemini');
|
|
}
|
|
// Add model response to history
|
|
const modelContent = {
|
|
role: 'model',
|
|
parts: candidate.content?.parts || []
|
|
};
|
|
history.push(modelContent);
|
|
// Extract function calls from the response
|
|
const functionCalls = candidate.content?.parts?.filter(p => p.functionCall) ?? [];
|
|
if (functionCalls.length === 0) {
|
|
// No tool calls — the agent is done
|
|
finalText = candidate.content?.parts
|
|
?.filter(p => p.text)
|
|
.map(p => p.text)
|
|
.join('') ?? '';
|
|
break;
|
|
}
|
|
// Execute all tool calls
|
|
const toolResultParts = [];
|
|
for (const part of functionCalls) {
|
|
const call = part.functionCall;
|
|
const callName = call.name ?? 'unknown';
|
|
const callArgs = (call.args ?? {});
|
|
toolCallCount++;
|
|
(0, job_store_1.updateJob)(job.id, {
|
|
progress: `Turn ${turn}: calling ${callName}...`,
|
|
toolCalls: [...(job.toolCalls || []), {
|
|
turn,
|
|
tool: callName,
|
|
args: callArgs,
|
|
timestamp: new Date().toISOString()
|
|
}]
|
|
});
|
|
let result;
|
|
try {
|
|
result = await (0, tools_1.executeTool)(callName, callArgs, ctx);
|
|
}
|
|
catch (err) {
|
|
result = { error: err instanceof Error ? err.message : String(err) };
|
|
}
|
|
toolResultParts.push({
|
|
functionResponse: {
|
|
name: callName,
|
|
response: { result }
|
|
}
|
|
});
|
|
}
|
|
// Next turn: send tool results back to the model
|
|
currentMessage = {
|
|
role: 'user',
|
|
parts: toolResultParts
|
|
};
|
|
}
|
|
if (turn >= MAX_TURNS && !finalText) {
|
|
finalText = `Agent reached the ${MAX_TURNS}-turn safety limit. Last tool call count: ${toolCallCount}.`;
|
|
}
|
|
return { finalText, toolCallCount, turns: turn };
|
|
}
|