@ggui-ai/ui-gen 0.8.0 → 0.10.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/generation-dispatch.d.ts +18 -1
- package/dist/adapters/generation-dispatch.js +819 -1577
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +28 -11
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +838 -1579
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +109 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +17 -4
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +11 -1
- package/dist/check/index.js.map +1 -1
- package/dist/compose.js +5 -5
- package/dist/compose.js.map +1 -1
- package/dist/{create-ui-generator-GX7tvbyH.d.ts → create-ui-generator-CF0EL0Li.d.ts} +26 -1
- package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +133 -1
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +133 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +46 -3
- package/dist/evaluation/index.js +410 -24
- package/dist/evaluation/index.js.map +1 -1
- package/dist/fragments/index.js +5 -5
- package/dist/fragments/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +110 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +5 -126
- package/dist/harness/index.js +292 -32
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +1 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.js +17 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +17 -1
- package/dist/harness/runtime.js +23 -8
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +22 -9
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +838 -1579
- package/dist/index.js.map +1 -1
- package/dist/llm-router-BEVtbAb9.d.ts +213 -0
- package/dist/patch.d.ts +1 -1
- package/dist/provider-adapter.d.ts +16 -6
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +54 -99
- package/dist/providers/index.js +13 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-CNZjHD5g.d.ts → render-check-BNYGVDJd.d.ts} +1 -1
- package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
- package/dist/validation/index.js +2 -2
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +18 -18
- package/src/adapters/generation-dispatch.ts +28 -2
- package/src/adapters/openrouter/client.ts +2 -2
- package/src/adapters/openrouter/types.ts +10 -0
- package/src/boilerplate/generate.ts +10 -2
- package/src/boilerplate/json-schema-ts.ts +22 -2
- package/src/boilerplate/system-prompt.ts +4 -2
- package/src/compile.ts +1 -1
- package/src/contract-context.ts +10 -2
- package/src/create-ui-generator.ts +58 -11
- package/src/design-system-docs.ts +6 -4
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
- package/src/evaluation/axis-checks/checks/universal.ts +39 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/index.ts +11 -0
- package/src/evaluation/llm-evaluator.ts +26 -3
- package/src/evaluation/visual-evaluator.ts +49 -66
- package/src/fragments/realtime.ts +3 -3
- package/src/fragments/state.ts +2 -2
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/index.ts +8 -0
- package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
- package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
- package/src/harness/check/runtime-render/render-check.ts +72 -1
- package/src/harness/coding/init-session.ts +4 -1
- package/src/harness/coding/run-eval-round.ts +67 -5
- package/src/harness/index.ts +1 -1
- package/src/harness/llm-router.ts +451 -27
- package/src/harness/llm-trace-sink.ts +2 -1
- package/src/harness/runtime.ts +15 -0
- package/src/index.ts +5 -5
- package/src/provider-adapter.ts +16 -6
- package/src/providers/bedrock.ts +89 -26
- package/src/providers/index.ts +16 -4
- package/src/tools/render-check.ts +19 -7
- package/src/validation/ui-compiler.ts +2 -2
package/dist/evaluation/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
|
|
|
2
2
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { listContractGadgets, HOOK_NAME_RE, STDLIB_GADGETS_PACKAGE } from '@ggui-ai/protocol';
|
|
5
|
+
import 'esbuild';
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -23,14 +24,16 @@ var OpenRouterError;
|
|
|
23
24
|
var init_types = __esm({
|
|
24
25
|
"src/adapters/openrouter/types.ts"() {
|
|
25
26
|
OpenRouterError = class extends Error {
|
|
26
|
-
constructor(message, status, code) {
|
|
27
|
+
constructor(message, status, code, headers) {
|
|
27
28
|
super(message);
|
|
28
29
|
this.status = status;
|
|
29
30
|
this.code = code;
|
|
31
|
+
this.headers = headers;
|
|
30
32
|
this.name = "OpenRouterError";
|
|
31
33
|
}
|
|
32
34
|
status;
|
|
33
35
|
code;
|
|
36
|
+
headers;
|
|
34
37
|
get isTransient() {
|
|
35
38
|
return this.status === 408 || this.status === 429 || this.status === 502 || this.status === 503;
|
|
36
39
|
}
|
|
@@ -75,7 +78,7 @@ var init_client = __esm({
|
|
|
75
78
|
if (!response.ok) {
|
|
76
79
|
const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
77
80
|
const message = errorBody.error?.message ?? response.statusText;
|
|
78
|
-
throw new OpenRouterError(message, response.status);
|
|
81
|
+
throw new OpenRouterError(message, response.status, void 0, response.headers);
|
|
79
82
|
}
|
|
80
83
|
return response.json();
|
|
81
84
|
}
|
|
@@ -93,7 +96,7 @@ var init_client = __esm({
|
|
|
93
96
|
if (!response.ok) {
|
|
94
97
|
const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
95
98
|
const message = errorBody.error?.message ?? response.statusText;
|
|
96
|
-
throw new OpenRouterError(message, response.status);
|
|
99
|
+
throw new OpenRouterError(message, response.status, void 0, response.headers);
|
|
97
100
|
}
|
|
98
101
|
if (!response.body) {
|
|
99
102
|
throw new OpenRouterError("No response body for streaming request", 500);
|
|
@@ -455,6 +458,34 @@ function errorSummary(e) {
|
|
|
455
458
|
}
|
|
456
459
|
return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
|
|
457
460
|
}
|
|
461
|
+
function isRateLimited(e) {
|
|
462
|
+
if (!(e instanceof Error) || !("status" in e)) return false;
|
|
463
|
+
return e.status === 429;
|
|
464
|
+
}
|
|
465
|
+
function extractStatus(e) {
|
|
466
|
+
if (!(e instanceof Error) || !("status" in e)) return void 0;
|
|
467
|
+
const status = e.status;
|
|
468
|
+
return typeof status === "number" ? status : void 0;
|
|
469
|
+
}
|
|
470
|
+
function extractRetryAfterSec(e) {
|
|
471
|
+
if (!(e instanceof Error) || !("headers" in e)) return void 0;
|
|
472
|
+
const headers = e.headers;
|
|
473
|
+
if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
const raw = headers.get("retry-after");
|
|
477
|
+
if (raw === null) return void 0;
|
|
478
|
+
const parsed = Number(raw);
|
|
479
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
|
|
480
|
+
}
|
|
481
|
+
function sleep(ms) {
|
|
482
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
483
|
+
}
|
|
484
|
+
var MAX_RETRY_ATTEMPTS = 2;
|
|
485
|
+
var RETRY_AFTER_CAP_SEC = 15;
|
|
486
|
+
var DEFAULT_BACKOFF_BASE_MS = 1e3;
|
|
487
|
+
var DEFAULT_BACKOFF_MAX_MS = 8e3;
|
|
488
|
+
var MAX_TOTAL_RETRY_DELAY_MS = 2e4;
|
|
458
489
|
var LLMAgent = class {
|
|
459
490
|
client = null;
|
|
460
491
|
// ── Session state for server-side chaining ──────────────
|
|
@@ -462,6 +493,19 @@ var LLMAgent = class {
|
|
|
462
493
|
// Enables turn 2+ to skip re-sending system prompt + history.
|
|
463
494
|
// Call resetSession() between independent generation runs.
|
|
464
495
|
lastSessionId;
|
|
496
|
+
/**
|
|
497
|
+
* Explicit per-call routing override — see `AgentConfig.routeOverride`.
|
|
498
|
+
* Absent when constructed via the bare `new XAgent()` form (every
|
|
499
|
+
* existing call site), so `createClient()`/`resolveModel()` fall
|
|
500
|
+
* back to `process.env` exactly as before.
|
|
501
|
+
*/
|
|
502
|
+
routeOverride;
|
|
503
|
+
/** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
|
|
504
|
+
onRetry;
|
|
505
|
+
constructor(routeOverride, onRetry) {
|
|
506
|
+
this.routeOverride = routeOverride;
|
|
507
|
+
this.onRetry = onRetry;
|
|
508
|
+
}
|
|
465
509
|
async getClient() {
|
|
466
510
|
if (!this.client) {
|
|
467
511
|
this.client = await this.createClient();
|
|
@@ -495,36 +539,104 @@ var LLMAgent = class {
|
|
|
495
539
|
async sendToolResult(_results) {
|
|
496
540
|
}
|
|
497
541
|
/**
|
|
498
|
-
* Execute an API call
|
|
499
|
-
*
|
|
542
|
+
* Execute an API call, retrying once or twice on HTTP 429
|
|
543
|
+
* (rate-limited) before giving up (#489).
|
|
544
|
+
*
|
|
545
|
+
* Policy: on a 429, honor the provider's `Retry-After` header when
|
|
546
|
+
* present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
|
|
547
|
+
* longer wait is treated as "don't retry", not "wait longer": making
|
|
548
|
+
* the caller sit through 15+ more seconds on a single failed call is
|
|
549
|
+
* a bad experience regardless of what's driving the request);
|
|
550
|
+
* otherwise fall back to exponential backoff with jitter, capped at
|
|
551
|
+
* `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
|
|
552
|
+
* `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
|
|
553
|
+
* exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
|
|
554
|
+
* non-429 error, or a 429 with no attempts left, is logged and
|
|
555
|
+
* re-thrown immediately — unchanged from the pre-#489 behavior.
|
|
556
|
+
*
|
|
557
|
+
* **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
|
|
558
|
+
* bounds a single `apiCall()` invocation, not a whole generation. A
|
|
559
|
+
* generation issues many sequential `apiCall()` calls (coding turns,
|
|
560
|
+
* tool-result round-trips, eval rounds); under sustained throttling
|
|
561
|
+
* each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
|
|
562
|
+
* a generation with k rate-limited calls can add up to roughly `k *
|
|
563
|
+
* MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
|
|
564
|
+
* resources (e.g. the caller's concurrency slot) — not a flat 20s
|
|
565
|
+
* ceiling. No shared per-generation budget is threaded here by
|
|
566
|
+
* design (accepted ruling, not an oversight): the caller's own
|
|
567
|
+
* queue/admission backstop is what bounds how long other
|
|
568
|
+
* queued work waits, independent of any one generation's retry
|
|
569
|
+
* total.
|
|
500
570
|
*/
|
|
501
571
|
async apiCall(fn) {
|
|
502
572
|
const start = Date.now();
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
573
|
+
let totalDelayMs = 0;
|
|
574
|
+
for (let attempt = 0; ; attempt++) {
|
|
575
|
+
try {
|
|
576
|
+
return await fn();
|
|
577
|
+
} catch (e) {
|
|
578
|
+
const ms = Date.now() - start;
|
|
579
|
+
if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
|
|
580
|
+
console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
|
|
581
|
+
throw e;
|
|
582
|
+
}
|
|
583
|
+
const retryAfterSec = extractRetryAfterSec(e);
|
|
584
|
+
if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
|
|
585
|
+
console.error(
|
|
586
|
+
`[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
|
|
587
|
+
);
|
|
588
|
+
throw e;
|
|
589
|
+
}
|
|
590
|
+
const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
|
|
591
|
+
if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
|
|
592
|
+
console.error(
|
|
593
|
+
`[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
|
|
594
|
+
);
|
|
595
|
+
throw e;
|
|
596
|
+
}
|
|
597
|
+
totalDelayMs += delayMs;
|
|
598
|
+
const info = {
|
|
599
|
+
provider: this.provider,
|
|
600
|
+
attempt: attempt + 1,
|
|
601
|
+
maxAttempts: MAX_RETRY_ATTEMPTS,
|
|
602
|
+
status: extractStatus(e),
|
|
603
|
+
retryAfterSec,
|
|
604
|
+
delayMs,
|
|
605
|
+
message: errorSummary(e)
|
|
606
|
+
};
|
|
607
|
+
console.warn(
|
|
608
|
+
`[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
|
|
609
|
+
);
|
|
610
|
+
try {
|
|
611
|
+
this.onRetry?.(info);
|
|
612
|
+
} catch (observerError) {
|
|
613
|
+
console.warn(
|
|
614
|
+
`[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
await sleep(delayMs);
|
|
618
|
+
}
|
|
509
619
|
}
|
|
510
620
|
}
|
|
511
621
|
};
|
|
512
622
|
var AnthropicAgent = class extends LLMAgent {
|
|
513
623
|
provider = "anthropic";
|
|
514
624
|
resolveModel(model) {
|
|
515
|
-
|
|
625
|
+
const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
|
|
626
|
+
if (useBedrock) {
|
|
516
627
|
return getBedrockModelId(model);
|
|
517
628
|
}
|
|
518
629
|
return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
|
|
519
630
|
}
|
|
520
631
|
async createClient() {
|
|
521
|
-
|
|
632
|
+
const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
|
|
633
|
+
if (useBedrock) {
|
|
522
634
|
const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk');
|
|
523
635
|
return new AnthropicBedrock({
|
|
524
636
|
awsRegion: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
|
|
525
637
|
});
|
|
526
638
|
}
|
|
527
|
-
return createAnthropicClient(process.env.ANTHROPIC_API_KEY);
|
|
639
|
+
return createAnthropicClient(this.routeOverride?.apiKey ?? process.env.ANTHROPIC_API_KEY);
|
|
528
640
|
}
|
|
529
641
|
async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
|
|
530
642
|
const client = await this.getClient();
|
|
@@ -586,6 +698,63 @@ var AnthropicAgent = class extends LLMAgent {
|
|
|
586
698
|
throw e;
|
|
587
699
|
}
|
|
588
700
|
}
|
|
701
|
+
async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
|
|
702
|
+
const client = await this.getClient();
|
|
703
|
+
const traceId = newLlmTraceId();
|
|
704
|
+
const startedAt = Date.now();
|
|
705
|
+
const resolvedModel = this.resolveModel(model);
|
|
706
|
+
const imageBlock = {
|
|
707
|
+
type: "image",
|
|
708
|
+
source: {
|
|
709
|
+
type: "base64",
|
|
710
|
+
media_type: image.mediaType,
|
|
711
|
+
data: image.base64
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
try {
|
|
715
|
+
const response = await this.apiCall(
|
|
716
|
+
() => client.messages.create({
|
|
717
|
+
model: resolvedModel,
|
|
718
|
+
max_tokens: maxTokens ?? 1500,
|
|
719
|
+
system: systemPrompt,
|
|
720
|
+
messages: [
|
|
721
|
+
{
|
|
722
|
+
role: "user",
|
|
723
|
+
content: [imageBlock, { type: "text", text: userPrompt }]
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
})
|
|
727
|
+
);
|
|
728
|
+
const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
729
|
+
const endedAt = Date.now();
|
|
730
|
+
emitLlmTraceEvent({
|
|
731
|
+
id: traceId,
|
|
732
|
+
at: startedAt,
|
|
733
|
+
endedAt,
|
|
734
|
+
durationMs: endedAt - startedAt,
|
|
735
|
+
provider: "anthropic",
|
|
736
|
+
model: resolvedModel,
|
|
737
|
+
kind: "callVision",
|
|
738
|
+
systemPrompt,
|
|
739
|
+
userPrompt,
|
|
740
|
+
result: {
|
|
741
|
+
inputTokens: response.usage.input_tokens,
|
|
742
|
+
outputTokens: response.usage.output_tokens,
|
|
743
|
+
text
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
return {
|
|
747
|
+
text,
|
|
748
|
+
inputTokens: response.usage.input_tokens,
|
|
749
|
+
outputTokens: response.usage.output_tokens
|
|
750
|
+
};
|
|
751
|
+
} catch (e) {
|
|
752
|
+
emitLlmTraceEvent({
|
|
753
|
+
error: { message: e instanceof Error ? e.message : String(e) }
|
|
754
|
+
});
|
|
755
|
+
throw e;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
589
758
|
async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
|
|
590
759
|
const client = await this.getClient();
|
|
591
760
|
const traceId = newLlmTraceId();
|
|
@@ -765,7 +934,7 @@ var OpenAIAgent = class extends LLMAgent {
|
|
|
765
934
|
}
|
|
766
935
|
async createClient() {
|
|
767
936
|
const { default: OpenAISDK } = await import('openai');
|
|
768
|
-
return new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY });
|
|
937
|
+
return new OpenAISDK({ apiKey: this.routeOverride?.apiKey ?? process.env.OPENAI_API_KEY });
|
|
769
938
|
}
|
|
770
939
|
async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
|
|
771
940
|
const client = await this.getClient();
|
|
@@ -936,7 +1105,7 @@ var GoogleAgent = class extends LLMAgent {
|
|
|
936
1105
|
async createClient() {
|
|
937
1106
|
const { GoogleGenAI: GoogleGenAISDK } = await import('@google/genai');
|
|
938
1107
|
return new GoogleGenAISDK({
|
|
939
|
-
apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
|
|
1108
|
+
apiKey: this.routeOverride?.apiKey ?? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
|
|
940
1109
|
httpOptions: { timeout: 3e5 }
|
|
941
1110
|
// 5 min — Pro models can be slow
|
|
942
1111
|
});
|
|
@@ -961,6 +1130,62 @@ var GoogleAgent = class extends LLMAgent {
|
|
|
961
1130
|
outputTokens: interaction.usage?.total_output_tokens ?? 0
|
|
962
1131
|
};
|
|
963
1132
|
}
|
|
1133
|
+
async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
|
|
1134
|
+
const client = await this.getClient();
|
|
1135
|
+
const traceId = newLlmTraceId();
|
|
1136
|
+
const startedAt = Date.now();
|
|
1137
|
+
const resolvedModel = this.resolveModel(model);
|
|
1138
|
+
try {
|
|
1139
|
+
const response = await this.apiCall(
|
|
1140
|
+
() => client.models.generateContent({
|
|
1141
|
+
model: resolvedModel,
|
|
1142
|
+
contents: [
|
|
1143
|
+
{
|
|
1144
|
+
role: "user",
|
|
1145
|
+
parts: [
|
|
1146
|
+
{ inlineData: { mimeType: image.mediaType, data: image.base64 } },
|
|
1147
|
+
{ text: userPrompt }
|
|
1148
|
+
]
|
|
1149
|
+
}
|
|
1150
|
+
],
|
|
1151
|
+
config: {
|
|
1152
|
+
systemInstruction: systemPrompt,
|
|
1153
|
+
...maxTokens ? { maxOutputTokens: maxTokens } : {}
|
|
1154
|
+
}
|
|
1155
|
+
})
|
|
1156
|
+
);
|
|
1157
|
+
const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
|
|
1158
|
+
const usage = response.usageMetadata;
|
|
1159
|
+
const result = {
|
|
1160
|
+
text,
|
|
1161
|
+
inputTokens: usage?.promptTokenCount ?? 0,
|
|
1162
|
+
outputTokens: usage?.candidatesTokenCount ?? 0
|
|
1163
|
+
};
|
|
1164
|
+
const endedAt = Date.now();
|
|
1165
|
+
emitLlmTraceEvent({
|
|
1166
|
+
id: traceId,
|
|
1167
|
+
at: startedAt,
|
|
1168
|
+
endedAt,
|
|
1169
|
+
durationMs: endedAt - startedAt,
|
|
1170
|
+
provider: "google",
|
|
1171
|
+
model: resolvedModel,
|
|
1172
|
+
kind: "callVision",
|
|
1173
|
+
systemPrompt,
|
|
1174
|
+
userPrompt,
|
|
1175
|
+
result: {
|
|
1176
|
+
inputTokens: result.inputTokens,
|
|
1177
|
+
outputTokens: result.outputTokens,
|
|
1178
|
+
text
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
return result;
|
|
1182
|
+
} catch (e) {
|
|
1183
|
+
emitLlmTraceEvent({
|
|
1184
|
+
error: { message: e instanceof Error ? e.message : String(e) }
|
|
1185
|
+
});
|
|
1186
|
+
throw e;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
964
1189
|
async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
|
|
965
1190
|
const client = await this.getClient();
|
|
966
1191
|
const interactionTools = tools.map((t) => ({
|
|
@@ -1163,7 +1388,7 @@ var OpenRouterAgent = class extends LLMAgent {
|
|
|
1163
1388
|
async createClient() {
|
|
1164
1389
|
const { OpenRouterClient: OpenRouterClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
1165
1390
|
return new OpenRouterClient2({
|
|
1166
|
-
apiKey: process.env.OPENROUTER_API_KEY
|
|
1391
|
+
apiKey: this.routeOverride?.apiKey ?? process.env.OPENROUTER_API_KEY
|
|
1167
1392
|
});
|
|
1168
1393
|
}
|
|
1169
1394
|
async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
|
|
@@ -1289,16 +1514,27 @@ var OpenRouterAgent = class extends LLMAgent {
|
|
|
1289
1514
|
return { text: finalText, inputTokens: totalInput, outputTokens: totalOutput, turnsUsed };
|
|
1290
1515
|
}
|
|
1291
1516
|
};
|
|
1292
|
-
function createAgent(
|
|
1517
|
+
function createAgent(providerOrConfig) {
|
|
1518
|
+
const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
|
|
1519
|
+
const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
|
|
1520
|
+
const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
|
|
1293
1521
|
switch (provider) {
|
|
1294
1522
|
case "anthropic":
|
|
1295
|
-
return new AnthropicAgent();
|
|
1523
|
+
return new AnthropicAgent(routeOverride, onRetry);
|
|
1296
1524
|
case "openai":
|
|
1297
|
-
return new OpenAIAgent();
|
|
1525
|
+
return new OpenAIAgent(routeOverride, onRetry);
|
|
1298
1526
|
case "google":
|
|
1299
|
-
return new GoogleAgent();
|
|
1527
|
+
return new GoogleAgent(routeOverride, onRetry);
|
|
1300
1528
|
case "openrouter":
|
|
1301
|
-
return new OpenRouterAgent();
|
|
1529
|
+
return new OpenRouterAgent(routeOverride, onRetry);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
function createVisionAgent(config) {
|
|
1533
|
+
switch (config.provider) {
|
|
1534
|
+
case "anthropic":
|
|
1535
|
+
return new AnthropicAgent(config.routeOverride, config.onRetry);
|
|
1536
|
+
case "google":
|
|
1537
|
+
return new GoogleAgent(config.routeOverride, config.onRetry);
|
|
1302
1538
|
}
|
|
1303
1539
|
}
|
|
1304
1540
|
|
|
@@ -2017,6 +2253,31 @@ function runNoPhantomUseState(input) {
|
|
|
2017
2253
|
}
|
|
2018
2254
|
return issues;
|
|
2019
2255
|
}
|
|
2256
|
+
function runPropSeedNoResync(input) {
|
|
2257
|
+
if (input.compiledCode === null) return [];
|
|
2258
|
+
const src = input.sourceCode;
|
|
2259
|
+
const issues = [];
|
|
2260
|
+
const re = /const\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useState(?:<[^>]*>)?\s*\(\s*props\??\.(\w+)/g;
|
|
2261
|
+
for (const m of src.matchAll(re)) {
|
|
2262
|
+
const [full, stateVar, setter, propName] = m;
|
|
2263
|
+
const idx = m.index ?? 0;
|
|
2264
|
+
const after = src.slice(idx + full.length);
|
|
2265
|
+
if (!new RegExp(`\\b${setter}\\s*\\(`).test(after)) continue;
|
|
2266
|
+
const resync = new RegExp(
|
|
2267
|
+
`\\[[^\\[\\]]*props\\??\\.${propName}\\b[^\\[\\]]*\\]`
|
|
2268
|
+
);
|
|
2269
|
+
if (resync.test(src)) continue;
|
|
2270
|
+
issues.push(
|
|
2271
|
+
mkIssue(
|
|
2272
|
+
"universal.prop_seed_no_resync",
|
|
2273
|
+
`useState(props.${propName}) seeds "${stateVar}" and mutates it, but no effect depends on [props.${propName}] \u2014 a ggui_amend repaint pushes new props that never reach this display (#480).`,
|
|
2274
|
+
`Add \`useEffect(() => ${setter}(props.${propName}), [props.${propName}]);\` so agent repaints re-seed the state, or derive the display directly from props.${propName}.`,
|
|
2275
|
+
"warn"
|
|
2276
|
+
)
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
return issues;
|
|
2280
|
+
}
|
|
2020
2281
|
var UNIVERSAL_CHECKS = [
|
|
2021
2282
|
{
|
|
2022
2283
|
id: "universal.prop_coverage",
|
|
@@ -2035,6 +2296,12 @@ var UNIVERSAL_CHECKS = [
|
|
|
2035
2296
|
axis: "render",
|
|
2036
2297
|
values: ALL_RENDER_VALUES,
|
|
2037
2298
|
run: runNoPhantomUseState
|
|
2299
|
+
},
|
|
2300
|
+
{
|
|
2301
|
+
id: "universal.prop_seed_no_resync",
|
|
2302
|
+
axis: "render",
|
|
2303
|
+
values: ALL_RENDER_VALUES,
|
|
2304
|
+
run: runPropSeedNoResync
|
|
2038
2305
|
}
|
|
2039
2306
|
];
|
|
2040
2307
|
|
|
@@ -2054,7 +2321,7 @@ function runStateSeededFromProps(input) {
|
|
|
2054
2321
|
mkIssue(
|
|
2055
2322
|
"state.merge.seeded_from_props",
|
|
2056
2323
|
`Entity collection "${e.name}" is not seeded from props \u2014 no useState initializer reads props.${e.name}.`,
|
|
2057
|
-
`Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` so stream/action updates
|
|
2324
|
+
`Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` paired with \`useEffect(() => set${cap(e.name)}(props.${e.name}), [props.${e.name}]);\` so stream/action updates merge into live state AND agent repaints (ggui_amend) re-seed it (#480).`
|
|
2058
2325
|
)
|
|
2059
2326
|
);
|
|
2060
2327
|
}
|
|
@@ -2158,6 +2425,106 @@ var STATE_MERGE_CHECKS = [
|
|
|
2158
2425
|
}
|
|
2159
2426
|
];
|
|
2160
2427
|
|
|
2428
|
+
// src/evaluation/axis-checks/checks/state-ui-affordance.ts
|
|
2429
|
+
var STATE_ARIA_OR_ROLE = /aria-(checked|pressed|selected|expanded|current)\s*=|role\s*=/;
|
|
2430
|
+
var STATE_PRIMITIVE_IN_SUBTREE = /<(Checkbox|Toggle|Tabs|Accordion)[\s/>]/;
|
|
2431
|
+
var SEMANTIC_PRIMITIVES = /* @__PURE__ */ new Set(["Button", "Checkbox", "Toggle", "Link", "MenuItem"]);
|
|
2432
|
+
function collectSetters(src) {
|
|
2433
|
+
const setters = /* @__PURE__ */ new Set();
|
|
2434
|
+
for (const m of src.matchAll(/const\s*\[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState/g)) {
|
|
2435
|
+
setters.add(m[1]);
|
|
2436
|
+
}
|
|
2437
|
+
return setters;
|
|
2438
|
+
}
|
|
2439
|
+
function collectHandlers(src, setters) {
|
|
2440
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
2441
|
+
for (const m of src.matchAll(/const\s+(\w+)\s*=\s*(?:\([^)]*\)|\w+)\s*=>\s*\{?/g)) {
|
|
2442
|
+
const body = src.slice(m.index + m[0].length, m.index + m[0].length + 400);
|
|
2443
|
+
for (const s of setters) {
|
|
2444
|
+
if (body.includes(s)) {
|
|
2445
|
+
handlers.add(m[1]);
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return handlers;
|
|
2451
|
+
}
|
|
2452
|
+
function attrBlock(src, start) {
|
|
2453
|
+
let depth = 0;
|
|
2454
|
+
for (let i = start; i < src.length; i++) {
|
|
2455
|
+
const c = src[i];
|
|
2456
|
+
if (c === "{") depth++;
|
|
2457
|
+
else if (c === "}") depth--;
|
|
2458
|
+
else if (c === ">" && depth === 0) return { block: src.slice(start, i), end: i };
|
|
2459
|
+
}
|
|
2460
|
+
return { block: src.slice(start, start + 900), end: start + 900 };
|
|
2461
|
+
}
|
|
2462
|
+
function onClickValue(block) {
|
|
2463
|
+
const m = block.match(/onClick\s*=\s*\{/);
|
|
2464
|
+
if (!m || m.index === void 0) return null;
|
|
2465
|
+
const open = m.index + m[0].length - 1;
|
|
2466
|
+
let depth = 0;
|
|
2467
|
+
for (let i = open; i < block.length; i++) {
|
|
2468
|
+
if (block[i] === "{") depth++;
|
|
2469
|
+
else if (block[i] === "}") {
|
|
2470
|
+
depth--;
|
|
2471
|
+
if (depth === 0) return block.slice(open + 1, i);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return block.slice(open + 1);
|
|
2475
|
+
}
|
|
2476
|
+
function subtree(src, pos, tag) {
|
|
2477
|
+
const close = src.indexOf(`</${tag}>`, pos);
|
|
2478
|
+
return src.slice(pos, close === -1 ? pos + 700 : close);
|
|
2479
|
+
}
|
|
2480
|
+
function runStateAriaPresent(input) {
|
|
2481
|
+
if (input.compiledCode === null) return [];
|
|
2482
|
+
const src = input.sourceCode;
|
|
2483
|
+
const setters = collectSetters(src);
|
|
2484
|
+
const handlers = collectHandlers(src, setters);
|
|
2485
|
+
const issues = [];
|
|
2486
|
+
const flaggedLines = /* @__PURE__ */ new Set();
|
|
2487
|
+
const localComponents = /* @__PURE__ */ new Set();
|
|
2488
|
+
for (const m of src.matchAll(/(?:function\s+([A-Z]\w*)\s*\(|const\s+([A-Z]\w*)\s*=)/g)) {
|
|
2489
|
+
localComponents.add(m[1] ?? m[2]);
|
|
2490
|
+
}
|
|
2491
|
+
for (const m of src.matchAll(/<([A-Za-z][A-Za-z0-9]*)\b/g)) {
|
|
2492
|
+
const tag = m[1];
|
|
2493
|
+
if (m.index === void 0) continue;
|
|
2494
|
+
if (localComponents.has(tag)) continue;
|
|
2495
|
+
const { block, end } = attrBlock(src, m.index + m[0].length);
|
|
2496
|
+
const clickValue = onClickValue(block);
|
|
2497
|
+
if (clickValue === null) continue;
|
|
2498
|
+
const mutatesAffordance = [...setters].some((s) => clickValue.includes(s)) || [...handlers].some((h) => new RegExp(`\\b${h}\\b`).test(clickValue)) || // Extracted-subcomponent shape: the toggle handler arrives as an
|
|
2499
|
+
// `on[A-Z]…` callback prop and is invoked from onClick — inside a
|
|
2500
|
+
// ui-affordance component that IS the affordance mutation.
|
|
2501
|
+
/\bon[A-Z]\w*\b/.test(clickValue);
|
|
2502
|
+
if (!mutatesAffordance) continue;
|
|
2503
|
+
if (SEMANTIC_PRIMITIVES.has(tag)) continue;
|
|
2504
|
+
if (STATE_ARIA_OR_ROLE.test(block)) continue;
|
|
2505
|
+
if (STATE_PRIMITIVE_IN_SUBTREE.test(subtree(src, end, tag))) continue;
|
|
2506
|
+
const line = src.slice(0, m.index).split("\n").length;
|
|
2507
|
+
if (flaggedLines.has(line)) continue;
|
|
2508
|
+
flaggedLines.add(line);
|
|
2509
|
+
issues.push(
|
|
2510
|
+
mkIssue(
|
|
2511
|
+
"state.ui_affordance.state_aria_present",
|
|
2512
|
+
`Line ${line}: <${tag}> toggles the UI affordance from its onClick but exposes no state ARIA \u2014 its checked/selected/expanded state is invisible to assistive technology (styling is the only carrier).`,
|
|
2513
|
+
`Add the matching state attribute to the SAME element that carries the onClick: \`role="checkbox"\` + \`aria-checked={\u2026}\` for a toggle row, \`aria-pressed\` for a toggle button, \`aria-selected\` for a selection, \`aria-expanded\` for an open/close affordance \u2014 or replace the hand-rolled control with the state-carrying primitive (Checkbox / Toggle / Tabs / Accordion). \`as={Clickable}\` adds button + keyboard semantics but NOT state.`
|
|
2514
|
+
)
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
return issues;
|
|
2518
|
+
}
|
|
2519
|
+
var STATE_UI_AFFORDANCE_CHECKS = [
|
|
2520
|
+
{
|
|
2521
|
+
id: "state.ui_affordance.state_aria_present",
|
|
2522
|
+
axis: "state",
|
|
2523
|
+
values: ["ui-affordance"],
|
|
2524
|
+
run: runStateAriaPresent
|
|
2525
|
+
}
|
|
2526
|
+
];
|
|
2527
|
+
|
|
2161
2528
|
// src/evaluation/axis-checks/checks/realtime.ts
|
|
2162
2529
|
var REALTIME_ACTIVE = ["merge", "append", "status", "presence", "mixed"];
|
|
2163
2530
|
function runStreamHandlerPerEvent(input) {
|
|
@@ -2847,6 +3214,7 @@ var EXTRA_CHECKS = [
|
|
|
2847
3214
|
var REGISTRY = [
|
|
2848
3215
|
...UNIVERSAL_CHECKS,
|
|
2849
3216
|
...STATE_MERGE_CHECKS,
|
|
3217
|
+
...STATE_UI_AFFORDANCE_CHECKS,
|
|
2850
3218
|
...REALTIME_CHECKS,
|
|
2851
3219
|
...WRITES_CHECKS,
|
|
2852
3220
|
...STATE_PAYLOAD_CHECKS,
|
|
@@ -2874,7 +3242,25 @@ function runAxisChecks(classification, input) {
|
|
|
2874
3242
|
}
|
|
2875
3243
|
return issues;
|
|
2876
3244
|
}
|
|
3245
|
+
async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt) {
|
|
3246
|
+
const userPrompt = `## Original Request
|
|
3247
|
+
${originalPrompt}
|
|
3248
|
+
|
|
3249
|
+
Evaluate the screenshot of the generated component.`;
|
|
3250
|
+
const agent = createVisionAgent({
|
|
3251
|
+
provider: config.provider === "claude" ? "anthropic" : config.provider,
|
|
3252
|
+
routeOverride: config.routeOverride,
|
|
3253
|
+
onRetry: config.onRetry
|
|
3254
|
+
});
|
|
3255
|
+
return agent.callVision(
|
|
3256
|
+
model,
|
|
3257
|
+
prompt,
|
|
3258
|
+
userPrompt,
|
|
3259
|
+
{ mediaType: "image/png", base64: screenshot.toString("base64") },
|
|
3260
|
+
1500
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
2877
3263
|
|
|
2878
|
-
export { CRITERIA, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, MAX_EVAL_ROUNDS_HARD_LIMIT, buildCodingCriteriaSummary, buildFixPrompt, computeEvaluationScore, createEvaluationToolsServer, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, matches, priorityForIssue, runAxisChecks, runEvaluation, runEvaluationLoop };
|
|
3264
|
+
export { CRITERIA, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, MAX_EVAL_ROUNDS_HARD_LIMIT, buildCodingCriteriaSummary, buildFixPrompt, callMultimodalLLM, computeEvaluationScore, createEvaluationToolsServer, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, matches, priorityForIssue, runAxisChecks, runEvaluation, runEvaluationLoop };
|
|
2879
3265
|
//# sourceMappingURL=index.js.map
|
|
2880
3266
|
//# sourceMappingURL=index.js.map
|