"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 }; }