@ggui-ai/ui-gen 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{adapter-Cg_rkpGq.d.ts → adapter-BJwn74rx.d.ts} +49 -1
- package/dist/adapters/generation-dispatch.d.ts +8 -5
- package/dist/adapters/generation-dispatch.js +1118 -193
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +27 -6
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +10 -9
- package/dist/advanced/index.js +1126 -202
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +416 -92
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +2 -2
- package/dist/boilerplate.js +10 -3
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +9 -2
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-DFnnArrv.d.ts → create-ui-generator-N4gRXpcL.d.ts} +3 -4
- package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
- package/dist/{dispatch-CStl9eAw.d.ts → dispatch-BUoXRj_r.d.ts} +2 -2
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +4 -3
- package/dist/evaluation/index.d.ts +107 -539
- package/dist/evaluation/index.js +552 -55
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +17 -0
- package/dist/evaluation/types.js.map +1 -1
- package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +5 -5
- package/dist/harness/check/runtime-render/index.js +416 -92
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +393 -88
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +4 -5
- package/dist/harness/index.js +36 -5
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +10 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +17 -3
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +14 -6
- package/dist/harness/runtime.js +11 -4
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +4 -2
- package/dist/harness/types-public.js +10 -3
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +4 -3
- package/dist/index.d.ts +46 -11
- package/dist/index.js +1128 -200
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -3
- package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
- package/dist/tools/render-check-worker.js +8 -1
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-BJTLpzWn.d.ts → types-public-B7wn4ce5.d.ts} +384 -3
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/visual-evaluator-C9L-o6ap.d.ts +639 -0
- package/dist/workflows.d.ts +3 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +2 -0
- package/src/boilerplate/free-design-prompt.ts +3 -3
- package/src/boilerplate/hard-sections.ts +1 -1
- package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +3 -1
- package/src/check/run-tier0.ts +6 -3
- package/src/contract-context.ts +1 -1
- package/src/evaluation/cost-tracker.ts +54 -6
- package/src/evaluation/criteria/edge-probe.ts +286 -0
- package/src/evaluation/criteria/edge.ts +202 -0
- package/src/evaluation/criteria/resolve.ts +55 -7
- package/src/evaluation/index.ts +5 -1
- package/src/evaluation/judge-spend.ts +48 -0
- package/src/evaluation/types-public.ts +91 -0
- package/src/evaluation/types.ts +10 -0
- package/src/evaluation/visual-evaluator.ts +236 -40
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/probe-pending.ts +111 -0
- package/src/harness/check/runtime-render/render-check-host.ts +10 -6
- package/src/harness/check/runtime-render/render-check-worker.ts +6 -3
- package/src/harness/check/runtime-render/render-check.ts +417 -133
- package/src/harness/check/runtime-render/walk.ts +200 -0
- package/src/harness/coding/assemble-result.ts +2 -0
- package/src/harness/coding/generate-task-runner.ts +27 -0
- package/src/harness/coding/init-session.ts +5 -3
- package/src/harness/coding/run-coding-turn.ts +5 -1
- package/src/harness/coding/run-eval-round.ts +63 -3
- package/src/harness/in-loop-evaluation.ts +39 -0
- package/src/harness/llm-router.ts +58 -1
- package/src/harness/result-types.ts +9 -0
- package/src/harness/runtime.ts +9 -0
- package/src/harness/types-public.ts +10 -1
- package/src/index.ts +5 -0
- package/src/internal/verdict-line.ts +33 -0
- package/src/taught-tokens.ts +32 -0
- package/src/tools/render-check-worker.ts +2 -1
- package/src/tools/render-check.ts +8 -7
- package/src/validation/primitives.ts +1 -1
- package/dist/llm-router-nfO7wPec.d.ts +0 -267
- package/dist/{bank-CT4eEkta.d.ts → bank-Ctdp3FUA.d.ts} +3 -3
package/dist/patch.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { PatchFn } from './harness/types-public.js';
|
|
|
2
2
|
import '@ggui-ai/protocol';
|
|
3
3
|
import './styling-profile-CxfvV48g.js';
|
|
4
4
|
import './axes-CKVCzR_x.js';
|
|
5
|
-
import './types-public-
|
|
5
|
+
import './types-public-B7wn4ce5.js';
|
|
6
|
+
import './llm.js';
|
|
6
7
|
import '@ggui-ai/mcp-server-core';
|
|
7
|
-
import './design-mode-
|
|
8
|
+
import './design-mode-DGWN_Gkm.js';
|
|
8
9
|
import './types-Dh1s4K9j.js';
|
|
9
|
-
import './llm.js';
|
|
10
10
|
import './policy.js';
|
|
11
11
|
|
|
12
12
|
/** Core pure logic — exported separately for tests + alternative wrappers. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RenderingContext as RenderingContext$1, DataContract, GadgetDescriptor, BlueprintVariance } from '@ggui-ai/protocol';
|
|
2
|
-
import { C as CanvasClass } from './design-mode-
|
|
2
|
+
import { C as CanvasClass } from './design-mode-DGWN_Gkm.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Rendering context — how and where the component will be displayed.
|
|
@@ -36,6 +36,13 @@ function hostGlobals() {
|
|
|
36
36
|
return openRecord(globalThis);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// src/internal/verdict-line.ts
|
|
40
|
+
var VERDICT_PREFIX = "@@ggui-render-check-verdict@@";
|
|
41
|
+
function formatVerdictLine(value) {
|
|
42
|
+
return `${VERDICT_PREFIX} ${JSON.stringify(value)}
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
// src/tools/render-check-worker.ts
|
|
40
47
|
async function workerMain(raw) {
|
|
41
48
|
try {
|
|
@@ -165,7 +172,7 @@ async function renderOnce(input) {
|
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
174
|
function emit(output) {
|
|
168
|
-
process.stdout.write(
|
|
175
|
+
process.stdout.write(formatVerdictLine(output));
|
|
169
176
|
}
|
|
170
177
|
async function readAllStdin() {
|
|
171
178
|
let buffer = "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/internal/evaluate-component-source.ts","../../src/internal/open-record.ts","../../src/tools/render-check-worker.ts"],"names":["esbuild","resolvePath"],"mappings":"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","file":"render-check-worker.js","sourcesContent":["// packages/ui-gen/src/internal/evaluate-component-source.ts\n//\n// The ONE compile-and-evaluate helper for component source: esbuild\n// TSX→CJS transform + vm.Script in-context execution behind a\n// caller-supplied `require`. Consumed by:\n//\n// - `tools/render-check-worker.ts` — the subprocess render smoke\n// test (host isolation lives at the spawn layer, in the parent's\n// `tryRender` via `@ggui-ai/sandbox`).\n// - `harness/check/runtime-render/load-component.ts` — the in-loop\n// probe (single-realm React; caller injects module resolutions).\n//\n// The module-resolution POLICY is deliberately not shared — each\n// caller owns its own `require` shim (allowed specifiers, fallbacks,\n// error wording). What is shared is the mechanical eval pipeline:\n//\n// 1. esbuild.transform(source) → CJS\n// 2. wrap in `(function(require, exports, module) { ... })`\n// 3. vm.Script(...).runInThisContext()\n// 4. call with the caller's require + a fresh module object\n// 5. return `module.exports` read AFTER execution — esbuild's CJS\n// output REPLACES `module.exports` (`module.exports =\n// __toCommonJS(...)`), so the initial reference can't be trusted.\n\nexport async function evaluateComponentSource(\n sourceCode: string,\n sandboxRequire: (id: string) => unknown,\n): Promise<Record<string, unknown>> {\n const esbuild = await import(\"esbuild\");\n const { Script } = await import(\"node:vm\");\n\n const cjsResult = await esbuild.transform(sourceCode, {\n loader: \"tsx\",\n target: \"es2020\",\n format: \"cjs\",\n jsx: \"automatic\",\n jsxImportSource: \"react\",\n sourcefile: \"Component.tsx\",\n });\n\n const sandboxModule: { exports: Record<string, unknown> } = { exports: {} };\n\n const wrappedCode = `(function(require, exports, module) {\\n${cjsResult.code}\\n})`;\n const script = new Script(wrappedCode, { filename: \"Component.cjs\" });\n const fn = script.runInThisContext() as (\n require: (id: string) => unknown,\n exports: Record<string, unknown>,\n module: { exports: Record<string, unknown> },\n ) => void;\n\n fn(sandboxRequire, sandboxModule.exports, sandboxModule);\n\n return sandboxModule.exports;\n}\n","// packages/ui-gen/src/internal/open-record.ts\n//\n// Single documented narrowing seam for \"object used as an open\n// string-keyed property bag\" — the host global object and happy-dom's\n// `Window` both genuinely ARE open records at runtime (we install /\n// restore named globals like `window` / `document` on them by key),\n// but TypeScript cannot express that without polluting the global type\n// space via declaration merging. Centralizing the one structural\n// widening here keeps every call site cast-free.\n\n/**\n * View an object as an open string-keyed record.\n *\n * Single structural widening (`Record<string, unknown>` is assignable\n * to `object`, so the cast is checked in one direction by tsc — no\n * `unknown` erasure). Returns the SAME reference: property writes land\n * on the live object.\n */\nexport function openRecord(o: object): Record<string, unknown> {\n return o as Record<string, unknown>;\n}\n\n/**\n * The host global object viewed as an open property bag.\n *\n * Used by the render-check pipeline (and its subprocess worker) to\n * install and restore DOM globals by name.\n */\nexport function hostGlobals(): Record<string, unknown> {\n return openRecord(globalThis);\n}\n","/**\n * Child worker for `tryRender`. Runs the render smoke test in a\n * subprocess so the LLM-generated TSX executes outside the parent\n * Node process.\n *\n * Wire protocol (newline-delimited JSON):\n *\n * stdin: { sourceCode: string, sampleProps?: JsonObject }\n * stdout: { ok: true } | { ok: false, error: string }\n * exit: 0 on either outcome (errors are data, not status).\n *\n * Why a separate worker instead of `vm.Script.runInThisContext` in\n * the parent:\n *\n * - `runInThisContext` runs in the parent's V8 context. A\n * pathological component that attaches to a global, recurses\n * into the event loop, or allocates a large buffer poisons the\n * parent.\n * - A subprocess gives us real kill semantics on runaway code +\n * output caps + env isolation — delegated to\n * `@ggui-ai/sandbox`.\n *\n * The worker still uses `vm.Script` to run compiled CJS because the\n * component needs a tailored `require` (react / jsx-runtime /\n * @ggui-ai/design only). Host-level process isolation is the\n * sandbox; module-scoped `require` is the resolution policy. The\n * mechanical compile+vm pipeline is the shared\n * `evaluateComponentSource` helper (also used by the in-loop probe's\n * `loadComponent`); only the resolution policy lives here.\n *\n * Shutdown (ggui#1380): the compile runs on esbuild's service, a child\n * process esbuild keeps alive for the life of this worker. After the\n * verdict is written the worker calls `esbuild.stop()` so that child is\n * gone before the worker is. `workerMain` is exported for the\n * process-shape pins; `main()` runs only when this file is the process\n * entry, so importing it never reads stdin.\n */\nimport { createRequire } from 'node:module';\nimport { realpathSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as esbuild from 'esbuild';\nimport type { JsonObject } from '@ggui-ai/protocol';\nimport { evaluateComponentSource } from '../internal/evaluate-component-source.js';\nimport { hostGlobals } from '../internal/open-record.js';\n\ninterface WorkerInput {\n readonly sourceCode: string;\n readonly sampleProps?: JsonObject;\n}\n\ntype WorkerOutput =\n | { readonly ok: true }\n | { readonly ok: false; readonly error: string };\n\n/**\n * Parse one input document, render once, write the verdict. Whatever\n * happens, esbuild's service is stopped before this returns (or\n * rejects). If the service was never started, `stop()` is a no-op.\n */\nexport async function workerMain(raw: string): Promise<void> {\n try {\n let input: WorkerInput;\n try {\n input = JSON.parse(raw) as WorkerInput;\n } catch (err) {\n emit({\n ok: false,\n error: `worker: malformed input JSON — ${\n err instanceof Error ? err.message : String(err)\n }`,\n });\n return;\n }\n\n if (typeof input.sourceCode !== 'string' || input.sourceCode.length === 0) {\n emit({ ok: false, error: 'worker: sourceCode is required' });\n return;\n }\n\n const result = await renderOnce(input);\n emit(result);\n } finally {\n // Post-verdict work must never turn a written verdict into a failure:\n // a rejecting `stop()` would otherwise become an unhandled rejection,\n // exit code 1, and the host discarding the verdict on stdout.\n try {\n await esbuild.stop();\n } catch (err) {\n process.stderr.write(\n `render-check worker: esbuild.stop() failed — ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n}\n\nasync function main(): Promise<void> {\n await workerMain(await readAllStdin());\n}\n\nasync function renderOnce(input: WorkerInput): Promise<WorkerOutput> {\n // Minimal window/document shims — mirror the in-process impl so\n // components that touch either global don't crash on import. The\n // shims only need to exist for the render; they're torn down by\n // process exit.\n const g = hostGlobals();\n if (!('window' in globalThis)) {\n g.window = {\n addEventListener: () => {},\n removeEventListener: () => {},\n };\n }\n if (!('document' in globalThis)) {\n g.document = {\n getElementById: () => null,\n createElement: () => ({}),\n addEventListener: () => {},\n };\n }\n\n try {\n const React = await import('react');\n const ReactDOMServer = await import('react-dom/server');\n\n const require_ = createRequire(import.meta.url);\n\n const sandboxRequire = (id: string): unknown => {\n if (id === 'react/jsx-runtime' || id === 'react/jsx-dev-runtime') {\n return require_('react/jsx-runtime');\n }\n if (id === 'react') return require_('react');\n if (id.startsWith('@ggui-ai/design')) {\n try {\n return require_(id);\n } catch {\n // Fallback proxy — used in environments where the real\n // design package isn't resolvable. Each primitive becomes\n // a pass-through `div` so the render still exercises the\n // component's own logic.\n return new Proxy(\n {},\n {\n get: (_target, prop) => {\n if (prop === '__esModule') return true;\n return ({\n children,\n ...props\n }: {\n children?: React.ReactNode;\n [key: string]: unknown;\n }) =>\n React.createElement(\n 'div',\n { 'data-component': String(prop), ...props },\n children,\n );\n },\n },\n );\n }\n }\n throw new Error(`Import not allowed: ${id}`);\n };\n\n const exportsRecord = await evaluateComponentSource(\n input.sourceCode,\n sandboxRequire,\n );\n\n const Component = exportsRecord.default;\n if (typeof Component !== 'function') {\n return {\n ok: false,\n error: 'No default export function found in compiled code',\n };\n }\n\n const html = ReactDOMServer.renderToString(\n React.createElement(\n Component as React.ComponentType<JsonObject>,\n input.sampleProps ?? {},\n ),\n );\n\n if (!html || html.length < 5) {\n return { ok: false, error: 'Component rendered empty output' };\n }\n\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('Cannot read properties of undefined')) {\n return {\n ok: false,\n error: `Render error: ${message}. A prop is likely undefined — add a default value or null check.`,\n };\n }\n if (message.includes('Cannot read properties of null')) {\n return {\n ok: false,\n error: `Render error: ${message}. A value is null — add a null check or fallback.`,\n };\n }\n if (message.includes('is not a function')) {\n return {\n ok: false,\n error: `Render error: ${message}. Check that all imported functions exist and are called correctly.`,\n };\n }\n return { ok: false, error: `Render error: ${message}` };\n }\n}\n\nfunction emit(output: WorkerOutput): void {\n process.stdout.write(JSON.stringify(output));\n}\n\nasync function readAllStdin(): Promise<string> {\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n for await (const chunk of process.stdin) {\n buffer += chunk as string;\n }\n return buffer;\n}\n\n/** True when this file is the process entry (`node <this file>`), not an import. */\nfunction isProcessEntry(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n // Resolve BOTH sides through the filesystem: `import.meta.url` is this\n // file's realpath while argv[1] is whatever the spawner passed — a\n // symlinked spawn path must still read as the entry, or the worker would\n // exit with no verdict and the host would read a silent `unverified`.\n let entryReal: string;\n try {\n entryReal = realpathSync(resolvePath(entry));\n } catch {\n return false;\n }\n return entryReal === realpathSync(fileURLToPath(import.meta.url));\n}\n\nif (isProcessEntry()) {\n void main().catch((err: unknown) => {\n emit({\n ok: false,\n error: `worker crashed: ${err instanceof Error ? err.message : String(err)}`,\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/internal/evaluate-component-source.ts","../../src/internal/open-record.ts","../../src/internal/verdict-line.ts","../../src/tools/render-check-worker.ts"],"names":["esbuild","resolvePath"],"mappings":"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","file":"render-check-worker.js","sourcesContent":["// packages/ui-gen/src/internal/evaluate-component-source.ts\n//\n// The ONE compile-and-evaluate helper for component source: esbuild\n// TSX→CJS transform + vm.Script in-context execution behind a\n// caller-supplied `require`. Consumed by:\n//\n// - `tools/render-check-worker.ts` — the subprocess render smoke\n// test (host isolation lives at the spawn layer, in the parent's\n// `tryRender` via `@ggui-ai/sandbox`).\n// - `harness/check/runtime-render/load-component.ts` — the in-loop\n// probe (single-realm React; caller injects module resolutions).\n//\n// The module-resolution POLICY is deliberately not shared — each\n// caller owns its own `require` shim (allowed specifiers, fallbacks,\n// error wording). What is shared is the mechanical eval pipeline:\n//\n// 1. esbuild.transform(source) → CJS\n// 2. wrap in `(function(require, exports, module) { ... })`\n// 3. vm.Script(...).runInThisContext()\n// 4. call with the caller's require + a fresh module object\n// 5. return `module.exports` read AFTER execution — esbuild's CJS\n// output REPLACES `module.exports` (`module.exports =\n// __toCommonJS(...)`), so the initial reference can't be trusted.\n\nexport async function evaluateComponentSource(\n sourceCode: string,\n sandboxRequire: (id: string) => unknown,\n): Promise<Record<string, unknown>> {\n const esbuild = await import(\"esbuild\");\n const { Script } = await import(\"node:vm\");\n\n const cjsResult = await esbuild.transform(sourceCode, {\n loader: \"tsx\",\n target: \"es2020\",\n format: \"cjs\",\n jsx: \"automatic\",\n jsxImportSource: \"react\",\n sourcefile: \"Component.tsx\",\n });\n\n const sandboxModule: { exports: Record<string, unknown> } = { exports: {} };\n\n const wrappedCode = `(function(require, exports, module) {\\n${cjsResult.code}\\n})`;\n const script = new Script(wrappedCode, { filename: \"Component.cjs\" });\n const fn = script.runInThisContext() as (\n require: (id: string) => unknown,\n exports: Record<string, unknown>,\n module: { exports: Record<string, unknown> },\n ) => void;\n\n fn(sandboxRequire, sandboxModule.exports, sandboxModule);\n\n return sandboxModule.exports;\n}\n","// packages/ui-gen/src/internal/open-record.ts\n//\n// Single documented narrowing seam for \"object used as an open\n// string-keyed property bag\" — the host global object and happy-dom's\n// `Window` both genuinely ARE open records at runtime (we install /\n// restore named globals like `window` / `document` on them by key),\n// but TypeScript cannot express that without polluting the global type\n// space via declaration merging. Centralizing the one structural\n// widening here keeps every call site cast-free.\n\n/**\n * View an object as an open string-keyed record.\n *\n * Single structural widening (`Record<string, unknown>` is assignable\n * to `object`, so the cast is checked in one direction by tsc — no\n * `unknown` erasure). Returns the SAME reference: property writes land\n * on the live object.\n */\nexport function openRecord(o: object): Record<string, unknown> {\n return o as Record<string, unknown>;\n}\n\n/**\n * The host global object viewed as an open property bag.\n *\n * Used by the render-check pipeline (and its subprocess worker) to\n * install and restore DOM globals by name.\n */\nexport function hostGlobals(): Record<string, unknown> {\n return openRecord(globalThis);\n}\n","/**\n * How an isolated worker that runs generated component code (the\n * runtime-render check's, the render smoke test's) hands its verdict to its\n * host.\n *\n * The worker's stdout is shared with the component: card code runs in the\n * worker's own realm with Node's `console`, so a card that logs even one line\n * would corrupt a verdict read as \"the whole of stdout\". The verdict\n * therefore goes out on one line carrying {@link VERDICT_PREFIX}, and the\n * host reads the LAST such line, ignoring everything else.\n */\n\n/** The prefix of the verdict line. Not a string a card would print by accident. */\nexport const VERDICT_PREFIX = '@@ggui-render-check-verdict@@';\n\n/** The worker's verdict line, newline-terminated. */\nexport function formatVerdictLine(value: unknown): string {\n return `${VERDICT_PREFIX} ${JSON.stringify(value)}\\n`;\n}\n\n/**\n * The value on the last verdict line in `stdout`, or `undefined` when no\n * line carries the prefix. A prefixed line that is not JSON throws, so the\n * host can report it as such.\n */\nexport function parseVerdictLine<Verdict>(stdout: string): Verdict | undefined {\n const lines = stdout.split('\\n');\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const line = lines[i]!;\n if (line.startsWith(`${VERDICT_PREFIX} `)) return JSON.parse(line.slice(VERDICT_PREFIX.length + 1)) as Verdict;\n }\n return undefined;\n}\n","/**\n * Child worker for `tryRender`. Runs the render smoke test in a\n * subprocess so the LLM-generated TSX executes outside the parent\n * Node process.\n *\n * Wire protocol (newline-delimited JSON):\n *\n * stdin: { sourceCode: string, sampleProps?: JsonObject }\n * stdout: { ok: true } | { ok: false, error: string }\n * exit: 0 on either outcome (errors are data, not status).\n *\n * Why a separate worker instead of `vm.Script.runInThisContext` in\n * the parent:\n *\n * - `runInThisContext` runs in the parent's V8 context. A\n * pathological component that attaches to a global, recurses\n * into the event loop, or allocates a large buffer poisons the\n * parent.\n * - A subprocess gives us real kill semantics on runaway code +\n * output caps + env isolation — delegated to\n * `@ggui-ai/sandbox`.\n *\n * The worker still uses `vm.Script` to run compiled CJS because the\n * component needs a tailored `require` (react / jsx-runtime /\n * @ggui-ai/design only). Host-level process isolation is the\n * sandbox; module-scoped `require` is the resolution policy. The\n * mechanical compile+vm pipeline is the shared\n * `evaluateComponentSource` helper (also used by the in-loop probe's\n * `loadComponent`); only the resolution policy lives here.\n *\n * Shutdown (ggui#1380): the compile runs on esbuild's service, a child\n * process esbuild keeps alive for the life of this worker. After the\n * verdict is written the worker calls `esbuild.stop()` so that child is\n * gone before the worker is. `workerMain` is exported for the\n * process-shape pins; `main()` runs only when this file is the process\n * entry, so importing it never reads stdin.\n */\nimport { createRequire } from 'node:module';\nimport { realpathSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as esbuild from 'esbuild';\nimport type { JsonObject } from '@ggui-ai/protocol';\nimport { evaluateComponentSource } from '../internal/evaluate-component-source.js';\nimport { hostGlobals } from '../internal/open-record.js';\nimport { formatVerdictLine } from '../internal/verdict-line.js';\n\ninterface WorkerInput {\n readonly sourceCode: string;\n readonly sampleProps?: JsonObject;\n}\n\ntype WorkerOutput =\n | { readonly ok: true }\n | { readonly ok: false; readonly error: string };\n\n/**\n * Parse one input document, render once, write the verdict. Whatever\n * happens, esbuild's service is stopped before this returns (or\n * rejects). If the service was never started, `stop()` is a no-op.\n */\nexport async function workerMain(raw: string): Promise<void> {\n try {\n let input: WorkerInput;\n try {\n input = JSON.parse(raw) as WorkerInput;\n } catch (err) {\n emit({\n ok: false,\n error: `worker: malformed input JSON — ${\n err instanceof Error ? err.message : String(err)\n }`,\n });\n return;\n }\n\n if (typeof input.sourceCode !== 'string' || input.sourceCode.length === 0) {\n emit({ ok: false, error: 'worker: sourceCode is required' });\n return;\n }\n\n const result = await renderOnce(input);\n emit(result);\n } finally {\n // Post-verdict work must never turn a written verdict into a failure:\n // a rejecting `stop()` would otherwise become an unhandled rejection,\n // exit code 1, and the host discarding the verdict on stdout.\n try {\n await esbuild.stop();\n } catch (err) {\n process.stderr.write(\n `render-check worker: esbuild.stop() failed — ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n}\n\nasync function main(): Promise<void> {\n await workerMain(await readAllStdin());\n}\n\nasync function renderOnce(input: WorkerInput): Promise<WorkerOutput> {\n // Minimal window/document shims — mirror the in-process impl so\n // components that touch either global don't crash on import. The\n // shims only need to exist for the render; they're torn down by\n // process exit.\n const g = hostGlobals();\n if (!('window' in globalThis)) {\n g.window = {\n addEventListener: () => {},\n removeEventListener: () => {},\n };\n }\n if (!('document' in globalThis)) {\n g.document = {\n getElementById: () => null,\n createElement: () => ({}),\n addEventListener: () => {},\n };\n }\n\n try {\n const React = await import('react');\n const ReactDOMServer = await import('react-dom/server');\n\n const require_ = createRequire(import.meta.url);\n\n const sandboxRequire = (id: string): unknown => {\n if (id === 'react/jsx-runtime' || id === 'react/jsx-dev-runtime') {\n return require_('react/jsx-runtime');\n }\n if (id === 'react') return require_('react');\n if (id.startsWith('@ggui-ai/design')) {\n try {\n return require_(id);\n } catch {\n // Fallback proxy — used in environments where the real\n // design package isn't resolvable. Each primitive becomes\n // a pass-through `div` so the render still exercises the\n // component's own logic.\n return new Proxy(\n {},\n {\n get: (_target, prop) => {\n if (prop === '__esModule') return true;\n return ({\n children,\n ...props\n }: {\n children?: React.ReactNode;\n [key: string]: unknown;\n }) =>\n React.createElement(\n 'div',\n { 'data-component': String(prop), ...props },\n children,\n );\n },\n },\n );\n }\n }\n throw new Error(`Import not allowed: ${id}`);\n };\n\n const exportsRecord = await evaluateComponentSource(\n input.sourceCode,\n sandboxRequire,\n );\n\n const Component = exportsRecord.default;\n if (typeof Component !== 'function') {\n return {\n ok: false,\n error: 'No default export function found in compiled code',\n };\n }\n\n const html = ReactDOMServer.renderToString(\n React.createElement(\n Component as React.ComponentType<JsonObject>,\n input.sampleProps ?? {},\n ),\n );\n\n if (!html || html.length < 5) {\n return { ok: false, error: 'Component rendered empty output' };\n }\n\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes('Cannot read properties of undefined')) {\n return {\n ok: false,\n error: `Render error: ${message}. A prop is likely undefined — add a default value or null check.`,\n };\n }\n if (message.includes('Cannot read properties of null')) {\n return {\n ok: false,\n error: `Render error: ${message}. A value is null — add a null check or fallback.`,\n };\n }\n if (message.includes('is not a function')) {\n return {\n ok: false,\n error: `Render error: ${message}. Check that all imported functions exist and are called correctly.`,\n };\n }\n return { ok: false, error: `Render error: ${message}` };\n }\n}\n\nfunction emit(output: WorkerOutput): void {\n process.stdout.write(formatVerdictLine(output));\n}\n\nasync function readAllStdin(): Promise<string> {\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n for await (const chunk of process.stdin) {\n buffer += chunk as string;\n }\n return buffer;\n}\n\n/** True when this file is the process entry (`node <this file>`), not an import. */\nfunction isProcessEntry(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n // Resolve BOTH sides through the filesystem: `import.meta.url` is this\n // file's realpath while argv[1] is whatever the spawner passed — a\n // symlinked spawn path must still read as the entry, or the worker would\n // exit with no verdict and the host would read a silent `unverified`.\n let entryReal: string;\n try {\n entryReal = realpathSync(resolvePath(entry));\n } catch {\n return false;\n }\n return entryReal === realpathSync(fileURLToPath(import.meta.url));\n}\n\nif (isProcessEntry()) {\n void main().catch((err: unknown) => {\n emit({\n ok: false,\n error: `worker crashed: ${err instanceof Error ? err.message : String(err)}`,\n });\n });\n}\n"]}
|
|
@@ -1,9 +1,288 @@
|
|
|
1
|
+
import { JsonObject, DataContract } from '@ggui-ai/protocol';
|
|
2
|
+
import { LLMToolDef } from './llm.js';
|
|
1
3
|
import { GenerationRuntimeProbeStatus, GenerationRuntimeProbeOutcome } from '@ggui-ai/mcp-server-core';
|
|
2
|
-
import { C as CanvasClass, D as DesignMode } from './design-mode-
|
|
4
|
+
import { C as CanvasClass, D as DesignMode } from './design-mode-DGWN_Gkm.js';
|
|
3
5
|
import { R as RiskTier, a as RenderShape, S as StateShape, W as WriteShape, F as FetchShape, L as LayoutShape, C as Classification, A as AxisVector } from './axes-CKVCzR_x.js';
|
|
4
|
-
import { DataContract, JsonObject } from '@ggui-ai/protocol';
|
|
5
6
|
import { A as AxisKey } from './types-Dh1s4K9j.js';
|
|
6
7
|
|
|
8
|
+
interface AgentConfig {
|
|
9
|
+
provider: 'anthropic' | 'openai' | 'google' | 'openrouter';
|
|
10
|
+
model: string;
|
|
11
|
+
/**
|
|
12
|
+
* Sampling temperature. When defined it is threaded into the provider
|
|
13
|
+
* request; when undefined the provider default is used (unchanged
|
|
14
|
+
* behavior for all existing callers).
|
|
15
|
+
*
|
|
16
|
+
* Capability is the ROUTER's: Anthropic Opus 4.7+ and the 5-family
|
|
17
|
+
* (`claude-opus-4-7*`, `claude-fable-5*`, `claude-opus-5*`,
|
|
18
|
+
* `claude-sonnet-5*`, incl. Bedrock ids) return HTTP 400 on a
|
|
19
|
+
* non-default `temperature`/`top_p`/`top_k`, so the Anthropic adapter
|
|
20
|
+
* STRIPS the value for those ids (one log line naming model + param)
|
|
21
|
+
* and reports the effective sampling on `LLMResponse.sampling`. Callers
|
|
22
|
+
* keep expressing intent; disclosure reads the applied value.
|
|
23
|
+
*/
|
|
24
|
+
temperature?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Explicit per-call credentials/routing, bypassing `process.env`
|
|
27
|
+
* entirely when supplied. Absent (default) preserves today's
|
|
28
|
+
* behavior — each agent falls back to reading its provider's env
|
|
29
|
+
* var(s) directly, so existing callers (including self-hosters who
|
|
30
|
+
* set env vars once at process start) are unaffected. Use this to
|
|
31
|
+
* run concurrent generations safely in one process: two simultaneous
|
|
32
|
+
* calls with different `routeOverride`s never race on shared
|
|
33
|
+
* `process.env` state.
|
|
34
|
+
*/
|
|
35
|
+
routeOverride?: {
|
|
36
|
+
readonly apiKey?: string;
|
|
37
|
+
/** Anthropic-only: force the Bedrock-IAM client instead of the direct API client. */
|
|
38
|
+
readonly useBedrock?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Anthropic-only (ggui#1185): run on the machine's own Claude Code
|
|
41
|
+
* LOGIN instead of a key — the route decision `auth: 'claude-code-login'`
|
|
42
|
+
* lands here, and `createAgent` / `createVisionAgent` select
|
|
43
|
+
* `ClaudeCodeLoginAgent` (one `query()` per call) instead of the raw
|
|
44
|
+
* API client. Every agent the harness builds — coding, evaluation,
|
|
45
|
+
* visual judge — takes the same decision. Off unless set; a real key
|
|
46
|
+
* never coexists with it (the router refuses a non-anthropic route).
|
|
47
|
+
*/
|
|
48
|
+
readonly claudeCodeLogin?: boolean;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Observer invoked once per retried attempt when `apiCall()` (#489)
|
|
52
|
+
* retries a rate-limited (HTTP 429) provider call. Fires AFTER the
|
|
53
|
+
* retry decision is made and BEFORE the delay is awaited — never on
|
|
54
|
+
* the terminal failure/success of the whole call. Purely an
|
|
55
|
+
* observability hook: it never influences whether or how long
|
|
56
|
+
* `apiCall()` retries. If the observer itself throws, `apiCall()`
|
|
57
|
+
* catches it, logs a `console.warn`, and proceeds to the retry
|
|
58
|
+
* delay unaffected — a broken observer cannot turn a recoverable
|
|
59
|
+
* 429 into an unrelated hard failure. Absent (default) is a no-op;
|
|
60
|
+
* `apiCall()` still logs every retry via `console.warn` regardless.
|
|
61
|
+
*/
|
|
62
|
+
onRetry?: (info: ProviderRetryInfo) => void;
|
|
63
|
+
}
|
|
64
|
+
/** One retried attempt's context — passed to `AgentConfig.onRetry`. */
|
|
65
|
+
interface ProviderRetryInfo {
|
|
66
|
+
readonly provider: AgentConfig['provider'];
|
|
67
|
+
/** 1-based index of this retry (1 = first retry, 2 = second, …). */
|
|
68
|
+
readonly attempt: number;
|
|
69
|
+
/** Total retries `apiCall()` will attempt before giving up. */
|
|
70
|
+
readonly maxAttempts: number;
|
|
71
|
+
/** HTTP status of the failed call, when the caught error carries one. */
|
|
72
|
+
readonly status: number | undefined;
|
|
73
|
+
/** Provider-supplied `Retry-After` value in seconds, when present and within the cap. */
|
|
74
|
+
readonly retryAfterSec: number | undefined;
|
|
75
|
+
/** Delay actually awaited before the next attempt. */
|
|
76
|
+
readonly delayMs: number;
|
|
77
|
+
/** One-line error summary (same shape as the existing `console.error` line). */
|
|
78
|
+
readonly message: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What sampling the provider ACTUALLY received for a text call — so a
|
|
82
|
+
* caller that asked for `temperature: 0` (a disclosed reproducibility
|
|
83
|
+
* property, e.g. the benchmark judge panel) can record the effective
|
|
84
|
+
* value instead of the requested one. `'provider-default'` means no
|
|
85
|
+
* temperature reached the provider: either the caller passed none, or
|
|
86
|
+
* the adapter stripped it because the model rejects sampling params
|
|
87
|
+
* (Anthropic Opus 4.7+ / 5-family → HTTP 400 on non-default
|
|
88
|
+
* `temperature`/`top_p`/`top_k`); `strippedReason` names the second case.
|
|
89
|
+
*/
|
|
90
|
+
interface AppliedSampling {
|
|
91
|
+
readonly temperature: number | 'provider-default';
|
|
92
|
+
readonly strippedReason?: string;
|
|
93
|
+
}
|
|
94
|
+
interface LLMResponse {
|
|
95
|
+
text: string;
|
|
96
|
+
inputTokens: number;
|
|
97
|
+
outputTokens: number;
|
|
98
|
+
/**
|
|
99
|
+
* Effective sampling for this call (see {@link AppliedSampling}).
|
|
100
|
+
* Every provider's `callText` sets it; absent only on paths that
|
|
101
|
+
* predate the field — treat absence as unknown, never as "as
|
|
102
|
+
* requested".
|
|
103
|
+
*/
|
|
104
|
+
sampling?: AppliedSampling;
|
|
105
|
+
/**
|
|
106
|
+
* Why the provider stopped, normalized (ggui#1127): `length` = it hit the output cap, `stop` = it ended its
|
|
107
|
+
* answer, `content-filter` = a safety stop, `other` = anything else it reported. Set by the vision calls of
|
|
108
|
+
* providers that report one (Anthropic `stop_reason`, Google `finishReason`); absent when the provider or route
|
|
109
|
+
* said nothing — read absence as unknown, never as `stop`.
|
|
110
|
+
*/
|
|
111
|
+
finishReason?: VisionFinishReason;
|
|
112
|
+
/**
|
|
113
|
+
* The model that answered, in the provider's own words (ggui#1687): Anthropic's `model`, Google's `modelVersion`.
|
|
114
|
+
* The request names an alias or a resolved id; this is what the provider says served it. Absent when it said
|
|
115
|
+
* nothing — read absence as unknown, never as the requested model.
|
|
116
|
+
*/
|
|
117
|
+
servedModel?: string;
|
|
118
|
+
}
|
|
119
|
+
/** The normalized stop reason a vision call reports (ggui#1127). */
|
|
120
|
+
type VisionFinishReason = 'stop' | 'length' | 'content-filter' | 'other';
|
|
121
|
+
interface LLMTool {
|
|
122
|
+
name: string;
|
|
123
|
+
description: string;
|
|
124
|
+
parameters: JsonObject;
|
|
125
|
+
handler: (args: JsonObject) => Promise<{
|
|
126
|
+
content: Array<{
|
|
127
|
+
text: string;
|
|
128
|
+
}>;
|
|
129
|
+
isError?: boolean;
|
|
130
|
+
}>;
|
|
131
|
+
}
|
|
132
|
+
interface LLMWithToolsResponse {
|
|
133
|
+
text: string;
|
|
134
|
+
inputTokens: number;
|
|
135
|
+
outputTokens: number;
|
|
136
|
+
turnsUsed: number;
|
|
137
|
+
/** Prompt-cache reads summed over the loop's turns, when the provider reports them (absent = unreported, never zero). */
|
|
138
|
+
cacheReadTokens?: number;
|
|
139
|
+
}
|
|
140
|
+
interface LLMToolCall {
|
|
141
|
+
/** Provider-specific call ID (for sendToolResult) */
|
|
142
|
+
id?: string;
|
|
143
|
+
name: string;
|
|
144
|
+
input: JsonObject;
|
|
145
|
+
}
|
|
146
|
+
interface LLMToolCallResponse {
|
|
147
|
+
toolCalls: LLMToolCall[];
|
|
148
|
+
inputTokens: number;
|
|
149
|
+
outputTokens: number;
|
|
150
|
+
/**
|
|
151
|
+
* The tool-choice mode the provider ACTUALLY received. Callers ask
|
|
152
|
+
* for `'required'` (Anthropic `tool_choice: any`); Claude Fable 5.1 and
|
|
153
|
+
* Opus 5.5 reject forced tool choice with HTTP 400, so the Anthropic
|
|
154
|
+
* and OpenRouter adapters downgrade to `'auto'` for those families and
|
|
155
|
+
* report it here. A
|
|
156
|
+
* downgraded call may legitimately return zero tool calls — the
|
|
157
|
+
* evaluator's `criteriaCoverage` records that as `skipped`, never as
|
|
158
|
+
* a pass. Absent = predates the field (treat as unknown).
|
|
159
|
+
*/
|
|
160
|
+
appliedToolChoice?: 'required' | 'auto';
|
|
161
|
+
/**
|
|
162
|
+
* Tokens read from / written to the prompt cache, when the provider
|
|
163
|
+
* reports them. Optional because not every provider exposes
|
|
164
|
+
* prompt-cache accounting — absent means "unreported", never zero.
|
|
165
|
+
*/
|
|
166
|
+
cacheReadTokens?: number;
|
|
167
|
+
cacheCreationTokens?: number;
|
|
168
|
+
}
|
|
169
|
+
/** Result of executing a tool — passed to sendToolResult to close the API contract. */
|
|
170
|
+
interface LLMToolResult {
|
|
171
|
+
/** Tool call ID from the response (for providers that need it) */
|
|
172
|
+
callId?: string;
|
|
173
|
+
/** Tool name */
|
|
174
|
+
name: string;
|
|
175
|
+
/** Text result of executing the tool */
|
|
176
|
+
result: string;
|
|
177
|
+
/** Whether the tool execution failed */
|
|
178
|
+
isError?: boolean;
|
|
179
|
+
}
|
|
180
|
+
declare abstract class LLMAgent {
|
|
181
|
+
abstract readonly provider: AgentConfig['provider'];
|
|
182
|
+
private client;
|
|
183
|
+
protected lastSessionId: string | undefined;
|
|
184
|
+
/**
|
|
185
|
+
* Explicit per-call routing override — see `AgentConfig.routeOverride`.
|
|
186
|
+
* Absent when constructed via the bare `new XAgent()` form (every
|
|
187
|
+
* existing call site), so `createClient()`/`resolveModel()` fall
|
|
188
|
+
* back to `process.env` exactly as before.
|
|
189
|
+
*/
|
|
190
|
+
protected readonly routeOverride: AgentConfig['routeOverride'];
|
|
191
|
+
/** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
|
|
192
|
+
protected readonly onRetry: AgentConfig['onRetry'];
|
|
193
|
+
constructor(routeOverride?: AgentConfig['routeOverride'], onRetry?: AgentConfig['onRetry']);
|
|
194
|
+
protected abstract resolveModel(model: string): string;
|
|
195
|
+
protected abstract createClient(): Promise<unknown>;
|
|
196
|
+
protected getClient<T>(): Promise<T>;
|
|
197
|
+
/** Text-only call — no tools */
|
|
198
|
+
abstract callText(model: string, systemPrompt: string, userPrompt: string, maxTokens?: number, temperature?: number): Promise<LLMResponse>;
|
|
199
|
+
/**
|
|
200
|
+
* Single-turn function calling — returns tool calls without executing them.
|
|
201
|
+
* Each provider uses its native function/tool calling:
|
|
202
|
+
* - Anthropic: tool_use blocks
|
|
203
|
+
* - OpenAI: function_call output items
|
|
204
|
+
* - Google: functionCall parts
|
|
205
|
+
* SDK handles JSON escaping — safe for code, diffs, and other content.
|
|
206
|
+
*/
|
|
207
|
+
abstract callTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMToolDef[], toolChoice?: 'required' | 'auto',
|
|
208
|
+
/**
|
|
209
|
+
* Optional scoped fallback tools. If the primary tools fail with a
|
|
210
|
+
* transport-class error (e.g. `malformed_tool_call` on Gemini after
|
|
211
|
+
* retry exhaustion), the provider may retry once with these narrower
|
|
212
|
+
* tools before throwing. Universal signal — not provider-gated.
|
|
213
|
+
*/
|
|
214
|
+
scopedTools?: LLMToolDef[]): Promise<LLMToolCallResponse>;
|
|
215
|
+
/** Multi-turn agentic loop — executes tools internally */
|
|
216
|
+
abstract callWithTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMTool[], maxTurns: number): Promise<LLMWithToolsResponse>;
|
|
217
|
+
/**
|
|
218
|
+
* Pre-warm cache for repeated callTools() calls with the same system prompt + tools.
|
|
219
|
+
* Override in providers that support server-side context caching (e.g., Google).
|
|
220
|
+
* No-op by default (Anthropic/OpenAI handle caching automatically per-request).
|
|
221
|
+
*/
|
|
222
|
+
warmCache(_model: string, _systemPrompt: string, _tools: LLMToolDef[], _toolChoice?: 'required' | 'auto'): Promise<void>;
|
|
223
|
+
/** Cleanup any cached resources. Call after generation completes. No-op by default. */
|
|
224
|
+
cleanup(): Promise<void>;
|
|
225
|
+
/** Reset session state between independent generation runs. */
|
|
226
|
+
resetSession(): void;
|
|
227
|
+
/**
|
|
228
|
+
* Send tool execution results back to the provider to close the API contract.
|
|
229
|
+
* Call this after executing tools from callTools() and before the next callTools().
|
|
230
|
+
*
|
|
231
|
+
* For providers with server-side state (Google, OpenAI), this sends the
|
|
232
|
+
* function results so the next callTools() can chain properly.
|
|
233
|
+
* For stateless providers (Anthropic), this is a no-op.
|
|
234
|
+
*
|
|
235
|
+
* Override in providers that need it.
|
|
236
|
+
*/
|
|
237
|
+
sendToolResult(_results: LLMToolResult[]): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Execute an API call, retrying once or twice on HTTP 429
|
|
240
|
+
* (rate-limited) before giving up (#489).
|
|
241
|
+
*
|
|
242
|
+
* Policy: on a 429, honor the provider's `Retry-After` header when
|
|
243
|
+
* present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
|
|
244
|
+
* longer wait is treated as "don't retry", not "wait longer": making
|
|
245
|
+
* the caller sit through 15+ more seconds on a single failed call is
|
|
246
|
+
* a bad experience regardless of what's driving the request);
|
|
247
|
+
* otherwise fall back to exponential backoff with jitter, capped at
|
|
248
|
+
* `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
|
|
249
|
+
* `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
|
|
250
|
+
* exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
|
|
251
|
+
* non-429 error, or a 429 with no attempts left, is logged and
|
|
252
|
+
* re-thrown immediately — unchanged from the pre-#489 behavior.
|
|
253
|
+
*
|
|
254
|
+
* **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
|
|
255
|
+
* bounds a single `apiCall()` invocation, not a whole generation. A
|
|
256
|
+
* generation issues many sequential `apiCall()` calls (coding turns,
|
|
257
|
+
* tool-result round-trips, eval rounds); under sustained throttling
|
|
258
|
+
* each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
|
|
259
|
+
* a generation with k rate-limited calls can add up to roughly `k *
|
|
260
|
+
* MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
|
|
261
|
+
* resources (e.g. the caller's concurrency slot) — not a flat 20s
|
|
262
|
+
* ceiling. No shared per-generation budget is threaded here by
|
|
263
|
+
* design (accepted ruling, not an oversight): the caller's own
|
|
264
|
+
* queue/admission backstop is what bounds how long other
|
|
265
|
+
* queued work waits, independent of any one generation's retry
|
|
266
|
+
* total.
|
|
267
|
+
*/
|
|
268
|
+
protected apiCall<T>(fn: () => Promise<T>): Promise<T>;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Create a fresh agent instance for the given provider.
|
|
272
|
+
* Each call returns a new instance — no shared state between callers.
|
|
273
|
+
*
|
|
274
|
+
* Accepts either a bare provider string (every pre-existing call
|
|
275
|
+
* site — the agent falls back to reading its provider's env var(s)
|
|
276
|
+
* directly) or a full {@link AgentConfig}, whose `routeOverride`
|
|
277
|
+
* (when present) is threaded into the constructed agent so it never
|
|
278
|
+
* touches `process.env`.
|
|
279
|
+
*/
|
|
280
|
+
declare function createAgent(provider: AgentConfig['provider']): LLMAgent;
|
|
281
|
+
declare function createAgent(config: AgentConfig): LLMAgent;
|
|
282
|
+
/** Providers whose agent implements {@link VisionAgent}. */
|
|
283
|
+
type VisionProvider = 'anthropic' | 'google';
|
|
284
|
+
declare function callLLM(config: AgentConfig, systemPrompt: string, userPrompt: string, maxTokens?: number): Promise<LLMResponse>;
|
|
285
|
+
|
|
7
286
|
type Priority = "P0" | "P1" | "P2";
|
|
8
287
|
|
|
9
288
|
type EvalTier = 0 | 1 | 2;
|
|
@@ -26,6 +305,11 @@ interface EvalIssue {
|
|
|
26
305
|
description: string;
|
|
27
306
|
fix: string;
|
|
28
307
|
line?: number;
|
|
308
|
+
/**
|
|
309
|
+
* The visual leg's provenance (ggui#1545): `judge` (the model's finding) or `instrument` (a deterministic
|
|
310
|
+
* measurement). Absent on every other leg's issues. `visualEvaluation.actOn` acts on `judge` only.
|
|
311
|
+
*/
|
|
312
|
+
origin?: "judge" | "instrument";
|
|
29
313
|
}
|
|
30
314
|
/**
|
|
31
315
|
* Runtime-render probe execution status — four-valued so "the probe
|
|
@@ -62,6 +346,22 @@ interface ProbeHostLoad {
|
|
|
62
346
|
readonly end: number;
|
|
63
347
|
readonly cores: number;
|
|
64
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* ggui#1398 — whether the controls the probe clicked showed they were
|
|
351
|
+
* working once their action was pending. Report-only: it is read by the
|
|
352
|
+
* benchmark and never counted into the probe's warnings or fed back to
|
|
353
|
+
* the coding loop.
|
|
354
|
+
*/
|
|
355
|
+
interface ProbePendingAffordance {
|
|
356
|
+
/** Controls the pending phase marked pending: those whose click dispatched their action (click or submit wiring) and that were still in the page. `visible + missing.length === dispatched`. */
|
|
357
|
+
readonly dispatched: number;
|
|
358
|
+
/** Of those, how many visibly changed while pending (disabled, busy, text, or replaced). */
|
|
359
|
+
readonly visible: number;
|
|
360
|
+
/** The actions whose control did not visibly change, including one whose card threw while pending. */
|
|
361
|
+
readonly missing: readonly string[];
|
|
362
|
+
/** Actions whose control was no longer in the page when its turn came (the card replaced it, or blanked after a throw): never marked, not counted as dispatched. */
|
|
363
|
+
readonly gone: readonly string[];
|
|
364
|
+
}
|
|
65
365
|
/**
|
|
66
366
|
* The engine's own fields on a probe's meta, beside the outcome a
|
|
67
367
|
* generation's metadata reports: why a probe did not finish, its render
|
|
@@ -78,6 +378,11 @@ interface RuntimeProbeMetaDetail {
|
|
|
78
378
|
readonly hostLoad?: ProbeHostLoad;
|
|
79
379
|
/** Time the check waited for a probe slot before it started, ms; present only when > 0 (ggui#1380 C2b). */
|
|
80
380
|
readonly queuedMs?: number;
|
|
381
|
+
/**
|
|
382
|
+
* `ran` only: the pending-affordance walk (ggui#1398). Absent when no
|
|
383
|
+
* action walk ran, so "no data" and "0 of 0" read differently.
|
|
384
|
+
*/
|
|
385
|
+
readonly pendingAffordance?: ProbePendingAffordance;
|
|
81
386
|
}
|
|
82
387
|
/**
|
|
83
388
|
* Probe execution meta stamped onto an `EvalResult` at the exit-decision
|
|
@@ -280,6 +585,64 @@ interface CanvasJudgeRecord {
|
|
|
280
585
|
readonly sigma: number;
|
|
281
586
|
/** The judge's per-canvas critique per sample — the words beside a swing. */
|
|
282
587
|
readonly notes: string[];
|
|
588
|
+
/**
|
|
589
|
+
* ggui#1127 — the cause of each scoring sample whose answer failed to parse and was SALVAGED: its four dimensions
|
|
590
|
+
* were recovered from the closed prefix, its issues kept only as far as they closed, its critique lost. One entry
|
|
591
|
+
* per salvaged sample; absent when every sample parsed. The salvaged sample's score still counts.
|
|
592
|
+
*/
|
|
593
|
+
readonly salvaged?: readonly JudgeSalvageCause[];
|
|
594
|
+
/**
|
|
595
|
+
* ggui#1127 — the criteria call's answer was salvaged. A salvage keeps no criteria, so that frame's judge rows read
|
|
596
|
+
* "not answered" with this cause rather than a clean `n/a`. Absent when the call parsed or was not asked.
|
|
597
|
+
*/
|
|
598
|
+
readonly criteriaSalvaged?: JudgeSalvageCause;
|
|
599
|
+
/**
|
|
600
|
+
* ggui#1687 — the model asked for, and every model the provider said answered this canvas's calls (scoring and
|
|
601
|
+
* criteria), deduplicated. `served` is empty when no provider named one: unknown, never "the requested model".
|
|
602
|
+
*/
|
|
603
|
+
readonly models?: {
|
|
604
|
+
readonly requested: string;
|
|
605
|
+
readonly served: readonly string[];
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* ggui#1687 — what the criteria call's answer held, before resolution. A row the judge left unanswered reads `n/a`
|
|
610
|
+
* either way; this says whether the call came back empty, cut, or with entries the parser had to drop.
|
|
611
|
+
*/
|
|
612
|
+
interface CriteriaAnswerCounts {
|
|
613
|
+
/** The answer carried a `criteria` array. `false`: absent or not an array, so no row was read from it. */
|
|
614
|
+
readonly array: boolean;
|
|
615
|
+
/** Entries in the array. */
|
|
616
|
+
readonly received: number;
|
|
617
|
+
/** Entries kept: an object with a string id and a verdict of pass, fail or n/a. */
|
|
618
|
+
readonly kept: number;
|
|
619
|
+
/** Entries dropped, by why. */
|
|
620
|
+
readonly dropped: {
|
|
621
|
+
readonly notObject: number;
|
|
622
|
+
readonly badId: number;
|
|
623
|
+
readonly badVerdict: number;
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* ggui#1127 — why a judge answer that failed to parse had to be salvaged. `output-cap`: the provider reported it
|
|
628
|
+
* stopped at the output cap. `malformed`: the provider reported a normal stop, so the answer ended but did not
|
|
629
|
+
* parse. `stopped`: the provider stopped it for another reason it named (a safety stop, or one it reports as
|
|
630
|
+
* other), so it is cut, not malformed. `unknown`: the provider or route reported no stop reason. Never inferred
|
|
631
|
+
* from the answer's length.
|
|
632
|
+
*/
|
|
633
|
+
type JudgeSalvageCause = "output-cap" | "malformed" | "stopped" | "unknown";
|
|
634
|
+
/** ggui#1127 — salvaged judge answers across a canvas run, per leg, as N of M. */
|
|
635
|
+
interface JudgeSalvageCounts {
|
|
636
|
+
/** Scoring answers: `answers` = samples that parsed or were salvaged, `salvaged` = those salvaged. */
|
|
637
|
+
readonly scoring: {
|
|
638
|
+
readonly salvaged: number;
|
|
639
|
+
readonly answers: number;
|
|
640
|
+
};
|
|
641
|
+
/** Criteria calls: `answered` = calls that parsed or were salvaged, `salvaged` = those salvaged (criteria lost). */
|
|
642
|
+
readonly criteria: {
|
|
643
|
+
readonly salvaged: number;
|
|
644
|
+
readonly answered: number;
|
|
645
|
+
};
|
|
283
646
|
}
|
|
284
647
|
|
|
285
648
|
type CriteriaSeverity = "must" | "should";
|
|
@@ -333,6 +696,22 @@ interface CriterionVerdict {
|
|
|
333
696
|
source: CriteriaSource;
|
|
334
697
|
verdict: CriteriaVerdict;
|
|
335
698
|
evidence: string;
|
|
699
|
+
/**
|
|
700
|
+
* ggui#1663 — a measured reading beside a judge row, REPORT-ONLY: the row's `verdict` stays the judge's, so this
|
|
701
|
+
* changes no verdict, score or bar. Present only on rows an instrument answers beside the judge (`space.edge`).
|
|
702
|
+
*/
|
|
703
|
+
instrument?: {
|
|
704
|
+
verdict: CriteriaVerdict;
|
|
705
|
+
evidence: string;
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/** ggui#1687 — the criteria call as it came back: its answer's counts, ids it answered outside the bank, its stop. */
|
|
709
|
+
interface CriteriaCallRecord {
|
|
710
|
+
readonly answer: CriteriaAnswerCounts;
|
|
711
|
+
/** Kept entries whose id names no row of the bank. */
|
|
712
|
+
readonly unknownIds: number;
|
|
713
|
+
/** The provider's normalized stop reason (`length` = the output cap); absent when it reported none. */
|
|
714
|
+
readonly finishReason?: VisionFinishReason;
|
|
336
715
|
}
|
|
337
716
|
interface CriteriaBlock {
|
|
338
717
|
/** sha256(bank version | selector version | canonical context), 16 hex — the block's own digest. */
|
|
@@ -342,6 +721,8 @@ interface CriteriaBlock {
|
|
|
342
721
|
context: CriteriaContext;
|
|
343
722
|
selection: CriteriaSelection[];
|
|
344
723
|
verdicts: CriterionVerdict[];
|
|
724
|
+
/** ggui#1687 — the call that answered this block, when one did. Absent for a block resolved with no call. */
|
|
725
|
+
call?: CriteriaCallRecord;
|
|
345
726
|
}
|
|
346
727
|
/** The roll-up across canvases; a `must` at `n/a` is listed, never counted as a pass. */
|
|
347
728
|
interface CriteriaRollup {
|
|
@@ -575,4 +956,4 @@ declare function getLLMCriteria(designMode?: DesignMode): EvalCriterion[];
|
|
|
575
956
|
*/
|
|
576
957
|
declare function buildCodingCriteriaSummary(designMode?: DesignMode): string;
|
|
577
958
|
|
|
578
|
-
export { type AxisCheck as A,
|
|
959
|
+
export { type AxisCheck as A, type EvalOutcome as B, type CriteriaAnswerCounts as C, DEFAULT_QUALITY_CONFIG as D, type EvalIssue as E, type HostPresentationIgnoredReason as F, LLM_EVAL_STATIC_CRITERIA as G, type HostInlineFrame as H, type Priority as I, type JudgeSalvageCounts as J, type QualityMode as K, type LLMResponse as L, buildCodingCriteriaSummary as M, criteriaFor as N, getActionableIssues as O, type ProviderRetryInfo as P, type QualityConfig as Q, type RuntimeProbeStatus as R, getCriteriaByPriority as S, getCriterionById as T, getLLMCriteria as U, type VisionProvider as V, isBlocked as W, notApplicableCoverage as X, priorityForIssue as Y, type AxisCheckInput as a, type AgentConfig as b, callLLM as c, createAgent as d, type EvalResult as e, type ProbeHostLoad as f, type ProbePendingAffordance as g, type CriteriaContext as h, type CriteriaChroma as i, type CanvasJudgeRecord as j, type CriteriaBlock as k, type CanvasPresentationOutcome as l, matches as m, type CanvasHostPresentation as n, type VisualEvalSummary as o, type VisualCoverage as p, type EvalTier as q, type CriteriaSelection as r, type VisionFinishReason as s, CRITERIA as t, type CanvasVisualSummary as u, type ContractFeedbackRecord as v, type CriterionCoverage as w, type CriterionRunStatus as x, type EvalCategory as y, type EvalCriterion as z };
|