import { randomUUID } from "node:crypto";
import type { AssistantMessage, Message, Tool, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
import { normalizeContext } from "@earendil-works/pi-ai";
import { Type } from "typebox";
import { piEngineFor, type PiEngine, type ThinkingLevel } from "../models.ts";
import { providerEventObserver, type ProviderEventObserver } from "../provider-events.ts";
import type { VisualResult } from "../../core/contracts.ts";
import { getHeadless, type HeadlessApi } from "./headless.ts";
/**
* The visual-maker sub-agent (vision §2.5): authors ONE precise SVG from a
* brief, renders it to a PNG with the same rasterizer the transcript
* will use, LOOKS at the
* result, and iterates until correct — a direct port of the vault POC's
* svg-maker doctrine ("the one rule that matters: verify by looking").
*
* LOOP SHAPE (decided in the W6 brief): hand-rolled with generateText + a
* messages array, because the loop must feed an IMAGE back to the model after
* each render. write_svg / render_svg are declared WITHOUT execute — when the
* model calls them generateText stops with the tool calls, and this loop
* appends the tool results itself, followed by a user message containing the
* rendered PNG as a file part ("Here is the current render"). ToolLoopAgent's
* execute-tool plumbing can't express that image-in-the-loop shape cleanly.
*
* Doctrine enforcement in code: if the model tries to finish while the
* current SVG has never been rendered (or changed since its last render), the
* loop renders it anyway, shows the image, and asks the model to look before
* finishing. Nothing is delivered without having been looked at.
*
* Failure semantics mirror the demo-builder: { svg: "", notes: "FAILED: …" }.
*/
export interface VisualMakerInput {
brief: string;
}
export interface VisualMakerOptions {
/** Test seam: scripted engine; production uses the visualMaker role. */
engine?: PiEngine;
headless?: HeadlessApi;
/** Max model rounds (write/render cycles + final answer). Default 8. */
maxIterations?: number;
/** Live activity-line callback (updates the job record). */
onActivity?: (line: string) => void;
/** Kills the run mid-model-call (2026-09 real-kill abort semantics). */
abortSignal?: AbortSignal;
/** Money channel: one call per loop iteration (streamSimple), folded
* into JobRecord.usage. */
onUsage?: (usage: Usage) => void;
/** Debug: raw provider-event recording key (LH_PROVIDER_EVENTS=1). */
chatId?: string;
}
const INSTRUCTIONS = `You are a visual maker — a diagram author + renderer for precise, spatial figures. You
receive a brief describing ONE idea that needs exact placement (coordinate geometry, number lines,
vectors, function plots, physical layouts — things auto-layout diagrams cannot express), and you return
ONE clean, correct SVG. You never see the lesson; the brief is everything you know.
You do NOT decide what idea to show — the teacher already decided. Your job is faithful, precise
composition, and above everything CORRECTNESS: the picture must not assert anything false. A right-angle
mark on the wrong corner, a vector pointing the wrong way, a point plotted at the wrong coordinate is a
failure even if it renders cleanly.
Your three tools:
- write_svg({ svg }) — write the complete source (overwrites the previous version).
- edit_svg({ old_text, new_text }) — a surgical exact-match replacement in the current SVG:
old_text must appear EXACTLY ONCE; it is replaced by new_text. Call write_svg first. For anything
beyond a one-spot fix (new elements, restructured layout), write_svg the whole document again.
- render_svg({}) — rasterize the current SVG to a PNG. The render arrives as an image in the
message right after the tool result.
THE ONE RULE THAT MATTERS MOST: verify by looking. You are done only when you have LOOKED at the
rendered PNG and confirmed it is true to the brief. Rendering success only proves the SVG parsed; it
says nothing about whether the geometry is right or the picture is readable. When you look:
- Is every coordinate, angle, direction, and proportion actually correct? Re-derive the geometry if unsure.
- Are labels placed clearly, not overlapping lines or each other?
- Is anything clipped by the viewBox, too small to read, or cramped?
- Would a learner instantly read the intended idea from this picture alone?
Workflow: plan the coordinate space (viewBox + margins, nothing touching the edges) → write_svg →
render_svg → LOOK critically → fix (edit_svg for one-spot fixes, write_svg otherwise) and re-render
until correct and clean. Never finish without having looked at a render of your FINAL SVG.
SVG requirements:
- A complete