import fetch from 'node-fetch'; /** * Service to wrap complex Jules interactions into simple API calls. */ export class WrapperService { constructor() {} // --- Helpers --- getApiKeyForAgent(agentId) { if (agentId === 'jules-2') return process.env.JULES_API_KEY_2 || process.env.JULES_API_KEY; if (agentId === 'jules-3') return process.env.JULES_API_KEY_3 || process.env.JULES_API_KEY; if (agentId === 'jules-4') return process.env.JULES_API_KEY_4 || process.env.JULES_API_KEY; return process.env.JULES_API_KEY; } getGithubTokenForAgent(agentId) { // Jules 2 & 4 -> Greene-ctrl (GITHUB_2_TOKEN) if (agentId === 'jules-2' || agentId === 'jules-4') return process.env.GITHUB_2_TOKEN; // Jules 1 & 3 -> JsonLord (GITHUB_API_KEY) return process.env.GITHUB_API_KEY || process.env.GITHUB_TOKEN; } getGithubProfileForAgent(agentId) { if (agentId === 'jules-2' || agentId === 'jules-4') return 'Greene-ctrl'; return 'JsonLord'; } async callJulesApi(method, path, apiKey, body = null) { const url = `https://jules.googleapis.com/v1alpha/${path}`; const headers = { 'Content-Type': 'application/json', 'X-Goog-Api-Key': apiKey }; const opts = { method, headers }; if (body) opts.body = JSON.stringify(body); const res = await fetch(url, opts); if (!res.ok) { const txt = await res.text(); throw new Error(`Jules API Error (${res.status}): ${txt}`); } return res.json(); } fillTemplate(content, parameters) { let filled = content; // Replace {{VAR}} or $VAR for (const [key, val] of Object.entries(parameters)) { const bracketRegex = new RegExp(`{{${key}}}|\\[\\[${key}\\]\\]`, 'g'); const dollarRegex = new RegExp(`\\$${key}\\b`, 'g'); // Word boundary for $VAR filled = filled.replace(bracketRegex, val).replace(dollarRegex, val); } return filled; } // --- Core Logic --- async listTemplates(agentId) { const token = this.getGithubTokenForAgent(agentId); const profile = this.getGithubProfileForAgent(agentId); // Hardcoded source for now based on memory const owner = 'JsonLord'; const repo = 'agent-notes'; const path = 'ChatTemplates'; const branch = 'main'; const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`; const headers = { 'Accept': 'application/vnd.github.v3+json' }; if (token) headers['Authorization'] = `token ${token}`; try { const res = await fetch(url, { headers }); if (!res.ok) throw new Error(`GitHub Error: ${res.status}`); const files = await res.json(); // Just return metadata, content needs individual fetch if not "custom" return files .filter(f => f.name.endsWith('.md') || f.name.endsWith('.txt')) .map(f => ({ id: f.name, name: f.name, download_url: f.download_url })); } catch (e) { console.error("List templates failed:", e); return []; } } async handleSessionRequest(agentId, templateId, sessionId, parameters) { const apiKey = this.getApiKeyForAgent(agentId); if (!apiKey) throw new Error(`No API Key found for agent ${agentId}`); let promptText = ''; // 1. Resolve Template Content if (templateId && templateId !== 'custom') { // Fetch template content // Reuse logic or simple fetch const templates = await this.listTemplates(agentId); const tmpl = templates.find(t => t.id === templateId); if (tmpl) { const res = await fetch(tmpl.download_url); const raw = await res.text(); // Strip JSON block const jsonMatch = raw.match(/^\s*(\{[\s\S]*?\})(\s*[\r\n]+|$)/); promptText = jsonMatch ? raw.substring(jsonMatch[0].length).trim() : raw; } else { throw new Error(`Template ${templateId} not found`); } } else { // Custom or direct prompt promptText = parameters.prompt || parameters.content || ''; } // 2. Fill Variables const finalPrompt = this.fillTemplate(promptText, parameters); // 3. Execute let currentSessionId = sessionId; let lastResponse = null; if (!currentSessionId) { // CREATE SESSION // Extract title or default const title = parameters.title || `Session ${new Date().toISOString()}`; const source = parameters.source || 'JsonLord/agent-notes'; // Default source? const payload = { title, prompt: finalPrompt, sourceContext: { source: source, githubRepoContext: { startingBranch: 'main' } } }; const session = await this.callJulesApi('POST', 'sessions', apiKey, payload); currentSessionId = session.name.split('/').pop(); // "sessions/XYZ" -> "XYZ" lastResponse = session; } else { // SEND MESSAGE const payload = { prompt: finalPrompt }; await this.callJulesApi('POST', `sessions/${currentSessionId}:sendMessage`, apiKey, payload); lastResponse = { status: 'Message Sent' }; } // 4. Auto-Approve logic (simplified) // If the user wants to auto-run, we might check recent activities for a plan // For now, return the ID so the user can call again. return { sessionId: currentSessionId, agentId, status: 'success', data: lastResponse }; } }