import { callGeminiProConfigurable } from './ai/providers.js';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
// STRICT COMPATIBILITY INTERFACE
export interface ResearchProgress {
currentDepth: number;
totalDepth: number;
currentBreadth: number;
totalBreadth: number;
totalQueries: number;
completedQueries: number;
currentQuery: string;
}
export interface ResearchOptions {
query: string;
depth?: number;
breadth?: number;
existingLearnings?: string[];
onProgress?: (progress: ResearchProgress) => void;
}
export interface ResearchResult {
learnings: string[];
visitedUrls: string[];
reportPath?: string;
content?: string; // Added to return full report
}
export async function research(options: ResearchOptions): Promise<ResearchResult> {
const { query, onProgress } = options;
const learnings: string[] = [];
// 1. Generate Outline
console.error('[Agent] Generating outline for: ' + query);
const outlinePrompt = `
You are a research architect.
Topic: "${query}".
Goal: Create a detailed Table of Contents for a comprehensive industry whitepaper.
Return ONLY a JSON list of section titles. Example:
["1. Executive Summary", "2. Market Analysis", ...]
Limit to 8-12 high-impact sections.
`;
const outlineRaw = await callGeminiProConfigurable(outlinePrompt, { json: true });
let sections: string[] = [];
try {
const cleanJson = outlineRaw.replace(/```json/g, '').replace(/```/g, '').trim();
sections = JSON.parse(cleanJson);
} catch (e) {
console.error("[Agent] Failed to parse outline JSON. Using fallback.");
sections = [
"1. Executive Summary",
"2. Key Technologies & Innovations",
"3. Market Analysis & Adoption Trends",
"4. Vendor Landscape & Comparisons",
"5. ROI & Business Impact",
"6. Future Outlook"
];
}
let fullReport = '# ' + query + '\n\n*Generated by Gemini Agentic Researcher*\n\n';
const totalSections = sections.length;
for (let i = 0; i < totalSections; i++) {
const section = sections[i];
if (!section) continue;
if (onProgress) {
onProgress({
currentDepth: totalSections - i,
totalDepth: totalSections,
currentBreadth: 1,
totalBreadth: 1,
totalQueries: totalSections,
completedQueries: i,
currentQuery: 'Researching: ' + section
});
}
const sectionPrompt = `
ACT AS: Senior Industry Analyst.
TASK: Write a comprehensive, detailed chapter for the section: "${section}".
CONTEXT: This is part of a larger report on "${query}".
REQUIREMENTS:
- Use Google Search to find real-world data, vendor names, and statistics from 2025-2026.
- Write at least 1500 words for this section. Be extremely verbose and detailed.
- Include specific adoption rates, costs, and ROI metrics if applicable.
- Name specific companies and products.
- FORMAT: Markdown. Use tables where appropriate.
- CRITICAL: DO NOT include the chapter title "${section}" as a header at the start. Start directly with the text of the chapter.
`;
const content = await callGeminiProConfigurable(sectionPrompt, { tools: [{ googleSearch: {} }] });
// Strip duplicate headers if the LLM ignored instructions
let cleanContent = content.trim();
// Handle cases where LLM includes/excludes numbering or uses different header styles
const titleWithoutNumber = section.replace(/^\d+[\.\)]\s*/, '');
const sectionTitleRegex = new RegExp(`^#*\\s*(\\d+[\\.\\)]\\s*)?${escapeRegExp(titleWithoutNumber)}[:.]?\\s*`, 'i');
cleanContent = cleanContent.replace(sectionTitleRegex, '').trim();
fullReport += '\n\n## ' + section + '\n\n' + cleanContent + '\n\n---\n';
learnings.push('Completed section: ' + section);
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `agentic_report_${timestamp}.md`;
const fullPath = path.resolve(process.cwd(), filename);
await fs.writeFile(fullPath, fullReport, 'utf-8');
console.error('[Agent] Report saved to ' + fullPath);
return {
learnings,
visitedUrls: [],
reportPath: fullPath,
content: fullReport // Return the full report here
};
}
// Update writeFinalReport to actually return the content if we have it
export async function writeFinalReport(params: any): Promise<string> {
// If we were called with learnings that look like our full report content, return it
if (params.learnings && params.learnings.length > 0 && params.learnings[0].startsWith('#')) {
return params.learnings[0];
}
return "Report generation is handled inside the research loop in this engine.";
}
export function validateAcademicInput(input: string): boolean { return true; }
export function validateAcademicOutput(text: string): boolean { return true; }
export async function conductResearch(query: string, depth: number = 3): Promise<any> { return {}; }
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}