@ggui-ai/ui-gen 0.23.0 → 0.25.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/dist/adapter-Cg_rkpGq.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +58 -15
- package/dist/adapters/generation-dispatch.js +1418 -180
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +12 -8
- package/dist/advanced/index.js +1467 -239
- package/dist/advanced/index.js.map +1 -1
- package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
- package/dist/bank-CT4eEkta.d.ts +118 -0
- package/dist/blueprint-validator.js +153 -59
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +24 -12
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +12 -4
- package/dist/check/index.js +94 -2
- package/dist/check/index.js.map +1 -1
- package/dist/classifier/index.d.ts +2 -2
- package/dist/compose.d.ts +2 -2
- package/dist/{create-ui-generator-D0kbWwln.d.ts → create-ui-generator-DFnnArrv.d.ts} +43 -11
- package/dist/{dispatch-wxaIMwZt.d.ts → dispatch-CStl9eAw.d.ts} +5 -3
- package/dist/evaluation/axis-checks/index.d.ts +6 -5
- package/dist/evaluation/axis-checks/index.js +90 -2
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +4 -3
- package/dist/evaluation/axis-checks/registry.js +88 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +301 -23
- package/dist/evaluation/index.js +746 -30
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +9 -1
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.d.ts +3 -3
- package/dist/harness/check/runtime-render/index.d.ts +30 -11
- package/dist/harness/check/runtime-render/index.js +203 -60
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +5 -4
- package/dist/harness/index.js +33 -15
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +4 -3
- package/dist/harness/runtime.d.ts +11 -4
- package/dist/harness/runtime.js +26 -14
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +13 -4
- package/dist/harness/types-public.js +33 -15
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +4 -3
- package/dist/hash.d.ts +1 -1
- package/dist/index.d.ts +59 -8
- package/dist/index.js +1476 -241
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-DwwUK1bz.d.ts → llm-router-nfO7wPec.d.ts} +3 -1
- package/dist/patch.d.ts +4 -3
- package/dist/policy.d.ts +8 -5
- package/dist/policy.js.map +1 -1
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.d.ts +7 -1
- package/dist/tools/render-check-worker.js +55 -23
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
- package/dist/{types-public-Ow-tNtFE.d.ts → types-public-BJTLpzWn.d.ts} +208 -15
- 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/workflows.d.ts +4 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +173 -98
- package/src/adapters/tools.ts +2 -1
- package/src/boilerplate/free-design-prompt.ts +1 -1
- package/src/boilerplate/generate.ts +26 -1
- package/src/boilerplate/hard-sections.ts +13 -13
- package/src/boilerplate/system-prompt.ts +1 -1
- package/src/check/index.ts +1 -0
- package/src/check/run-tier0.ts +80 -1
- package/src/check/sample-props.ts +63 -0
- package/src/coding-agent/tools.ts +4 -1
- package/src/create-harness.ts +3 -3
- package/src/create-ui-generator.ts +204 -95
- package/src/evaluation/__fixtures__/png.ts +100 -0
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/universal.ts +111 -0
- package/src/evaluation/axis-checks/dispatch.ts +4 -1
- package/src/evaluation/criteria/bank.ts +85 -0
- package/src/evaluation/criteria/context.ts +52 -0
- package/src/evaluation/criteria/resolve.ts +159 -0
- package/src/evaluation/criteria/select.ts +79 -0
- package/src/evaluation/index.ts +23 -0
- package/src/evaluation/ink-extent.ts +200 -0
- package/src/evaluation/theme-css.ts +12 -5
- package/src/evaluation/types-public.ts +200 -11
- package/src/evaluation/types.ts +9 -0
- package/src/evaluation/visual-evaluator.ts +636 -30
- package/src/harness/check/runtime-render/adapter.ts +221 -75
- package/src/harness/check/runtime-render/index.ts +75 -2
- package/src/harness/check/runtime-render/render-check-host.ts +82 -19
- package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
- package/src/harness/check/runtime-render/render-check.ts +154 -117
- package/src/harness/coding/generate-task-runner.ts +60 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-coding-turn.ts +7 -3
- package/src/harness/coding/run-eval-round.ts +257 -15
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/runtime.ts +5 -0
- package/src/harness/types-public.ts +9 -1
- package/src/harness-tool-names.ts +38 -0
- package/src/index.ts +16 -0
- package/src/policy.ts +7 -4
- package/src/run-check.ts +2 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/get-wire.ts +1 -1
- package/src/tools/render-check-worker.ts +74 -23
- package/src/tools/render-check.ts +0 -60
- package/src/tools.ts +11 -1
- package/src/validation/primitives.ts +1 -1
- package/dist/render-check-Blwj8gB6.d.ts +0 -105
|
@@ -31,66 +31,97 @@
|
|
|
31
31
|
* Callers who want a lightweight single-shot path can build it
|
|
32
32
|
* themselves.
|
|
33
33
|
*/
|
|
34
|
-
import type { SingleComponentParams } from
|
|
34
|
+
import type { SingleComponentParams } from "./harness/runtime.js";
|
|
35
35
|
import type {
|
|
36
36
|
GadgetDescriptor,
|
|
37
37
|
GenerationError,
|
|
38
38
|
JsonObject,
|
|
39
39
|
GeneratorId,
|
|
40
40
|
ModelRef,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
import
|
|
41
|
+
AppGenerationProfileEffort,
|
|
42
|
+
} from "@ggui-ai/protocol";
|
|
43
|
+
import { isGeneratorId, modelRefOfRoute } from "@ggui-ai/protocol";
|
|
44
|
+
import type { GadgetCatalogAdapter } from "@ggui-ai/gadgets";
|
|
44
45
|
import type {
|
|
45
46
|
GenerationMetadata,
|
|
47
|
+
GenerationRuntimeProbe,
|
|
48
|
+
GenerationRuntimeProbeOutcome,
|
|
49
|
+
GenerationRuntimeProbeRepair,
|
|
46
50
|
GeneratorTier,
|
|
47
51
|
LlmProvider,
|
|
48
52
|
UiGenerateInput,
|
|
49
53
|
UiGenerateResult,
|
|
50
54
|
UiGenerator,
|
|
51
|
-
} from
|
|
55
|
+
} from "@ggui-ai/mcp-server-core";
|
|
52
56
|
import {
|
|
53
57
|
formatGeneratorSlug,
|
|
54
58
|
isValidGeneratorSlug,
|
|
55
59
|
parseGeneratorSlug,
|
|
56
|
-
} from
|
|
57
|
-
import { createGeneratorTools } from
|
|
58
|
-
import { dispatchGeneration } from
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
import
|
|
60
|
+
} from "@ggui-ai/mcp-server-core";
|
|
61
|
+
import { createGeneratorTools } from "./adapters/index.js";
|
|
62
|
+
import { dispatchGeneration, type VisualEvalAgentSpec } from "./adapters/generation-dispatch.js";
|
|
63
|
+
import {
|
|
64
|
+
createRuntimeRenderCheck,
|
|
65
|
+
type RuntimeRenderProbeConfig,
|
|
66
|
+
} from "./harness/check/runtime-render/adapter.js";
|
|
67
|
+
import { generatorBuild } from "./generator-build.js";
|
|
68
|
+
import { hasProfile } from "./boilerplate/styling-profile.js";
|
|
69
|
+
import { effortDials } from "./effort.js";
|
|
70
|
+
import type { ProviderName } from "./adapters/types.js";
|
|
71
|
+
import type { GenerationResult } from "./harness/result-types.js";
|
|
64
72
|
import {
|
|
65
73
|
canvasForRenderingContext,
|
|
66
74
|
injectContracts,
|
|
67
75
|
injectRenderingContext,
|
|
68
76
|
injectVariance,
|
|
69
|
-
} from
|
|
70
|
-
import type { RenderingContext } from
|
|
71
|
-
import type { DesignMode } from
|
|
72
|
-
import { DEFAULT_DESIGN_MODE } from
|
|
73
|
-
import { resolveRoute, applyRouteToEnv } from
|
|
74
|
-
import type { QualityConfig } from
|
|
75
|
-
import type { EvaluationConfig } from
|
|
76
|
-
import type { AgentConfig } from
|
|
77
|
+
} from "./contract-context.js";
|
|
78
|
+
import type { RenderingContext } from "./contract-context.js";
|
|
79
|
+
import type { DesignMode } from "./design-mode.js";
|
|
80
|
+
import { DEFAULT_DESIGN_MODE } from "./design-mode.js";
|
|
81
|
+
import { resolveRoute, applyRouteToEnv } from "./adapters/provider-router.js";
|
|
82
|
+
import type { QualityConfig, RuntimeProbeMeta, RuntimeProbeRepair } from "./evaluation/types-public.js";
|
|
83
|
+
import type { EvaluationConfig } from "./evaluation/types.js";
|
|
84
|
+
import type { AgentConfig } from "./harness/llm-router.js";
|
|
77
85
|
|
|
78
86
|
/** The slug for the OSS default seed generator. */
|
|
79
|
-
const DEFAULT_TIER: GeneratorTier =
|
|
80
|
-
const DEFAULT_MODEL: ModelRef =
|
|
87
|
+
const DEFAULT_TIER: GeneratorTier = "default";
|
|
88
|
+
const DEFAULT_MODEL: ModelRef = "anthropic/claude-haiku-4-5";
|
|
81
89
|
|
|
82
90
|
export interface CreateUiGeneratorOptions {
|
|
83
91
|
/**
|
|
84
|
-
* Wire
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* Wire the runtime-render check into the harness's check leg — one
|
|
93
|
+
* instance per generator, built from `runtimeRenderProbe` (the default
|
|
94
|
+
* bounds when it is absent). When `true`, the runtime-render probe runs
|
|
95
|
+
* ONCE at the exit decision,
|
|
96
|
+
* after the coding turns, in an isolated subprocess: it mounts the
|
|
97
|
+
* compiled component against the contract and catches crash-class and
|
|
98
|
+
* contract-wiring bugs (a render that throws, a missing `useAction()`,
|
|
99
|
+
* a wrong stream channel name). It is never on the per-turn hot path.
|
|
100
|
+
*
|
|
101
|
+
* Without an `evaluation` config the round is observe + one repair turn
|
|
102
|
+
* (ggui#1380): a recoverable render crash buys exactly one repair turn
|
|
103
|
+
* and one re-probe, then the generation serves what it has; a probe that
|
|
104
|
+
* timed out, could not run, or had nothing to probe never blocks and
|
|
105
|
+
* never repairs. The probe's status and timing ride the result's
|
|
106
|
+
* metadata (`runtimeProbe`, `evalMs`). With an `evaluation` config the
|
|
107
|
+
* probe is the evaluation round's exit gate instead.
|
|
88
108
|
*
|
|
89
|
-
* Default: `false`
|
|
90
|
-
*
|
|
91
|
-
*
|
|
109
|
+
* Default: `false` — keeps cold-start light. The probe pulls happy-dom
|
|
110
|
+
* + @testing-library on first use (~700-1500ms cold). A serving
|
|
111
|
+
* deployment that wants the probe's verdict on every card sets it.
|
|
92
112
|
*/
|
|
93
113
|
readonly enableRuntimeRender?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* How the runtime-render probe runs its isolated worker (ggui#1380). A
|
|
116
|
+
* serving deployment sets the probe's wall-clock bound, worker heap and
|
|
117
|
+
* concurrency here; the defaults are the evaluation lane's (30 s, 512 MB,
|
|
118
|
+
* no concurrency cap). The generator builds ONE check from it and hands
|
|
119
|
+
* that instance to every generation it dispatches — the concurrency cap
|
|
120
|
+
* lives on the instance, so it bounds the workers this generator has
|
|
121
|
+
* live at once. Only read when `enableRuntimeRender` is on. Never part
|
|
122
|
+
* of the prompt or the identity.
|
|
123
|
+
*/
|
|
124
|
+
readonly runtimeRenderProbe?: RuntimeRenderProbeConfig;
|
|
94
125
|
/** Maximum coding attempts per generation pass. */
|
|
95
126
|
readonly maxAttempts?: number;
|
|
96
127
|
/** Maximum evaluation rounds. */
|
|
@@ -108,7 +139,14 @@ export interface CreateUiGeneratorOptions {
|
|
|
108
139
|
* stylesheet to render under). Forwarded verbatim; never part of the
|
|
109
140
|
* prompt or the identity. Default: none — the round runs as today.
|
|
110
141
|
*/
|
|
111
|
-
readonly visualEvaluation?: SingleComponentParams[
|
|
142
|
+
readonly visualEvaluation?: SingleComponentParams["visualEvaluation"];
|
|
143
|
+
/**
|
|
144
|
+
* ggui#1250 — the in-loop VISUAL judge as its own agent (a vision provider, a model, and that
|
|
145
|
+
* provider's key when the caller resolves keys itself). Absent (default): the judge is the
|
|
146
|
+
* evaluation agent's provider, which on a lane without a vision path records the score half as
|
|
147
|
+
* skipped (#1248). Forwarded verbatim to the dispatch; never part of the prompt or the identity.
|
|
148
|
+
*/
|
|
149
|
+
readonly visualEvalAgent?: VisualEvalAgentSpec;
|
|
112
150
|
/** Quality config controlling eval tiers + improvement behavior. */
|
|
113
151
|
readonly qualityConfig?: QualityConfig;
|
|
114
152
|
/**
|
|
@@ -189,7 +227,7 @@ export interface CreateUiGeneratorOptions {
|
|
|
189
227
|
* request. Absent (default) is a no-op — retries still happen and
|
|
190
228
|
* still log via `console.warn`, just without a caller-side hook.
|
|
191
229
|
*/
|
|
192
|
-
readonly onRetry?: AgentConfig[
|
|
230
|
+
readonly onRetry?: AgentConfig["onRetry"];
|
|
193
231
|
/**
|
|
194
232
|
* Generation observer. Invoked once per successful `generate()` with
|
|
195
233
|
* the FULL harness result — source, compiled code, token counters,
|
|
@@ -221,15 +259,15 @@ export interface CreateUiGeneratorOptions {
|
|
|
221
259
|
readonly designMode?: DesignMode;
|
|
222
260
|
}
|
|
223
261
|
|
|
224
|
-
export function createUiGenerator(
|
|
225
|
-
options: CreateUiGeneratorOptions = {},
|
|
226
|
-
): UiGenerator {
|
|
262
|
+
export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGenerator {
|
|
227
263
|
const {
|
|
228
264
|
enableRuntimeRender = false,
|
|
265
|
+
runtimeRenderProbe,
|
|
229
266
|
maxAttempts,
|
|
230
267
|
maxEvalRounds,
|
|
231
268
|
evaluation,
|
|
232
269
|
visualEvaluation,
|
|
270
|
+
visualEvalAgent,
|
|
233
271
|
qualityConfig,
|
|
234
272
|
gadgetCatalog,
|
|
235
273
|
disableEnvMutation = false,
|
|
@@ -237,6 +275,11 @@ export function createUiGenerator(
|
|
|
237
275
|
onGenerated,
|
|
238
276
|
designMode,
|
|
239
277
|
} = options;
|
|
278
|
+
// ggui#1380 — ONE runtime-render check per generator: the instance owns
|
|
279
|
+
// the probe's concurrency cap, so it must outlive a single generation.
|
|
280
|
+
const runtimeRender = enableRuntimeRender
|
|
281
|
+
? createRuntimeRenderCheck(runtimeRenderProbe)
|
|
282
|
+
: undefined;
|
|
240
283
|
|
|
241
284
|
const identity = resolveIdentity(options);
|
|
242
285
|
|
|
@@ -273,9 +316,9 @@ export function createUiGenerator(
|
|
|
273
316
|
});
|
|
274
317
|
} catch (err) {
|
|
275
318
|
return failWithoutMetadata(input, identity.slug, startedAt, {
|
|
276
|
-
code:
|
|
319
|
+
code: "PRODUCTION_FAILED",
|
|
277
320
|
message: err instanceof Error ? err.message : String(err),
|
|
278
|
-
details: { kind:
|
|
321
|
+
details: { kind: "route-resolution-failed" },
|
|
279
322
|
});
|
|
280
323
|
}
|
|
281
324
|
|
|
@@ -307,19 +350,22 @@ export function createUiGenerator(
|
|
|
307
350
|
// flag is the only way the decision reaches `createAgent` /
|
|
308
351
|
// `createVisionAgent` (which select the login client on it).
|
|
309
352
|
const routeOverride =
|
|
310
|
-
route.auth ===
|
|
353
|
+
route.auth === "claude-code-login"
|
|
311
354
|
? { claudeCodeLogin: true }
|
|
312
355
|
: disableEnvMutation
|
|
313
356
|
? {
|
|
314
357
|
apiKey:
|
|
315
|
-
route.env[
|
|
316
|
-
route.env[
|
|
317
|
-
route.env[
|
|
318
|
-
route.env[
|
|
319
|
-
useBedrock: route.env[
|
|
358
|
+
route.env["ANTHROPIC_API_KEY"] ??
|
|
359
|
+
route.env["OPENAI_API_KEY"] ??
|
|
360
|
+
route.env["GEMINI_API_KEY"] ??
|
|
361
|
+
route.env["OPENROUTER_API_KEY"],
|
|
362
|
+
useBedrock: route.env["CLAUDE_CODE_USE_BEDROCK"] === "1",
|
|
320
363
|
}
|
|
321
364
|
: undefined;
|
|
322
365
|
|
|
366
|
+
// ggui#1459 — the named level whose dials this generation applied; set
|
|
367
|
+
// where the dials are resolved, read by both metadata arms below.
|
|
368
|
+
let effortRan: AppGenerationProfileEffort | undefined;
|
|
323
369
|
try {
|
|
324
370
|
const tools = createGeneratorTools({
|
|
325
371
|
...(input.contract ? { contract: input.contract } : {}),
|
|
@@ -342,11 +388,9 @@ export function createUiGenerator(
|
|
|
342
388
|
// descriptors stay byte-identical).
|
|
343
389
|
const canvas = rendering ? canvasForRenderingContext(rendering) : undefined;
|
|
344
390
|
const shellType = harnessShellForRendering(rendering);
|
|
345
|
-
const promptWithVariance = injectVariance(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{ profileDeclared: hasProfile(input.profile) },
|
|
349
|
-
);
|
|
391
|
+
const promptWithVariance = injectVariance(promptWithRendering, input.variance, {
|
|
392
|
+
profileDeclared: hasProfile(input.profile),
|
|
393
|
+
});
|
|
350
394
|
|
|
351
395
|
// Resolve appGadgets by precedence:
|
|
352
396
|
// 1. input.appGadgets (caller pre-fetched, handler path)
|
|
@@ -359,23 +403,22 @@ export function createUiGenerator(
|
|
|
359
403
|
input.appGadgets !== undefined
|
|
360
404
|
? input.appGadgets
|
|
361
405
|
: gadgetCatalog !== undefined && input.appId !== undefined
|
|
362
|
-
|
|
363
|
-
|
|
406
|
+
? await gadgetCatalog.list(input.appId)
|
|
407
|
+
: undefined;
|
|
364
408
|
|
|
365
|
-
const userPrompt = injectContracts(
|
|
366
|
-
promptWithVariance,
|
|
367
|
-
input.contract,
|
|
368
|
-
resolvedAppGadgets,
|
|
369
|
-
);
|
|
409
|
+
const userPrompt = injectContracts(promptWithVariance, input.contract, resolvedAppGadgets);
|
|
370
410
|
|
|
371
411
|
// `effort` (ggui#1059): a named level's dials override the
|
|
372
412
|
// deployment's options for THIS generation; absent ⇒ untouched.
|
|
373
413
|
// Prompt bytes never move with it (dials only).
|
|
374
414
|
const dials = effortDials(input.profile?.effort);
|
|
415
|
+
if (dials !== undefined) effortRan = input.profile?.effort;
|
|
375
416
|
const effectiveMaxAttempts = dials?.maxAttempts ?? maxAttempts;
|
|
376
417
|
const effectiveMaxEvalRounds = dials?.maxEvalRounds ?? maxEvalRounds;
|
|
377
418
|
const effectiveEvaluation =
|
|
378
|
-
dials !== undefined && evaluation !== undefined
|
|
419
|
+
dials !== undefined && evaluation !== undefined
|
|
420
|
+
? { ...evaluation, maxRounds: dials.maxEvalRounds }
|
|
421
|
+
: evaluation;
|
|
379
422
|
const effectiveVisualEvaluation =
|
|
380
423
|
dials !== undefined && visualEvaluation !== undefined
|
|
381
424
|
? { ...visualEvaluation, passThreshold: dials.selfEvalPassThreshold }
|
|
@@ -389,21 +432,22 @@ export function createUiGenerator(
|
|
|
389
432
|
...(input.contract ? { contract: input.contract } : {}),
|
|
390
433
|
originalPrompt: input.request.prompt,
|
|
391
434
|
...(effectiveMaxAttempts !== undefined ? { maxAttempts: effectiveMaxAttempts } : {}),
|
|
392
|
-
...(effectiveMaxEvalRounds !== undefined
|
|
435
|
+
...(effectiveMaxEvalRounds !== undefined
|
|
436
|
+
? { maxEvalRounds: effectiveMaxEvalRounds }
|
|
437
|
+
: {}),
|
|
393
438
|
...(effectiveEvaluation !== undefined ? { evaluation: effectiveEvaluation } : {}),
|
|
394
|
-
...(effectiveVisualEvaluation !== undefined
|
|
395
|
-
|
|
396
|
-
...(resolvedAppGadgets !== undefined
|
|
397
|
-
? { appGadgets: resolvedAppGadgets }
|
|
439
|
+
...(effectiveVisualEvaluation !== undefined
|
|
440
|
+
? { visualEvaluation: effectiveVisualEvaluation }
|
|
398
441
|
: {}),
|
|
442
|
+
...(qualityConfig !== undefined ? { qualityConfig } : {}),
|
|
443
|
+
...(resolvedAppGadgets !== undefined ? { appGadgets: resolvedAppGadgets } : {}),
|
|
399
444
|
// Forward the third-party wrapper `.d.ts` map the render
|
|
400
445
|
// handler pre-fetched. Reaches both the code-gen prompt's
|
|
401
446
|
// `Type:` lines and the coding-agent typecheck overlay via
|
|
402
447
|
// `dispatchGeneration`.
|
|
403
|
-
...(input.gadgetTypes !== undefined
|
|
404
|
-
? { gadgetTypes: input.gadgetTypes }
|
|
405
|
-
: {}),
|
|
448
|
+
...(input.gadgetTypes !== undefined ? { gadgetTypes: input.gadgetTypes } : {}),
|
|
406
449
|
enableRuntimeRender,
|
|
450
|
+
...(runtimeRender !== undefined ? { runtimeRender } : {}),
|
|
407
451
|
...(routeOverride !== undefined ? { routeOverride } : {}),
|
|
408
452
|
...(onRetry !== undefined ? { onRetry } : {}),
|
|
409
453
|
...(designMode !== undefined ? { designMode } : {}),
|
|
@@ -411,12 +455,16 @@ export function createUiGenerator(
|
|
|
411
455
|
// ggui#1279 — the shell the declared rendering composes for (chat only; see harnessShellForRendering).
|
|
412
456
|
...(shellType !== undefined ? { shellType } : {}),
|
|
413
457
|
...(input.profile !== undefined ? { profile: input.profile } : {}),
|
|
458
|
+
// ggui#1250 — the named in-loop visual judge, when this generator was built with one.
|
|
459
|
+
...(visualEvalAgent !== undefined ? { visualEvalAgent } : {}),
|
|
414
460
|
// Fixture props reach the in-loop probe + visual round only (never the prompt, never identity).
|
|
415
461
|
...(input.fixtureProps !== undefined ? { fixtureProps: input.fixtureProps } : {}),
|
|
416
462
|
});
|
|
417
463
|
|
|
418
464
|
onGenerated?.(result);
|
|
419
465
|
|
|
466
|
+
const runtimeProbe = metadataRuntimeProbe(result);
|
|
467
|
+
const evalMs = metadataEvalMs(result);
|
|
420
468
|
const metadata: GenerationMetadata = {
|
|
421
469
|
provider: input.llm.provider,
|
|
422
470
|
generator: identity.slug,
|
|
@@ -428,6 +476,8 @@ export function createUiGenerator(
|
|
|
428
476
|
attempts: result.turnsUsed,
|
|
429
477
|
// ggui#1280 — the build that made this generation (template keys, never per-request).
|
|
430
478
|
build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
|
|
479
|
+
// ggui#1459 — the level that ran; absent when none was applied.
|
|
480
|
+
...(effortRan !== undefined ? { effort: effortRan } : {}),
|
|
431
481
|
// Prompt-cache counters are provider-specific (Claude reports
|
|
432
482
|
// them; others omit). Pass through truthfully — absent on the
|
|
433
483
|
// adapter result stays absent here, never defaulted to 0.
|
|
@@ -437,6 +487,10 @@ export function createUiGenerator(
|
|
|
437
487
|
...(result.cacheCreationTokens !== undefined
|
|
438
488
|
? { cacheCreationTokens: result.cacheCreationTokens }
|
|
439
489
|
: {}),
|
|
490
|
+
// ggui#1380 — the runtime probe's record and the eval rounds' wall,
|
|
491
|
+
// when the harness ran a round; absent stays absent.
|
|
492
|
+
...(runtimeProbe !== undefined ? { runtimeProbe } : {}),
|
|
493
|
+
...(evalMs !== undefined ? { evalMs } : {}),
|
|
440
494
|
};
|
|
441
495
|
|
|
442
496
|
return {
|
|
@@ -451,12 +505,12 @@ export function createUiGenerator(
|
|
|
451
505
|
};
|
|
452
506
|
} catch (err) {
|
|
453
507
|
const message = err instanceof Error ? err.message : String(err);
|
|
454
|
-
const details: JsonObject = { kind:
|
|
455
|
-
if (err instanceof Error && err.stack) details[
|
|
508
|
+
const details: JsonObject = { kind: "harness-failed" };
|
|
509
|
+
if (err instanceof Error && err.stack) details["stack"] = err.stack;
|
|
456
510
|
return {
|
|
457
511
|
ok: false,
|
|
458
512
|
error: {
|
|
459
|
-
code:
|
|
513
|
+
code: "PRODUCTION_FAILED",
|
|
460
514
|
message,
|
|
461
515
|
details,
|
|
462
516
|
},
|
|
@@ -470,6 +524,8 @@ export function createUiGenerator(
|
|
|
470
524
|
cacheHit: false,
|
|
471
525
|
// ggui#1280 — a failed generation still names the build that failed.
|
|
472
526
|
build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
|
|
527
|
+
// ggui#1459 — and the level it ran, when one was applied.
|
|
528
|
+
...(effortRan !== undefined ? { effort: effortRan } : {}),
|
|
473
529
|
},
|
|
474
530
|
};
|
|
475
531
|
} finally {
|
|
@@ -488,11 +544,9 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
488
544
|
tier: GeneratorTier;
|
|
489
545
|
model: ModelRef;
|
|
490
546
|
} {
|
|
491
|
-
const hasSlug = typeof opts.slug ===
|
|
547
|
+
const hasSlug = typeof opts.slug === "string" && opts.slug.length > 0;
|
|
492
548
|
if (hasSlug && opts.tier !== undefined) {
|
|
493
|
-
throw new Error(
|
|
494
|
-
'createUiGenerator: pass either { slug } or { tier } — not both.',
|
|
495
|
-
);
|
|
549
|
+
throw new Error("createUiGenerator: pass either { slug } or { tier } — not both.");
|
|
496
550
|
}
|
|
497
551
|
const model = opts.model ?? DEFAULT_MODEL;
|
|
498
552
|
if (hasSlug) {
|
|
@@ -500,7 +554,7 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
500
554
|
const parsed = parseGeneratorSlug(slug);
|
|
501
555
|
if (!parsed || !isGeneratorId(slug)) {
|
|
502
556
|
throw new Error(
|
|
503
|
-
`createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier
|
|
557
|
+
`createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier.`
|
|
504
558
|
);
|
|
505
559
|
}
|
|
506
560
|
return { slug, tier: parsed.tier, model };
|
|
@@ -512,23 +566,80 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
512
566
|
// so a slug it returns must parse. Defensive check kept to surface
|
|
513
567
|
// any future drift between the two helpers.
|
|
514
568
|
throw new Error(
|
|
515
|
-
`createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift
|
|
569
|
+
`createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift.`
|
|
516
570
|
);
|
|
517
571
|
}
|
|
518
572
|
return { slug, tier, model };
|
|
519
573
|
}
|
|
520
574
|
|
|
575
|
+
/**
|
|
576
|
+
* ggui#1380 — one probe's meta, projected onto the metadata's outcome ARM BY
|
|
577
|
+
* ARM: the outcome fields (`status`, `verdict`, `failChecks`, `elapsedMs`)
|
|
578
|
+
* are copied; the engine's own detail (`reason`, `renderMs`, `hostLoad`)
|
|
579
|
+
* stays on the harness result. No cast — each arm is rebuilt as a literal of
|
|
580
|
+
* the target arm, so a field the metadata does not carry cannot ride along
|
|
581
|
+
* and a field the arm requires cannot be dropped. Absent stays absent.
|
|
582
|
+
*/
|
|
583
|
+
function metadataProbeOutcome(meta: RuntimeProbeMeta): GenerationRuntimeProbeOutcome {
|
|
584
|
+
const elapsed = {
|
|
585
|
+
...(meta.elapsedMs !== undefined ? { elapsedMs: meta.elapsedMs } : {}),
|
|
586
|
+
...(meta.queuedMs !== undefined ? { queuedMs: meta.queuedMs } : {}),
|
|
587
|
+
};
|
|
588
|
+
if (meta.status !== "ran") return { status: meta.status, ...elapsed };
|
|
589
|
+
if (meta.verdict === "pass") return { status: "ran", verdict: "pass", ...elapsed };
|
|
590
|
+
return { status: "ran", verdict: "fail", failChecks: meta.failChecks, ...elapsed };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* ggui#1380 — the repair record, arm by arm: `compiled: false` is carried as
|
|
595
|
+
* is (no re-probe, so no after); `compiled: true` carries the re-probe's meta
|
|
596
|
+
* projected with the same function as the record's own outcome.
|
|
597
|
+
*/
|
|
598
|
+
function metadataProbeRepair(repair: RuntimeProbeRepair): GenerationRuntimeProbeRepair {
|
|
599
|
+
return repair.compiled
|
|
600
|
+
? { attempted: true, compiled: true, trigger: metadataProbeOutcome(repair.trigger) }
|
|
601
|
+
: { attempted: true, compiled: false };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* ggui#1380 — the harness result's probe record, projected onto the metadata
|
|
606
|
+
* a host reads. The outcome fields are the LAST probe's (the re-probe's when
|
|
607
|
+
* the repair compiled, else the pre-repair probe's — exactly what the harness
|
|
608
|
+
* stamps on `evalResult.runtimeProbe`), and `repair` is present only when a
|
|
609
|
+
* repair turn was bought. Absent when no probe ran — never a default status.
|
|
610
|
+
*/
|
|
611
|
+
function metadataRuntimeProbe(result: GenerationResult): GenerationRuntimeProbe | undefined {
|
|
612
|
+
const probe = result.evalResult?.runtimeProbe;
|
|
613
|
+
if (probe === undefined) return undefined;
|
|
614
|
+
const repair = result.evalResult?.runtimeProbeRepair;
|
|
615
|
+
return {
|
|
616
|
+
...metadataProbeOutcome(probe),
|
|
617
|
+
...(repair !== undefined ? { repair: metadataProbeRepair(repair) } : {}),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* ggui#1380 — the eval rounds' wall-clock, present only when a round ran:
|
|
623
|
+
* a generation with no round has nothing to time, and the field stays
|
|
624
|
+
* absent rather than reporting a structural 0.
|
|
625
|
+
*/
|
|
626
|
+
function metadataEvalMs(result: GenerationResult): number | undefined {
|
|
627
|
+
const breakdown = result.breakdown;
|
|
628
|
+
if (breakdown === undefined || breakdown.evalRounds === 0) return undefined;
|
|
629
|
+
return breakdown.evalMs;
|
|
630
|
+
}
|
|
631
|
+
|
|
521
632
|
function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
|
|
522
633
|
switch (provider) {
|
|
523
|
-
case
|
|
524
|
-
case
|
|
525
|
-
return
|
|
526
|
-
case
|
|
527
|
-
return
|
|
528
|
-
case
|
|
529
|
-
return
|
|
530
|
-
case
|
|
531
|
-
return
|
|
634
|
+
case "anthropic":
|
|
635
|
+
case "bedrock":
|
|
636
|
+
return "claude";
|
|
637
|
+
case "openai":
|
|
638
|
+
return "openai";
|
|
639
|
+
case "google":
|
|
640
|
+
return "google";
|
|
641
|
+
case "openrouter":
|
|
642
|
+
return "openrouter";
|
|
532
643
|
}
|
|
533
644
|
}
|
|
534
645
|
|
|
@@ -542,20 +653,18 @@ function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
|
|
|
542
653
|
* yet, so both pass no shell and those generations stay byte-identical — named
|
|
543
654
|
* here rather than guessed.
|
|
544
655
|
*/
|
|
545
|
-
function harnessShellForRendering(rendering: RenderingContext | undefined):
|
|
656
|
+
function harnessShellForRendering(rendering: RenderingContext | undefined): "chat" | undefined {
|
|
546
657
|
if (rendering === undefined) return undefined;
|
|
547
658
|
switch (rendering.shell) {
|
|
548
|
-
case
|
|
549
|
-
return
|
|
550
|
-
case
|
|
551
|
-
case
|
|
659
|
+
case "chat":
|
|
660
|
+
return "chat";
|
|
661
|
+
case "fullscreen":
|
|
662
|
+
case "partial":
|
|
552
663
|
return undefined;
|
|
553
664
|
}
|
|
554
665
|
}
|
|
555
666
|
|
|
556
|
-
function mapRendering(
|
|
557
|
-
rendering: UiGenerateInput['rendering'],
|
|
558
|
-
): RenderingContext | undefined {
|
|
667
|
+
function mapRendering(rendering: UiGenerateInput["rendering"]): RenderingContext | undefined {
|
|
559
668
|
if (!rendering) return undefined;
|
|
560
669
|
return {
|
|
561
670
|
device: rendering.device,
|
|
@@ -568,7 +677,7 @@ function failWithoutMetadata(
|
|
|
568
677
|
input: UiGenerateInput,
|
|
569
678
|
generatorSlug: GeneratorId,
|
|
570
679
|
startedAt: number,
|
|
571
|
-
error: GenerationError
|
|
680
|
+
error: GenerationError
|
|
572
681
|
): UiGenerateResult {
|
|
573
682
|
return {
|
|
574
683
|
ok: false,
|
|
@@ -600,18 +709,18 @@ export function extractComponentCode(raw: string): string {
|
|
|
600
709
|
const fences: Array<{ lang: string; body: string }> = [];
|
|
601
710
|
let match: RegExpExecArray | null;
|
|
602
711
|
while ((match = fencePattern.exec(raw)) !== null) {
|
|
603
|
-
const lang = (match[1] ??
|
|
604
|
-
const body = match[2] ??
|
|
712
|
+
const lang = (match[1] ?? "").toLowerCase();
|
|
713
|
+
const body = match[2] ?? "";
|
|
605
714
|
fences.push({ lang, body });
|
|
606
715
|
}
|
|
607
716
|
|
|
608
717
|
if (fences.length > 0) {
|
|
609
|
-
const priority = [
|
|
718
|
+
const priority = ["tsx", "jsx", "typescript", "ts", "javascript", "js", ""];
|
|
610
719
|
for (const preferred of priority) {
|
|
611
720
|
const hit = fences.find((f) => f.lang === preferred);
|
|
612
721
|
if (hit) return hit.body.trim();
|
|
613
722
|
}
|
|
614
|
-
const firstBody = fences[0]?.body ??
|
|
723
|
+
const firstBody = fences[0]?.body ?? "";
|
|
615
724
|
return firstBody.trim();
|
|
616
725
|
}
|
|
617
726
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A PNG writer for tests (ggui#1120): 8-bit RGB / RGBA, non-interlaced, one scanline filter for the
|
|
3
|
+
* whole image — the shapes the judge's browser writes, so `decodePng` is exercised on every filter it
|
|
4
|
+
* must undo. Test-only: nothing in `src` imports it.
|
|
5
|
+
*/
|
|
6
|
+
import { crc32, deflateSync } from 'node:zlib';
|
|
7
|
+
|
|
8
|
+
export type Rgb = readonly [number, number, number];
|
|
9
|
+
|
|
10
|
+
export interface PngSpec {
|
|
11
|
+
readonly width: number;
|
|
12
|
+
readonly height: number;
|
|
13
|
+
readonly channels: 3 | 4;
|
|
14
|
+
/** The colour at (x, y). */
|
|
15
|
+
readonly pixel: (x: number, y: number) => Rgb;
|
|
16
|
+
/** The scanline filter applied to every row (0 none, 1 sub, 2 up, 3 average, 4 paeth). */
|
|
17
|
+
readonly filter?: 0 | 1 | 2 | 3 | 4;
|
|
18
|
+
/** Overrides for the malformed cases. */
|
|
19
|
+
readonly bitDepth?: number;
|
|
20
|
+
readonly colorType?: number;
|
|
21
|
+
readonly interlace?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function chunk(type: string, data: Buffer): Buffer {
|
|
25
|
+
const head = Buffer.alloc(8);
|
|
26
|
+
head.writeUInt32BE(data.length, 0);
|
|
27
|
+
head.write(type, 4, 'latin1');
|
|
28
|
+
const crc = Buffer.alloc(4);
|
|
29
|
+
crc.writeUInt32BE(crc32(data, crc32(Buffer.from(type, 'latin1'))) >>> 0, 0);
|
|
30
|
+
return Buffer.concat([head, data, crc]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function encodePng(spec: PngSpec): Buffer {
|
|
34
|
+
const { width, height, channels } = spec;
|
|
35
|
+
const filter = spec.filter ?? 0;
|
|
36
|
+
const stride = width * channels;
|
|
37
|
+
const rows: number[][] = [];
|
|
38
|
+
for (let y = 0; y < height; y++) {
|
|
39
|
+
const row: number[] = [];
|
|
40
|
+
for (let x = 0; x < width; x++) {
|
|
41
|
+
const [r, g, b] = spec.pixel(x, y);
|
|
42
|
+
row.push(r, g, b);
|
|
43
|
+
if (channels === 4) row.push(255);
|
|
44
|
+
}
|
|
45
|
+
rows.push(row);
|
|
46
|
+
}
|
|
47
|
+
const raw = Buffer.alloc((stride + 1) * height);
|
|
48
|
+
for (let y = 0; y < height; y++) {
|
|
49
|
+
raw[y * (stride + 1)] = filter;
|
|
50
|
+
for (let i = 0; i < stride; i++) {
|
|
51
|
+
const x = rows[y]![i]!;
|
|
52
|
+
const a = i >= channels ? rows[y]![i - channels]! : 0;
|
|
53
|
+
const b = y > 0 ? rows[y - 1]![i]! : 0;
|
|
54
|
+
const c = y > 0 && i >= channels ? rows[y - 1]![i - channels]! : 0;
|
|
55
|
+
let out: number;
|
|
56
|
+
switch (filter) {
|
|
57
|
+
case 0:
|
|
58
|
+
out = x;
|
|
59
|
+
break;
|
|
60
|
+
case 1:
|
|
61
|
+
out = x - a;
|
|
62
|
+
break;
|
|
63
|
+
case 2:
|
|
64
|
+
out = x - b;
|
|
65
|
+
break;
|
|
66
|
+
case 3:
|
|
67
|
+
out = x - ((a + b) >> 1);
|
|
68
|
+
break;
|
|
69
|
+
case 4: {
|
|
70
|
+
const p = a + b - c;
|
|
71
|
+
const pa = Math.abs(p - a);
|
|
72
|
+
const pb = Math.abs(p - b);
|
|
73
|
+
const pc = Math.abs(p - c);
|
|
74
|
+
out = x - (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
raw[y * (stride + 1) + 1 + i] = out & 0xff;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const ihdr = Buffer.alloc(13);
|
|
82
|
+
ihdr.writeUInt32BE(width, 0);
|
|
83
|
+
ihdr.writeUInt32BE(height, 4);
|
|
84
|
+
ihdr[8] = spec.bitDepth ?? 8;
|
|
85
|
+
ihdr[9] = spec.colorType ?? (channels === 4 ? 6 : 2);
|
|
86
|
+
ihdr[10] = 0;
|
|
87
|
+
ihdr[11] = 0;
|
|
88
|
+
ihdr[12] = spec.interlace ?? 0;
|
|
89
|
+
return Buffer.concat([
|
|
90
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
91
|
+
chunk('IHDR', ihdr),
|
|
92
|
+
chunk('IDAT', deflateSync(raw)),
|
|
93
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** One flat colour — the blank. */
|
|
98
|
+
export function flatPng(width: number, height: number, colour: Rgb, channels: 3 | 4 = 3, filter: PngSpec['filter'] = 0): Buffer {
|
|
99
|
+
return encodePng({ width, height, channels, pixel: () => colour, filter });
|
|
100
|
+
}
|
|
@@ -108,7 +108,7 @@ function runMapKeyIsId(input: AxisCheckInput): EvalIssue[] {
|
|
|
108
108
|
mkIssue(
|
|
109
109
|
"render.map_key_is_id",
|
|
110
110
|
`Array key uses index variable "${key}" — reorders and stream merges will break React reconciliation.`,
|
|
111
|
-
`Replace key={${key}} with key={item.id}
|
|
111
|
+
`Replace key={${key}} with key={item.id} — or, when the items carry no id, with the field that names each one (item.label / item.symbol / item.name).`,
|
|
112
112
|
),
|
|
113
113
|
);
|
|
114
114
|
}
|