Spaces:
Paused
Paused
| 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 | |
| }; | |
| } | |
| } | |