import { createLogger } from '@sim/logger' import { executeInE2B } from '@/lib/execution/e2b' import { CodeLanguage } from '@/lib/execution/languages' import { compileDoc, DocCompileUserError } from './doc-compile' const logger = createLogger('CopilotDocRecalc') const RECALC_TIMEOUT_MS = 150_000 const MAX_REPORTED_ERRORS = 50 export interface XlsxCellError { sheet: string cell: string error: string } export interface XlsxRecalcResult { ok: boolean errors: XlsxCellError[] } /** * Scans an .xlsx workbook for spilled error values (#REF!/#DIV/0!/etc.) — the * spreadsheet equivalent of the visual QA loop. * * Precondition: the binary must already be recalculated (cached values present). * compileDoc bakes a LibreOffice recalc into every xlsx artifact, so the input * here always has cached values — meaning we can read them with openpyxl * (data_only) directly and skip a second LibreOffice cold-start. Throws on * sandbox/infra failure. */ export async function recalcXlsx(args: { binary: Buffer workspaceId: string }): Promise { const script = ` import json, openpyxl ERR = {"#REF!", "#DIV/0!", "#VALUE!", "#NAME?", "#N/A", "#NULL!", "#NUM!"} # Input is already recalculated by compileDoc, so cached values are present. wb = openpyxl.load_workbook("/home/user/input.xlsx", data_only=True) errors = [] for ws in wb.worksheets: for row in ws.iter_rows(): for c in row: if isinstance(c.value, str) and c.value.strip() in ERR: errors.append({"sheet": ws.title, "cell": c.coordinate, "error": c.value.strip()}) print("__SIM_RESULT__=" + json.dumps({"ok": len(errors) == 0, "errors": errors[:${MAX_REPORTED_ERRORS}]})) `.trim() const result = await executeInE2B({ code: script, language: CodeLanguage.Python, timeoutMs: RECALC_TIMEOUT_MS, sandboxKind: 'doc', sandboxFiles: [ { path: '/home/user/input.xlsx', content: args.binary.toString('base64'), encoding: 'base64', }, ], }) if (result.error) { throw new Error(`Spreadsheet recalc failed: ${result.error}`) } const payload = result.result as XlsxRecalcResult | null if (!payload || typeof payload.ok !== 'boolean') { logger.warn('Recalc returned no structured result', { workspaceId: args.workspaceId }) return { ok: true, errors: [] } } return { ok: payload.ok, errors: Array.isArray(payload.errors) ? payload.errors : [] } } /** Single-line summary of the first few formula errors, for the compiled-check result. */ export function formatXlsxErrors(errors: XlsxCellError[]): string { return `${errors.length} formula error(s): ${errors .slice(0, 5) .map((e) => `${e.sheet}!${e.cell}=${e.error}`) .join(', ')}` } export interface CompiledCheckResult { ok: boolean error?: string errors?: XlsxCellError[] } /** * Compiles a generated doc (and, for xlsx, recalc-scans its formulas) to verify * it builds — the shared body behind the /compiled-check route and the VFS * compiled-check read. Returns { ok: false } only for a DocCompileUserError (the * agent's script is wrong); infra failures (E2B/S3) rethrow so callers surface a * 5xx instead of telling the agent to fix its script. */ export async function runE2BCompiledCheck(args: { source: string fileName: string workspaceId: string ext: string }): Promise { try { const compiled = await compileDoc({ source: args.source, fileName: args.fileName, workspaceId: args.workspaceId, }) if (args.ext === 'xlsx') { const recalc = await recalcXlsx({ binary: compiled.buffer, workspaceId: args.workspaceId }) return recalc.ok ? { ok: true } : { ok: false, error: formatXlsxErrors(recalc.errors), errors: recalc.errors } } return { ok: true } } catch (err) { if (err instanceof DocCompileUserError) return { ok: false, error: err.message } throw err } }