/** * Search-box.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/agent/src/run.ts * Description: Session runner — creates/executes a full research session end to end. */ import { sessions } from "@search-box/db"; import { ResearchState } from "@search-box/research"; import { BudgetsSchema, DEFAULT_BUDGETS, type Budgets } from "@search-box/shared"; import { runOrchestration } from "./orchestrator.js"; import { runSynthesis } from "./synthesis.js"; export async function createSession(question: string, budgets?: Partial): Promise { const resolved = BudgetsSchema.parse({ ...DEFAULT_BUDGETS, ...budgets }); const session = await sessions.create(question, resolved as unknown as Record); const state = new ResearchState(session.id); await state.emit({ type: "session.started", question }); return session.id; } /** * Executes a created session. A single tool failure never kills the session * (handled inside the loop); only unrecoverable errors mark it failed. */ export async function runSession(sessionId: string): Promise { const session = await sessions.get(sessionId); if (!session) throw new Error(`unknown session: ${sessionId}`); const budgets = BudgetsSchema.parse({ ...DEFAULT_BUDGETS, ...session.budgets }); const state = new ResearchState(sessionId); try { await state.setStatus("running"); const result = await runOrchestration(state, session.question, budgets); await state.setStatus("synthesizing"); const refreshed = await sessions.get(sessionId); const { answer } = await runSynthesis(state, session.question, refreshed?.objectives ?? []); await sessions.complete(sessionId, answer); await state.emit({ type: "session.completed", answer }); await state.emit({ type: "session.status", status: "completed" }); void result; } catch (err) { const message = err instanceof Error ? err.message : String(err); await sessions.fail(sessionId, message); await state.emit({ type: "session.failed", error: message }); await state.emit({ type: "session.status", status: "failed" }); throw err; } }