// ============================================================================ // Step 00b: Research — Industry Research via @mintel/journaling (No LLM hallus) // Uses Serper API for real web search results about the industry/company. // ============================================================================ import type { ConceptState, StepResult } from "../types.js"; interface ResearchResult { companyContext: string[]; industryInsights: string[]; competitorInfo: string[]; } /** * Research the company and industry using real web search data. * Uses @mintel/journaling's ResearchAgent — results are grounded in real sources. * * NOTE: The journaling package can cause unhandled rejections that crash the process. * We wrap each call in an additional safety layer. */ export async function executeResearch( state: ConceptState, ): Promise> { const startTime = Date.now(); const companyName = state.siteProfile?.companyInfo?.name || ""; const websiteTopic = state.siteProfile?.services?.slice(0, 3).join(", ") || ""; const domain = state.siteProfile?.domain || ""; if (!companyName && !websiteTopic && !domain) { return { success: true, data: { companyContext: [], industryInsights: [], competitorInfo: [] }, usage: { step: "00b-research", model: "none", promptTokens: 0, completionTokens: 0, cost: 0, durationMs: 0 }, }; } // Safety wrapper: catch ANY unhandled rejections during this step const safeCall = (fn: () => Promise, fallback: T): Promise => { return new Promise((resolve) => { const handler = (err: any) => { console.warn(` ⚠️ Unhandled rejection caught in research: ${err?.message || err}`); process.removeListener("unhandledRejection", handler); resolve(fallback); }; process.on("unhandledRejection", handler); fn() .then((result) => { process.removeListener("unhandledRejection", handler); resolve(result); }) .catch((err) => { process.removeListener("unhandledRejection", handler); console.warn(` ⚠️ Research call failed: ${err?.message || err}`); resolve(fallback); }); }); }; try { const { ResearchAgent } = await import("@mintel/journaling"); const agent = new ResearchAgent(process.env.OPENROUTER_API_KEY || ""); const results: ResearchResult = { companyContext: [], industryInsights: [], competitorInfo: [], }; // 1. Research the company itself if (companyName || domain) { const searchQuery = companyName ? `${companyName} ${websiteTopic} Unternehmen` : `site:${domain}`; console.log(` 🔍 Researching: "${searchQuery}"...`); const facts = await safeCall( () => agent.researchTopic(searchQuery), [] as any[], ); results.companyContext = (facts || []) .filter((f: any) => f?.fact || f?.value || f?.text || f?.statement) .map((f: any) => f.fact || f.value || f.text || f.statement) .slice(0, 5); } // 2. Industry research if (websiteTopic) { console.log(` 🔍 Researching industry: "${websiteTopic}"...`); const insights = await safeCall( () => agent.researchCompetitors(websiteTopic), [] as any[], ); results.industryInsights = (insights || []).slice(0, 5); } const totalFacts = results.companyContext.length + results.industryInsights.length + results.competitorInfo.length; console.log(` 📊 Research found ${totalFacts} data points.`); return { success: true, data: results, usage: { step: "00b-research", model: "serper/datacommons", promptTokens: 0, completionTokens: 0, cost: 0, durationMs: Date.now() - startTime, }, }; } catch (err) { console.warn(` ⚠️ Research step skipped: ${(err as Error).message}`); return { success: true, data: { companyContext: [], industryInsights: [], competitorInfo: [] }, usage: { step: "00b-research", model: "none", promptTokens: 0, completionTokens: 0, cost: 0, durationMs: Date.now() - startTime }, }; } }