Files
simstudioai--sim/apps/sim/lib/copilot/tools/server/files/doc-recalc.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

123 lines
3.9 KiB
TypeScript

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<XlsxRecalcResult> {
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<CompiledCheckResult> {
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
}
}