import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { promisify } from "node:util"; import { artifactUrl, createStepGlbPreview, readArtifact } from "@/lib/preview-artifacts"; import { enginePath } from "@/lib/paths"; import { ensureTask, sanitizeFilename, upsertManifest } from "@/lib/task-store"; const execFileAsync = promisify(execFile); export type CadGenerationResult = { handled: true; taskId: string; artifactPath: string; artifactUrl: string; previewPath: string; previewUrl: string; viewerAssetPath: string; viewerAssetUrl: string; sourcePath: string; summary: string; }; function ensureRelativeFilename(filename: string, extension: string, fallback: string) { const safeName = sanitizeFilename(filename || fallback); return safeName.toLowerCase().endsWith(extension) ? safeName : `${safeName}${extension}`; } function pythonExecutable() { return process.env.CAD_PYTHON || enginePath("text-to-cad", ".venv", "bin", "python"); } function pipelineScript() { return enginePath("designir-pipeline", "scripts", "designir_pipeline.py"); } function routerScript() { return enginePath("text-to-cad", "skills", "cad-router", "scripts", "route.py"); } async function runJson(command: string, args: string[], cwd: string) { const { stdout } = await execFileAsync(command, args, { cwd, timeout: 120_000, maxBuffer: 1024 * 1024 * 8, }); return JSON.parse(stdout) as Record; } export async function executeDesignIR({ prompt, summary, designir, designirFilename, stepFilename, selectedTaskId, assumptions = [], teacherTaskId, teacherPath, }: { prompt: string; summary: string; designir: Record; designirFilename?: string; stepFilename?: string; selectedTaskId?: string; assumptions?: string[]; teacherTaskId?: string; teacherPath?: string; }): Promise { const task = await ensureTask(selectedTaskId || undefined); const sourcePath = ensureRelativeFilename( designirFilename || "model.designir.json", ".designir.json", "model.designir.json", ); const artifactPath = ensureRelativeFilename(stepFilename || "model.step", ".step", "model.step"); const sourceAbsolutePath = path.join(task.dir, sourcePath); const artifactAbsolutePath = path.join(task.dir, artifactPath); await fs.writeFile(sourceAbsolutePath, `${JSON.stringify(designir, null, 2)}\n`); const route = await runJson( pythonExecutable(), [routerScript(), prompt], task.dir, ); const selectedBackend = String(route.selected_backend || "build123d"); const normalizedDesignIR = { ...designir, backend_hint: selectedBackend, }; await fs.writeFile(sourceAbsolutePath, `${JSON.stringify(normalizedDesignIR, null, 2)}\n`); const antiCheat = await runJson( pythonExecutable(), [pipelineScript(), "anti-cheat", sourceAbsolutePath], task.dir, ); if (antiCheat.valid !== true) { throw new Error(`DesignIR anti-cheat validation failed: ${JSON.stringify(antiCheat.violations || [])}`); } const rebuild = await runJson( pythonExecutable(), [ pipelineScript(), "isolated-rebuild", sourceAbsolutePath, "--output", artifactAbsolutePath, "--backend", selectedBackend, ], task.dir, ); if (rebuild.valid !== true) { throw new Error("Isolated DesignIR rebuild failed."); } const validation: string[] = [ "CAD Router selected the backend before compilation.", "DesignIR anti-cheat validation passed.", "STEP rebuilt in an isolated workspace without teacher STEP access.", ]; const artifacts: Array<{ path: string; role: string; kind?: string }> = [ { path: sourcePath, role: "editable_contract", kind: "designir-2.0" }, { path: artifactPath, role: "primary", kind: "step" }, ]; if (typeof rebuild.model_json === "string" && rebuild.model_json) { const simpleCadModelPath = path.basename(rebuild.model_json); artifacts.push({ path: simpleCadModelPath, role: "backend_editable_graph", kind: "simplecad-model-json", }); } if (teacherTaskId && teacherPath) { const { absolutePath: teacherAbsolutePath } = await readArtifact( teacherTaskId, teacherPath, ); const reportPath = "acceptance-report.json"; const reportAbsolutePath = path.join(task.dir, reportPath); const acceptance = await runJson( pythonExecutable(), [ pipelineScript(), "accept", "--teacher", teacherAbsolutePath, "--designir", sourceAbsolutePath, "--rebuilt", artifactAbsolutePath, "--report", reportAbsolutePath, ], task.dir, ); artifacts.push({ path: reportPath, role: "acceptance", kind: "json" }); validation.push(`Independent acceptance verdict: ${String(acceptance.verdict || "unknown")}.`); } const viewerAssetPath = await createStepGlbPreview({ taskId: task.taskId, stepPath: artifactPath, }); artifacts.push({ path: viewerAssetPath, role: "viewer", kind: "glb" }); const artifactVersion = Date.now().toString(36); await upsertManifest(task.taskId, { request: prompt, route: { selected_backend: selectedBackend, runner_skill: String(route.runner_skill || "cad"), project: String(route.project || "text-to-cad"), fallback_order: Array.isArray(route.fallback_order) ? route.fallback_order : [], workflow_profiles: Array.isArray(route.workflow_profiles) ? route.workflow_profiles : [], }, source: { path: sourcePath, format: "designir-2.0", backend: selectedBackend, source_editability: "editable_source", generated_by: "llm_designir_tool_call", }, artifacts, assumptions, validation, studio: { latestArtifact: artifactPath, latestPreview: artifactPath, latestViewerAsset: viewerAssetPath, latestVersion: artifactVersion, }, }); const stepUrl = artifactUrl(task.taskId, artifactPath, artifactVersion); return { handled: true, taskId: task.taskId, artifactPath, artifactUrl: stepUrl, previewPath: artifactPath, previewUrl: stepUrl, viewerAssetPath, viewerAssetUrl: artifactUrl(task.taskId, viewerAssetPath, artifactVersion), sourcePath, summary: summary || "CAD model generated from executable DesignIR 2.0.", }; } export function cadResultDirective(result: CadGenerationResult) { return `::cad-result{base64="${Buffer.from(JSON.stringify(result), "utf8").toString("base64")}"}`; }