Files
at-mintel/packages/concept-engine/src/steps/00b-research.ts
Marc Mintel 5da88356a8
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
feat: migrate npm registry from Verdaccio to Gitea Packages
2026-02-27 00:12:00 +01:00

122 lines
4.6 KiB
TypeScript

// ============================================================================
// 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<StepResult<ResearchResult>> {
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 = <T>(fn: () => Promise<T>, fallback: T): Promise<T> => {
return new Promise<T>((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 },
};
}
}