@ggui-ai/ui-gen 0.20.0 → 0.21.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/adapters/base.d.ts +7 -0
- package/dist/adapters/base.js +1 -0
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +17 -3
- package/dist/adapters/generation-dispatch.js +959 -3484
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +35 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +4 -4
- package/dist/advanced/index.js +1039 -3573
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +10 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +2 -2
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-CxPvudbs.d.ts → create-ui-generator-D7kOXNjJ.d.ts} +2 -2
- package/dist/{dispatch-N8wdNOX-.d.ts → dispatch-DGvhmARp.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/index.d.ts +38 -6
- package/dist/evaluation/index.js +649 -3370
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +10 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +5 -3
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.js +562 -30
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +7 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +11 -2
- package/dist/harness/runtime.js +3 -3
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +2 -2
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1039 -3573
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-D7fjBhtr.d.ts → llm-router-DwwUK1bz.d.ts} +16 -3
- package/dist/patch.d.ts +1 -1
- package/dist/providers/index.js +5 -4
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-WfOVO9kU.d.ts → render-check-C0CqVceF.d.ts} +17 -0
- package/dist/{types-public-DKR_h70H.d.ts → types-public-CzYqZclY.d.ts} +44 -1
- 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 +1 -1
- package/package.json +16 -12
- package/src/adapters/base.ts +8 -0
- package/src/adapters/claude/claude-code-login.ts +73 -0
- package/src/adapters/claude/sdk.ts +8 -1
- package/src/adapters/generation-dispatch.ts +19 -2
- package/src/adapters/openai/tokens.ts +18 -0
- package/src/adapters/provider-router.ts +28 -0
- package/src/boilerplate/generate.ts +19 -4
- package/src/boilerplate/hard-sections.ts +1 -1
- package/src/create-ui-generator.ts +19 -10
- package/src/evaluation/design-identity.ts +16 -5
- package/src/evaluation/types-public.ts +42 -0
- package/src/evaluation/visual-evaluator.ts +231 -24
- package/src/harness/check/runtime-render/adapter.ts +9 -0
- package/src/harness/check/runtime-render/render-check.ts +26 -5
- package/src/harness/claude-code-login-query.ts +528 -0
- package/src/harness/coding/assemble-result.ts +10 -1
- package/src/harness/coding/run-eval-round.ts +179 -34
- package/src/harness/enforced-coding.ts +20 -0
- package/src/harness/llm-router.ts +390 -55
- package/src/harness/llm-trace-sink.ts +7 -1
- package/src/harness/runtime.ts +6 -0
- package/src/providers/openai.ts +31 -19
- package/src/validation/primitives.ts +1 -1
package/dist/workflows.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { C as Classification } from './axes-CzLEMDeB.js';
|
|
|
2
2
|
import { Workflow } from './harness/types-public.js';
|
|
3
3
|
import '@ggui-ai/protocol';
|
|
4
4
|
import './styling-profile-CxfvV48g.js';
|
|
5
|
-
import './types-public-
|
|
5
|
+
import './types-public-CzYqZclY.js';
|
|
6
6
|
import './design-mode-DLufhrNp.js';
|
|
7
7
|
import './types-BOvHNG7K.js';
|
|
8
8
|
import './llm.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/ui-gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "UI generation harness for ggui — the LLM system prompt, multi-turn coding loop, and evaluation engine that turn a DataContract into a React component. Implements the UiGenerator contract from @ggui-ai/mcp-server-core; consumed by @ggui-ai/mcp-server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -185,12 +185,13 @@
|
|
|
185
185
|
"peerDependencies": {
|
|
186
186
|
"@google/adk": "^2.0.0",
|
|
187
187
|
"@openai/agents": "^0.17.0",
|
|
188
|
+
"@sparticuz/chromium": "^149.0.0",
|
|
188
189
|
"puppeteer-core": "^24.40.0 || ^25.0.0",
|
|
189
190
|
"react": "^18.0.0 || ^19.0.0",
|
|
190
191
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
191
|
-
"@ggui-ai/
|
|
192
|
-
"@ggui-ai/mcp-server-core": "0.
|
|
193
|
-
"@ggui-ai/
|
|
192
|
+
"@ggui-ai/protocol": "0.21.0",
|
|
193
|
+
"@ggui-ai/mcp-server-core": "0.21.0",
|
|
194
|
+
"@ggui-ai/project-config": "0.21.0"
|
|
194
195
|
},
|
|
195
196
|
"peerDependenciesMeta": {
|
|
196
197
|
"@google/adk": {
|
|
@@ -199,6 +200,9 @@
|
|
|
199
200
|
"@openai/agents": {
|
|
200
201
|
"optional": true
|
|
201
202
|
},
|
|
203
|
+
"@sparticuz/chromium": {
|
|
204
|
+
"optional": true
|
|
205
|
+
},
|
|
202
206
|
"puppeteer-core": {
|
|
203
207
|
"optional": true
|
|
204
208
|
},
|
|
@@ -229,11 +233,11 @@
|
|
|
229
233
|
"openai": "^7.9.0",
|
|
230
234
|
"typescript": "^5.0.0",
|
|
231
235
|
"zod": "^4.3.6",
|
|
232
|
-
"@ggui-ai/
|
|
233
|
-
"@ggui-ai/
|
|
234
|
-
"@ggui-ai/sandbox": "0.
|
|
235
|
-
"@ggui-ai/ui-visual-tester": "0.
|
|
236
|
-
"@ggui-ai/wire": "0.
|
|
236
|
+
"@ggui-ai/design": "0.21.0",
|
|
237
|
+
"@ggui-ai/gadgets": "0.21.0",
|
|
238
|
+
"@ggui-ai/sandbox": "0.21.0",
|
|
239
|
+
"@ggui-ai/ui-visual-tester": "0.21.0",
|
|
240
|
+
"@ggui-ai/wire": "0.21.0"
|
|
237
241
|
},
|
|
238
242
|
"devDependencies": {
|
|
239
243
|
"@google/adk": "^2.0.0",
|
|
@@ -248,9 +252,9 @@
|
|
|
248
252
|
"react-dom": "^19.0.0",
|
|
249
253
|
"tsup": "^8.5.0",
|
|
250
254
|
"vitest": "^3.2.6",
|
|
251
|
-
"@ggui-ai/
|
|
252
|
-
"@ggui-ai/
|
|
253
|
-
"@ggui-ai/
|
|
255
|
+
"@ggui-ai/mcp-server-core": "0.21.0",
|
|
256
|
+
"@ggui-ai/project-config": "0.21.0",
|
|
257
|
+
"@ggui-ai/protocol": "0.21.0"
|
|
254
258
|
},
|
|
255
259
|
"engines": {
|
|
256
260
|
"node": ">=22.0.0"
|
package/src/adapters/base.ts
CHANGED
|
@@ -14,6 +14,13 @@ export interface AdapterConfig {
|
|
|
14
14
|
apiKey?: string;
|
|
15
15
|
/** Use Bedrock instead of direct API (Claude only) */
|
|
16
16
|
useBedrock?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* ggui#1185 — run on the machine's own Claude Code login instead of a key.
|
|
19
|
+
* Claude SDK adapter only. The adapter strips every provider key from the
|
|
20
|
+
* spawned env and pins the run tool-less, config-less and non-bare (see
|
|
21
|
+
* `claude/claude-code-login.ts`). Opt-in; absent ⇒ key-based auth.
|
|
22
|
+
*/
|
|
23
|
+
claudeCodeLogin?: boolean;
|
|
17
24
|
/**
|
|
18
25
|
* Thinking level for models with extended thinking (Gemini 3.x Flash
|
|
19
26
|
* via the Interactions API). Mirrors the API's `thinking_level` enum —
|
|
@@ -129,5 +136,6 @@ export function hasCredentials(
|
|
|
129
136
|
): boolean {
|
|
130
137
|
if (config.apiKey) return true;
|
|
131
138
|
if (config.useBedrock) return true;
|
|
139
|
+
if (config.claudeCodeLogin) return true;
|
|
132
140
|
return envVarNames.some((name) => !!process.env[name]);
|
|
133
141
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ggui#1185 — generation through the machine's own Claude Code login.
|
|
3
|
+
*
|
|
4
|
+
* The Claude SDK adapter spawns the Claude Code binary. Given no provider
|
|
5
|
+
* key, that binary authenticates with whatever `claude` is logged in as on
|
|
6
|
+
* the machine — the operator's own plan. This module holds the three
|
|
7
|
+
* things that make such a run honest and reproducible:
|
|
8
|
+
*
|
|
9
|
+
* 1. {@link CLAUDE_CODE_LOGIN_CREDENTIAL} — the sentinel a `ProviderKeyRef`
|
|
10
|
+
* carries in place of a key, the same shape `bedrock-iam` already uses
|
|
11
|
+
* for "authenticate with ambient credentials, not a key I hold".
|
|
12
|
+
* 2. {@link stripProviderKeyEnv} — no provider key may reach the spawned
|
|
13
|
+
* process: the binary prefers an env key over its login, so a stale
|
|
14
|
+
* `ANTHROPIC_API_KEY` would silently take the run off the login path.
|
|
15
|
+
* 3. {@link claudeCodeLoginQueryOptions} — the SDK options that keep the
|
|
16
|
+
* run bounded: no built-in tool (`tools: []`), no `~/.claude` settings,
|
|
17
|
+
* hooks or MCP servers leaking in (`settingSources: []`). Not
|
|
18
|
+
* `--bare`, either: on the SDK's bundled binary `-p` reads the login
|
|
19
|
+
* store as it always has, so no flag is needed — and the flag it
|
|
20
|
+
* would be (`--no-bare`) is one that binary rejects; see the note on
|
|
21
|
+
* the function below.
|
|
22
|
+
*
|
|
23
|
+
* Opt-in, off by default; the default stays a provider key. The copy that
|
|
24
|
+
* names what this does lives with the CLI flag that enables it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
|
28
|
+
|
|
29
|
+
/** Sentinel credential: "no key — use the machine's Claude Code login". */
|
|
30
|
+
export const CLAUDE_CODE_LOGIN_CREDENTIAL = "claude-code-login" as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Every env name the spawned binary would read as a credential or an
|
|
34
|
+
* endpoint override ahead of its login. Stripped whole, never rewritten.
|
|
35
|
+
*/
|
|
36
|
+
export const PROVIDER_KEY_ENV_NAMES = [
|
|
37
|
+
"ANTHROPIC_API_KEY",
|
|
38
|
+
"CLAUDE_API_KEY",
|
|
39
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
40
|
+
"ANTHROPIC_BASE_URL",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
/** A copy of `env` without any provider key or endpoint override. */
|
|
44
|
+
export function stripProviderKeyEnv(
|
|
45
|
+
env: Readonly<Record<string, string | undefined>>
|
|
46
|
+
): Record<string, string> {
|
|
47
|
+
const out: Record<string, string> = {};
|
|
48
|
+
const drop: ReadonlySet<string> = new Set(PROVIDER_KEY_ENV_NAMES);
|
|
49
|
+
for (const [k, v] of Object.entries(env)) {
|
|
50
|
+
if (v !== undefined && !drop.has(k)) out[k] = v;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The SDK `query()` options a login-path run is pinned to — typed as the
|
|
57
|
+
* SDK's own `Options` members, so a change in the SDK's shape is a compile
|
|
58
|
+
* error here and never a silently ignored key.
|
|
59
|
+
*
|
|
60
|
+
* NOT pinned: `--no-bare`. The SDK spawns its BUNDLED binary (2.1.229 for
|
|
61
|
+
* SDK 0.3.229), which has no bare mode and rejects the flag outright
|
|
62
|
+
* (`error: unknown option '--no-bare'`, measured 2026-09-21 on the first
|
|
63
|
+
* live turn of the login client); the machine's own `claude` (2.1.274)
|
|
64
|
+
* accepts it, which is where the earlier probe measured it. On the
|
|
65
|
+
* bundled binary `-p` reads the login store as it always has. If a later
|
|
66
|
+
* SDK bundles a binary where bare becomes the `-p` default, the flag
|
|
67
|
+
* comes back here — with the SDK version bump, measured again.
|
|
68
|
+
*/
|
|
69
|
+
export type ClaudeCodeLoginQueryOptions = Required<Pick<Options, "tools" | "settingSources">>;
|
|
70
|
+
|
|
71
|
+
export function claudeCodeLoginQueryOptions(): ClaudeCodeLoginQueryOptions {
|
|
72
|
+
return { tools: [], settingSources: [] };
|
|
73
|
+
}
|
|
@@ -13,6 +13,7 @@ import type { ClaudeSdkConfig, GenerateParams } from '../base';
|
|
|
13
13
|
import type { AdapterResult, ProviderName, AdapterMode } from '../types';
|
|
14
14
|
import { createArtifacts, processMessage } from './message-parser';
|
|
15
15
|
import { createToolMcpServer } from './tool-bridge';
|
|
16
|
+
import { claudeCodeLoginQueryOptions, stripProviderKeyEnv } from './claude-code-login';
|
|
16
17
|
|
|
17
18
|
export class ClaudeSdkAdapter extends GeneratorAdapter {
|
|
18
19
|
readonly provider: ProviderName = 'claude';
|
|
@@ -40,7 +41,11 @@ export class ClaudeSdkAdapter extends GeneratorAdapter {
|
|
|
40
41
|
const { query } = await import('@anthropic-ai/claude-agent-sdk');
|
|
41
42
|
|
|
42
43
|
// Build environment from config or process.env
|
|
43
|
-
|
|
44
|
+
// ggui#1185 — on the login path no provider key may reach the binary:
|
|
45
|
+
// it prefers an env key over its login, so a stale one would take the
|
|
46
|
+
// run off this path without a word.
|
|
47
|
+
const login = cfg.claudeCodeLogin === true;
|
|
48
|
+
const env = login ? stripProviderKeyEnv(buildEnv(cfg.env)) : buildEnv(cfg.env);
|
|
44
49
|
|
|
45
50
|
const artifacts = createArtifacts();
|
|
46
51
|
const onProgress = cfg.onProgress;
|
|
@@ -72,6 +77,8 @@ export class ClaudeSdkAdapter extends GeneratorAdapter {
|
|
|
72
77
|
allowDangerouslySkipPermissions: true,
|
|
73
78
|
...(cfg.cliPath && { pathToClaudeCodeExecutable: cfg.cliPath }),
|
|
74
79
|
...(cfg.stderr && { stderr: cfg.stderr }),
|
|
80
|
+
// ggui#1185 — tool-less, config-less, non-bare; the model is pinned above.
|
|
81
|
+
...(login ? claudeCodeLoginQueryOptions() : {}),
|
|
75
82
|
},
|
|
76
83
|
})) {
|
|
77
84
|
processMessage(
|
|
@@ -102,6 +102,12 @@ export interface GenerationDispatchParams {
|
|
|
102
102
|
* today's single screenshot at `viewport`.
|
|
103
103
|
*/
|
|
104
104
|
canvases?: readonly CanvasClass[];
|
|
105
|
+
/**
|
|
106
|
+
* ggui#1195 — per-canvas viewport override for the in-loop round: the
|
|
107
|
+
* box the composer targeted (`rendering.viewport`) so the round judges
|
|
108
|
+
* the same box. See `VisualEvalConfig.canvasViewports`.
|
|
109
|
+
*/
|
|
110
|
+
canvasViewports?: Partial<Readonly<Record<CanvasClass, { width: number; height: number }>>>;
|
|
105
111
|
};
|
|
106
112
|
/** Quality config controlling eval tiers and improvement behavior */
|
|
107
113
|
qualityConfig?: QualityConfig;
|
|
@@ -179,7 +185,12 @@ export interface GenerationDispatchParams {
|
|
|
179
185
|
* means `constrained` (byte-identical to the pre-`designMode` path).
|
|
180
186
|
*/
|
|
181
187
|
designMode?: DesignMode;
|
|
182
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* Rendering canvas class — see `CreateHarnessInput.canvas`. Absent, the
|
|
190
|
+
* harness derives it from `shellType` × `screen` (ggui#1117), so the axis
|
|
191
|
+
* checks always judge for the canvas the generation renders on; the
|
|
192
|
+
* `free` prompt renders it, the constrained prompt ignores it.
|
|
193
|
+
*/
|
|
183
194
|
canvas?: CanvasClass;
|
|
184
195
|
/** The app's generation profile (#991). */
|
|
185
196
|
profile?: GenerationProfileInput;
|
|
@@ -296,7 +307,13 @@ export async function dispatchGeneration(
|
|
|
296
307
|
: {}),
|
|
297
308
|
// Design mode + canvas — the harness carries them to every leg.
|
|
298
309
|
...(params.designMode !== undefined ? { designMode: params.designMode } : {}),
|
|
299
|
-
|
|
310
|
+
// ggui#1117 — the canvas is ALWAYS carried: a stated one, else the class
|
|
311
|
+
// shell × screen renders on. The axis checks judge for it
|
|
312
|
+
// (`universal.root_width_cap` stands down without one — which is what every
|
|
313
|
+
// served generation got, since the serving runtime states no canvas); the
|
|
314
|
+
// constrained prompt ignores it (INVARIANT 1, `design-mode.pin.test.ts`),
|
|
315
|
+
// so the prompt digests do not move; the free prompt renders it as before.
|
|
316
|
+
canvas: params.canvas ?? canvasForRendering(params.shellType, params.screen),
|
|
300
317
|
...(params.profile !== undefined ? { profile: params.profile } : {}),
|
|
301
318
|
// Policy is pre-resolved at the dispatch boundary so `createHarness`
|
|
302
319
|
// stays free of experiment plumbing. `resolveHarnessPolicy` applies
|
|
@@ -31,6 +31,24 @@ export function splitOpenAiUsage(
|
|
|
31
31
|
inputTokensInclCached: number,
|
|
32
32
|
outputTokens: number,
|
|
33
33
|
cachedTokens: number,
|
|
34
|
+
): { tokens: AdapterResult['tokens']; cacheReadTokens: number } {
|
|
35
|
+
return splitCacheInclusiveUsage(inputTokensInclCached, outputTokens, cachedTokens);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The provider-neutral form of the split above (ggui#1186, third site): any
|
|
40
|
+
* provider whose reported input token count INCLUDES the cached part of the
|
|
41
|
+
* prompt — OpenAI's `input_tokens` + `input_tokens_details.cached_tokens`,
|
|
42
|
+
* Gemini's `total_input_tokens` + `total_cached_tokens` and
|
|
43
|
+
* `promptTokenCount` + `cachedContentTokenCount` ("the cached part of the
|
|
44
|
+
* prompt", per the SDK) — is split the same way into the non-cached input and
|
|
45
|
+
* the cached subset, so `input` is never double-counted and `total` stays the
|
|
46
|
+
* full footprint.
|
|
47
|
+
*/
|
|
48
|
+
export function splitCacheInclusiveUsage(
|
|
49
|
+
inputTokensInclCached: number,
|
|
50
|
+
outputTokens: number,
|
|
51
|
+
cachedTokens: number,
|
|
34
52
|
): { tokens: AdapterResult['tokens']; cacheReadTokens: number } {
|
|
35
53
|
// Never let a malformed usage report drive input negative.
|
|
36
54
|
const cacheRead = Math.min(
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// legacy) still needs the wire-canonical anthropic id → cross-region
|
|
13
13
|
// profile id upcast.
|
|
14
14
|
|
|
15
|
+
import { CLAUDE_CODE_LOGIN_CREDENTIAL, PROVIDER_KEY_ENV_NAMES } from './claude/claude-code-login';
|
|
15
16
|
import type { LlmRoute } from '@ggui-ai/protocol';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -58,6 +59,13 @@ export function getBedrockModelId(model: string): string {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
export interface RoutingDecision {
|
|
62
|
+
/**
|
|
63
|
+
* Named only for the Claude Code login path (ggui#1185): the caller passed
|
|
64
|
+
* {@link CLAUDE_CODE_LOGIN_CREDENTIAL} as the key, every provider key is
|
|
65
|
+
* cleared from the env below, and the adapter pins the SDK options that keep
|
|
66
|
+
* the run tool-less, config-less and non-bare. Absent ⇒ key-based auth.
|
|
67
|
+
*/
|
|
68
|
+
readonly auth?: 'claude-code-login';
|
|
61
69
|
/**
|
|
62
70
|
* Upstream model id the dispatch adapter will pass to the provider
|
|
63
71
|
* SDK. For most routes this is `route.model` verbatim — the typed
|
|
@@ -143,6 +151,13 @@ function clearSiblings(
|
|
|
143
151
|
*/
|
|
144
152
|
export function resolveRoute(input: RoutingInput): RoutingDecision {
|
|
145
153
|
const { route, apiKey, env } = input;
|
|
154
|
+
if (apiKey === CLAUDE_CODE_LOGIN_CREDENTIAL && route.provider !== 'anthropic') {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`${route.provider}:${route.model} cannot use the claude-code-login credential — ` +
|
|
157
|
+
`the machine's Claude Code login authenticates the Claude Code binary only; ` +
|
|
158
|
+
`pick an anthropic route or supply a ${route.provider} API key.`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
146
161
|
|
|
147
162
|
switch (route.provider) {
|
|
148
163
|
case 'openai': {
|
|
@@ -215,6 +230,19 @@ export function resolveRoute(input: RoutingInput): RoutingDecision {
|
|
|
215
230
|
}
|
|
216
231
|
|
|
217
232
|
case 'anthropic': {
|
|
233
|
+
if (apiKey === CLAUDE_CODE_LOGIN_CREDENTIAL) {
|
|
234
|
+
// ggui#1185: no key reaches the spawned binary — it authenticates with
|
|
235
|
+
// the machine's own Claude Code login. Every provider key AND every
|
|
236
|
+
// sibling is cleared, because the binary prefers an env key over its
|
|
237
|
+
// login and a stale one would silently take the run off this path.
|
|
238
|
+
const cleared: Record<string, undefined> = {};
|
|
239
|
+
for (const k of PROVIDER_KEY_ENV_NAMES) cleared[k] = undefined;
|
|
240
|
+
return {
|
|
241
|
+
model: route.model,
|
|
242
|
+
env: { ...cleared, ...clearSiblings(new Set()) },
|
|
243
|
+
auth: 'claude-code-login',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
218
246
|
if (apiKey) {
|
|
219
247
|
return {
|
|
220
248
|
model: route.model,
|
|
@@ -246,8 +246,20 @@ export function generateBoilerplate(
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
const toolNote = tool ? ` (label "${label}", nextStep hint → ${tool})` : "";
|
|
249
|
+
// ggui#1190: the control's copy is the contract's label, VERBATIM — stated for
|
|
250
|
+
// EVERY action (it used to appear only beside a `nextStep` hint), because the
|
|
251
|
+
// model given `"Confirm & Schedule"` in the contract block wrote
|
|
252
|
+
// `'Confirm Schedule'`. The rule lives in DATA_PARAMETERIZATION rule 4; this is
|
|
253
|
+
// its reminder beside the action, and the axis check
|
|
254
|
+
// `universal.action_label_dropped` names the drop when both are ignored.
|
|
255
|
+
// ggui#1244: it rides the payload type's doc comment, NOT the hook line. Placed
|
|
256
|
+
// on the hook line directly before the once-hint below, the two read as the
|
|
257
|
+
// control's spec and gemini-3.5-flash-lite guarded the repeatable Save 8/8;
|
|
258
|
+
// here, with the hook line back to its earlier bytes, 5/24 — within noise of
|
|
259
|
+
// the rate before either hint moved (1/8) — while one-time commitments stayed
|
|
260
|
+
// guarded 40/40 across three providers and every label stayed verbatim (Exp 009).
|
|
249
261
|
actionTypeAliases.push(
|
|
250
|
-
`/** Action payload: ${desc || label}${toolNote} */\ntype ${typeName} = ${tsType};`
|
|
262
|
+
`/** Action payload: ${desc || label}${toolNote} — control copy: "${label}" VERBATIM */\ntype ${typeName} = ${tsType};`
|
|
251
263
|
);
|
|
252
264
|
// Inline signature tells the LLM exactly how to call this action
|
|
253
265
|
const callSig = tsType === "void" ? "() => void — fire and forget" : `(data: ${tsType}) => void`;
|
|
@@ -255,9 +267,12 @@ export function generateBoilerplate(
|
|
|
255
267
|
// ggui#1108 (mitigation): the reminder sits at the EXACT site the model
|
|
256
268
|
// writes the handler. It is a hint, not the rule — the rule is the prompt's
|
|
257
269
|
// `TERMINAL_ACTIONS` section, because only the request and the contract say
|
|
258
|
-
// which actions are meant once.
|
|
259
|
-
//
|
|
260
|
-
//
|
|
270
|
+
// which actions are meant once. It is NOT inert, whatever Exp 002b measured
|
|
271
|
+
// for a different site comment (adoption 1/36): Exp 009 (ggui#1244) removed
|
|
272
|
+
// it and one-time scheduling went armed on all three providers (guarded 9/12
|
|
273
|
+
// against 24/24), and a second hint placed beside it turned it into an
|
|
274
|
+
// instruction for every action. So it stays, ALONE on the hook line — what
|
|
275
|
+
// sits next to it is part of what it says.
|
|
261
276
|
const onceHint = " — if this is meant ONCE, disable its control after it fires";
|
|
262
277
|
actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
|
|
263
278
|
actionReturnFields.push(key);
|
|
@@ -213,7 +213,7 @@ Rules:
|
|
|
213
213
|
1. All request-specific data → props with defaults. City names, tickers, user names, dates, counts.
|
|
214
214
|
2. Layout and styling are universal. Colors, spacing, structure — these are the reusable part.
|
|
215
215
|
3. Default values come from the current request — so the component renders correctly standalone.
|
|
216
|
-
4. Copy comes from the props. NEVER invent display text the contract does not carry — no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. **Do NOT add a caps label above the heading** — not \`<Text caps>LET'S BEGIN</Text>\`, not \`WELCOME\`, not a section kicker like \`GETTING STARTED\` or \`OVERVIEW\` — UNLESS a prop supplies that exact text (\`<Text caps>{props.section}</Text>\`). When the request asks for a heading and a line, render the heading and the line and NOTHING above the heading. A heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render, and a caps eyebrow you wrote is the most common form of it.
|
|
216
|
+
4. Copy comes from the props. NEVER invent display text the contract does not carry — no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. **Do NOT add a caps label above the heading** — not \`<Text caps>LET'S BEGIN</Text>\`, not \`WELCOME\`, not a section kicker like \`GETTING STARTED\` or \`OVERVIEW\` — UNLESS a prop supplies that exact text (\`<Text caps>{props.section}</Text>\`). When the request asks for a heading and a line, render the heading and the line and NOTHING above the heading. A heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render, and a caps eyebrow you wrote is the most common form of it. An action's control shows the contract's \`label\` VERBATIM — every character, including \`&\`, \`/\`, \`–\` and quotes — never paraphrased, shortened or re-cased (\`"Confirm & Schedule"\` is not \`"Confirm Schedule"\`).
|
|
217
217
|
5. Props interface must be typed and exported.`;
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -298,16 +298,25 @@ export function createUiGenerator(
|
|
|
298
298
|
// structurally into the dispatch call instead. `route.env`
|
|
299
299
|
// already carries exactly the key(s) that would have gone into
|
|
300
300
|
// `process.env`, so no extra resolution work is needed here.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
301
|
+
//
|
|
302
|
+
// ggui#1185 — the login path has NO key to write anywhere: the
|
|
303
|
+
// route decision `auth: 'claude-code-login'` is carried as
|
|
304
|
+
// `routeOverride.claudeCodeLogin` in BOTH env modes, because the
|
|
305
|
+
// flag is the only way the decision reaches `createAgent` /
|
|
306
|
+
// `createVisionAgent` (which select the login client on it).
|
|
307
|
+
const routeOverride =
|
|
308
|
+
route.auth === 'claude-code-login'
|
|
309
|
+
? { claudeCodeLogin: true }
|
|
310
|
+
: disableEnvMutation
|
|
311
|
+
? {
|
|
312
|
+
apiKey:
|
|
313
|
+
route.env['ANTHROPIC_API_KEY'] ??
|
|
314
|
+
route.env['OPENAI_API_KEY'] ??
|
|
315
|
+
route.env['GEMINI_API_KEY'] ??
|
|
316
|
+
route.env['OPENROUTER_API_KEY'],
|
|
317
|
+
useBedrock: route.env['CLAUDE_CODE_USE_BEDROCK'] === '1',
|
|
318
|
+
}
|
|
319
|
+
: undefined;
|
|
311
320
|
|
|
312
321
|
try {
|
|
313
322
|
const tools = createGeneratorTools({
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// that carries the same rendering inputs.
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
17
|
-
import { resolve } from 'node:path';
|
|
17
|
+
import { relative, resolve } from 'node:path';
|
|
18
18
|
|
|
19
19
|
export interface JudgeDesignIdentity {
|
|
20
20
|
/** Absolute path of the design `src` tree the judge bundled against. */
|
|
@@ -37,20 +37,31 @@ export function isDesignRenderingInput(relativePath: string): boolean {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* sha256 over every rendering input under `root` (sorted relative path +
|
|
42
|
+
* contents) — the identity of a design tree as it paints.
|
|
43
|
+
*
|
|
44
|
+
* A function of the TREE, not of the spelling of `root`: the relative keys
|
|
45
|
+
* are taken against the RESOLVED root, so `…/design/src`, `…/design/src/` and
|
|
46
|
+
* `…/ui-gen/../design/src` hash the same bytes. Before this the key was
|
|
47
|
+
* sliced by the unresolved root's length while each entry had already been
|
|
48
|
+
* resolved, so an un-normalised root produced a different hash for the same
|
|
49
|
+
* tree — a phantom design skew for any caller composing the path.
|
|
50
|
+
*/
|
|
41
51
|
export function designTreeSha256(root: string): string {
|
|
52
|
+
const base = resolve(root);
|
|
42
53
|
const files: string[] = [];
|
|
43
54
|
const walk = (dir: string): void => {
|
|
44
55
|
for (const name of readdirSync(dir).sort()) {
|
|
45
56
|
const full = resolve(dir, name);
|
|
46
57
|
if (statSync(full).isDirectory()) walk(full);
|
|
47
|
-
else if (isDesignRenderingInput(
|
|
58
|
+
else if (isDesignRenderingInput(relative(base, full))) files.push(full);
|
|
48
59
|
}
|
|
49
60
|
};
|
|
50
|
-
walk(
|
|
61
|
+
walk(base);
|
|
51
62
|
const h = createHash('sha256');
|
|
52
63
|
for (const f of files) {
|
|
53
|
-
h.update(
|
|
64
|
+
h.update(relative(base, f) + '\n');
|
|
54
65
|
h.update(readFileSync(f));
|
|
55
66
|
h.update('\n');
|
|
56
67
|
}
|
|
@@ -123,6 +123,24 @@ export interface CriterionCoverage {
|
|
|
123
123
|
readonly reason?: string;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Coverage of the screenshot JUDGE leg for one eval round (ggui#1221) — a
|
|
128
|
+
* different surface from the `visual` CRITERION in `criteriaCoverage` (the
|
|
129
|
+
* LLM evaluator's read of the source). `ran` = a frame was captured and
|
|
130
|
+
* judged; `skipped` = the leg was configured but produced no verdict
|
|
131
|
+
* (no browser, a launch or in-page failure, an unbundlable component, an
|
|
132
|
+
* unparsable judge answer) with `reason` naming why; `not-applicable` =
|
|
133
|
+
* the round had no visual leg configured. Before this row a skipped leg
|
|
134
|
+
* left NO trace on the result — `{ issues: [] }` read exactly like a
|
|
135
|
+
* clean run, and every bootstrap mint on a browserless runtime "passed"
|
|
136
|
+
* its visual round that way.
|
|
137
|
+
*/
|
|
138
|
+
export interface VisualCoverage {
|
|
139
|
+
readonly status: CriterionRunStatus;
|
|
140
|
+
/** Populated for `skipped` / `not-applicable` — why no verdict. */
|
|
141
|
+
readonly reason?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
/**
|
|
127
145
|
* The static criteria `runLLMEvaluation` runs, in stamp order — the
|
|
128
146
|
* ONE list both the evaluator's coverage stamp and the harness's
|
|
@@ -185,6 +203,13 @@ export interface EvalResult {
|
|
|
185
203
|
* never here — this object is serialized into reports.
|
|
186
204
|
*/
|
|
187
205
|
visual?: VisualEvalSummary;
|
|
206
|
+
/**
|
|
207
|
+
* Whether the screenshot judge leg ran, was skipped (with the reason) or
|
|
208
|
+
* was not configured — ALWAYS stamped by the harness's eval round
|
|
209
|
+
* (ggui#1221), so a consumer can tell a clean visual run from a leg
|
|
210
|
+
* that silently could not run. See {@link VisualCoverage}.
|
|
211
|
+
*/
|
|
212
|
+
visualCoverage?: VisualCoverage;
|
|
188
213
|
}
|
|
189
214
|
|
|
190
215
|
/** One canvas's visual verdict, PNG-free — see `EvalResult.visual`. */
|
|
@@ -227,6 +252,21 @@ export interface CanvasVisualSummary {
|
|
|
227
252
|
fit?: 'fill';
|
|
228
253
|
}
|
|
229
254
|
|
|
255
|
+
/**
|
|
256
|
+
* ggui#1195 — the fit reading on the canvas whose policy FAILS an overflow
|
|
257
|
+
* (the inline chat card): the box it was judged at (`ceiling` — the order's
|
|
258
|
+
* declared viewport when one was carried, else the class box), whether that
|
|
259
|
+
* box was declared, and how many px of content sit below it (0 = fits).
|
|
260
|
+
* Present only when such a canvas was judged with a measurable height, so
|
|
261
|
+
* the bar and the ledger read one number for "does the hello fit".
|
|
262
|
+
*/
|
|
263
|
+
export interface VisualFitStamp {
|
|
264
|
+
readonly canvas: CanvasClass;
|
|
265
|
+
readonly ceiling: { readonly width: number; readonly height: number };
|
|
266
|
+
readonly declared: boolean;
|
|
267
|
+
readonly overflowPx: number;
|
|
268
|
+
}
|
|
269
|
+
|
|
230
270
|
/** The visual leg's per-canvas summary — see `EvalResult.visual`. */
|
|
231
271
|
export interface VisualEvalSummary {
|
|
232
272
|
/** Mean of the canvas scores (rounded). */
|
|
@@ -234,6 +274,8 @@ export interface VisualEvalSummary {
|
|
|
234
274
|
/** Every canvas passed. */
|
|
235
275
|
passed: boolean;
|
|
236
276
|
canvases: CanvasVisualSummary[];
|
|
277
|
+
/** ggui#1195 — see {@link VisualFitStamp}; absent when no fail-policy canvas was judged. */
|
|
278
|
+
fit?: VisualFitStamp;
|
|
237
279
|
/** The design tree the judge painted with (ggui#1042): `src` + `srcSha256` — `design@judge`. */
|
|
238
280
|
design?: { readonly src: string; readonly srcSha256: string };
|
|
239
281
|
/** The mode the judge's tokens were composed in, when the caller said (ggui#1076). */
|