@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.
Files changed (100) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +18 -1
  2. package/dist/adapters/generation-dispatch.js +819 -1577
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +28 -11
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +5 -3
  7. package/dist/advanced/index.js +838 -1579
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +109 -3
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.js +17 -4
  12. package/dist/boilerplate.js.map +1 -1
  13. package/dist/check/index.d.ts +1 -1
  14. package/dist/check/index.js +11 -1
  15. package/dist/check/index.js.map +1 -1
  16. package/dist/compose.js +5 -5
  17. package/dist/compose.js.map +1 -1
  18. package/dist/{create-ui-generator-GX7tvbyH.d.ts → create-ui-generator-CF0EL0Li.d.ts} +26 -1
  19. package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
  20. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  21. package/dist/evaluation/axis-checks/index.js +133 -1
  22. package/dist/evaluation/axis-checks/index.js.map +1 -1
  23. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  24. package/dist/evaluation/axis-checks/registry.js +133 -1
  25. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  26. package/dist/evaluation/index.d.ts +46 -3
  27. package/dist/evaluation/index.js +410 -24
  28. package/dist/evaluation/index.js.map +1 -1
  29. package/dist/fragments/index.js +5 -5
  30. package/dist/fragments/index.js.map +1 -1
  31. package/dist/harness/check/runtime-render/index.d.ts +2 -2
  32. package/dist/harness/check/runtime-render/index.js +110 -3
  33. package/dist/harness/check/runtime-render/index.js.map +1 -1
  34. package/dist/harness/index.d.ts +5 -126
  35. package/dist/harness/index.js +292 -32
  36. package/dist/harness/index.js.map +1 -1
  37. package/dist/harness/llm-trace-sink.d.ts +1 -1
  38. package/dist/harness/llm-trace-sink.js.map +1 -1
  39. package/dist/harness/prompts.js +17 -3
  40. package/dist/harness/prompts.js.map +1 -1
  41. package/dist/harness/result-types.d.ts +1 -1
  42. package/dist/harness/runtime.d.ts +17 -1
  43. package/dist/harness/runtime.js +23 -8
  44. package/dist/harness/runtime.js.map +1 -1
  45. package/dist/harness/types-public.d.ts +1 -1
  46. package/dist/harness/types-public.js +22 -9
  47. package/dist/harness/types-public.js.map +1 -1
  48. package/dist/harness/validator-trace-sink.d.ts +1 -1
  49. package/dist/index.d.ts +5 -3
  50. package/dist/index.js +838 -1579
  51. package/dist/index.js.map +1 -1
  52. package/dist/llm-router-BEVtbAb9.d.ts +213 -0
  53. package/dist/patch.d.ts +1 -1
  54. package/dist/provider-adapter.d.ts +16 -6
  55. package/dist/provider-adapter.js.map +1 -1
  56. package/dist/providers/index.d.ts +54 -99
  57. package/dist/providers/index.js +13 -8
  58. package/dist/providers/index.js.map +1 -1
  59. package/dist/{render-check-CNZjHD5g.d.ts → render-check-BNYGVDJd.d.ts} +1 -1
  60. package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
  61. package/dist/validation/index.js +2 -2
  62. package/dist/validation/index.js.map +1 -1
  63. package/dist/workflows.d.ts +1 -1
  64. package/package.json +18 -18
  65. package/src/adapters/generation-dispatch.ts +28 -2
  66. package/src/adapters/openrouter/client.ts +2 -2
  67. package/src/adapters/openrouter/types.ts +10 -0
  68. package/src/boilerplate/generate.ts +10 -2
  69. package/src/boilerplate/json-schema-ts.ts +22 -2
  70. package/src/boilerplate/system-prompt.ts +4 -2
  71. package/src/compile.ts +1 -1
  72. package/src/contract-context.ts +10 -2
  73. package/src/create-ui-generator.ts +58 -11
  74. package/src/design-system-docs.ts +6 -4
  75. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  76. package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
  77. package/src/evaluation/axis-checks/checks/universal.ts +39 -0
  78. package/src/evaluation/axis-checks/registry.ts +2 -0
  79. package/src/evaluation/index.ts +11 -0
  80. package/src/evaluation/llm-evaluator.ts +26 -3
  81. package/src/evaluation/visual-evaluator.ts +49 -66
  82. package/src/fragments/realtime.ts +3 -3
  83. package/src/fragments/state.ts +2 -2
  84. package/src/harness/check/runtime-render/adapter.ts +18 -0
  85. package/src/harness/check/runtime-render/index.ts +8 -0
  86. package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
  87. package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
  88. package/src/harness/check/runtime-render/render-check.ts +72 -1
  89. package/src/harness/coding/init-session.ts +4 -1
  90. package/src/harness/coding/run-eval-round.ts +67 -5
  91. package/src/harness/index.ts +1 -1
  92. package/src/harness/llm-router.ts +451 -27
  93. package/src/harness/llm-trace-sink.ts +2 -1
  94. package/src/harness/runtime.ts +15 -0
  95. package/src/index.ts +5 -5
  96. package/src/provider-adapter.ts +16 -6
  97. package/src/providers/bedrock.ts +89 -26
  98. package/src/providers/index.ts +16 -4
  99. package/src/tools/render-check.ts +19 -7
  100. package/src/validation/ui-compiler.ts +2 -2
@@ -10,7 +10,10 @@ import path, { dirname, resolve, join, basename } from 'path';
10
10
  import { createRequire } from 'module';
11
11
  import { Linter } from 'eslint';
12
12
  import { createHash } from 'crypto';
13
+ import 'fs/promises';
13
14
  import { tmpdir } from 'os';
15
+ import { Readable } from 'stream';
16
+ import { pipeline } from 'stream/promises';
14
17
  import { createBrotliDecompress, createUnzip } from 'zlib';
15
18
  import { fileURLToPath } from 'url';
16
19
  import '@ggui-ai/sandbox';
@@ -79,6 +82,15 @@ function jsonSchemaTypeToTs(schema) {
79
82
  if (schema.enum?.length) {
80
83
  return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
81
84
  }
85
+ if (Array.isArray(schema.type)) {
86
+ const { nullable: _nullable, ...rest } = schema;
87
+ const members = [...new Set(schema.type)].map(
88
+ (member) => jsonSchemaTypeToTs({ ...rest, type: member })
89
+ );
90
+ const unique = [...new Set(members)];
91
+ if (schema.nullable && !unique.includes("null")) unique.push("null");
92
+ return unique.join(" | ");
93
+ }
82
94
  let result;
83
95
  switch (schema.type) {
84
96
  case "string":
@@ -96,7 +108,8 @@ function jsonSchemaTypeToTs(schema) {
96
108
  case "array": {
97
109
  if (schema.items) {
98
110
  const itemType = jsonSchemaTypeToTs(schema.items);
99
- result = schema.items.type === "object" && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
111
+ const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
112
+ result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
100
113
  } else {
101
114
  result = "unknown[]";
102
115
  }
@@ -549,14 +562,16 @@ var OpenRouterError;
549
562
  var init_types = __esm({
550
563
  "src/adapters/openrouter/types.ts"() {
551
564
  OpenRouterError = class extends Error {
552
- constructor(message, status, code) {
565
+ constructor(message, status, code, headers) {
553
566
  super(message);
554
567
  this.status = status;
555
568
  this.code = code;
569
+ this.headers = headers;
556
570
  this.name = "OpenRouterError";
557
571
  }
558
572
  status;
559
573
  code;
574
+ headers;
560
575
  get isTransient() {
561
576
  return this.status === 408 || this.status === 429 || this.status === 502 || this.status === 503;
562
577
  }
@@ -601,7 +616,7 @@ var init_client2 = __esm({
601
616
  if (!response.ok) {
602
617
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
603
618
  const message = errorBody.error?.message ?? response.statusText;
604
- throw new OpenRouterError(message, response.status);
619
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
605
620
  }
606
621
  return response.json();
607
622
  }
@@ -619,7 +634,7 @@ var init_client2 = __esm({
619
634
  if (!response.ok) {
620
635
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
621
636
  const message = errorBody.error?.message ?? response.statusText;
622
- throw new OpenRouterError(message, response.status);
637
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
623
638
  }
624
639
  if (!response.body) {
625
640
  throw new OpenRouterError("No response body for streaming request", 500);
@@ -687,6 +702,29 @@ function errorSummary(e) {
687
702
  }
688
703
  return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
689
704
  }
705
+ function isRateLimited(e) {
706
+ if (!(e instanceof Error) || !("status" in e)) return false;
707
+ return e.status === 429;
708
+ }
709
+ function extractStatus(e) {
710
+ if (!(e instanceof Error) || !("status" in e)) return void 0;
711
+ const status = e.status;
712
+ return typeof status === "number" ? status : void 0;
713
+ }
714
+ function extractRetryAfterSec(e) {
715
+ if (!(e instanceof Error) || !("headers" in e)) return void 0;
716
+ const headers = e.headers;
717
+ if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
718
+ return void 0;
719
+ }
720
+ const raw = headers.get("retry-after");
721
+ if (raw === null) return void 0;
722
+ const parsed = Number(raw);
723
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
724
+ }
725
+ function sleep(ms) {
726
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
727
+ }
690
728
  function interactionText(interaction) {
691
729
  let text = "";
692
730
  for (const step of interaction.steps ?? []) {
@@ -708,25 +746,41 @@ function functionCallSteps(interaction) {
708
746
  function toUserInputStep(text) {
709
747
  return { type: "user_input", content: [{ type: "text", text }] };
710
748
  }
711
- function createAgent(provider) {
749
+ function createAgent(providerOrConfig) {
750
+ const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
751
+ const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
752
+ const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
712
753
  switch (provider) {
713
754
  case "anthropic":
714
- return new AnthropicAgent();
755
+ return new AnthropicAgent(routeOverride, onRetry);
715
756
  case "openai":
716
- return new OpenAIAgent();
757
+ return new OpenAIAgent(routeOverride, onRetry);
717
758
  case "google":
718
- return new GoogleAgent();
759
+ return new GoogleAgent(routeOverride, onRetry);
719
760
  case "openrouter":
720
- return new OpenRouterAgent();
761
+ return new OpenRouterAgent(routeOverride, onRetry);
762
+ }
763
+ }
764
+ function createVisionAgent(config) {
765
+ switch (config.provider) {
766
+ case "anthropic":
767
+ return new AnthropicAgent(config.routeOverride, config.onRetry);
768
+ case "google":
769
+ return new GoogleAgent(config.routeOverride, config.onRetry);
721
770
  }
722
771
  }
723
- var LLMAgent, AnthropicAgent, OpenAIAgent, GoogleAgent, OpenRouterAgent;
772
+ var MAX_RETRY_ATTEMPTS, RETRY_AFTER_CAP_SEC, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_MAX_MS, MAX_TOTAL_RETRY_DELAY_MS, LLMAgent, AnthropicAgent, OpenAIAgent, GoogleAgent, OpenRouterAgent;
724
773
  var init_llm_router = __esm({
725
774
  "src/harness/llm-router.ts"() {
726
775
  init_client();
727
776
  init_provider_router();
728
777
  init_tool_bridge();
729
778
  init_llm_trace_sink();
779
+ MAX_RETRY_ATTEMPTS = 2;
780
+ RETRY_AFTER_CAP_SEC = 15;
781
+ DEFAULT_BACKOFF_BASE_MS = 1e3;
782
+ DEFAULT_BACKOFF_MAX_MS = 8e3;
783
+ MAX_TOTAL_RETRY_DELAY_MS = 2e4;
730
784
  LLMAgent = class {
731
785
  client = null;
732
786
  // ── Session state for server-side chaining ──────────────
@@ -734,6 +788,19 @@ var init_llm_router = __esm({
734
788
  // Enables turn 2+ to skip re-sending system prompt + history.
735
789
  // Call resetSession() between independent generation runs.
736
790
  lastSessionId;
791
+ /**
792
+ * Explicit per-call routing override — see `AgentConfig.routeOverride`.
793
+ * Absent when constructed via the bare `new XAgent()` form (every
794
+ * existing call site), so `createClient()`/`resolveModel()` fall
795
+ * back to `process.env` exactly as before.
796
+ */
797
+ routeOverride;
798
+ /** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
799
+ onRetry;
800
+ constructor(routeOverride, onRetry) {
801
+ this.routeOverride = routeOverride;
802
+ this.onRetry = onRetry;
803
+ }
737
804
  async getClient() {
738
805
  if (!this.client) {
739
806
  this.client = await this.createClient();
@@ -767,36 +834,104 @@ var init_llm_router = __esm({
767
834
  async sendToolResult(_results) {
768
835
  }
769
836
  /**
770
- * Execute an API call. No retry — if it fails, it fails.
771
- * Logs the error with provider context and re-throws.
837
+ * Execute an API call, retrying once or twice on HTTP 429
838
+ * (rate-limited) before giving up (#489).
839
+ *
840
+ * Policy: on a 429, honor the provider's `Retry-After` header when
841
+ * present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
842
+ * longer wait is treated as "don't retry", not "wait longer": making
843
+ * the caller sit through 15+ more seconds on a single failed call is
844
+ * a bad experience regardless of what's driving the request);
845
+ * otherwise fall back to exponential backoff with jitter, capped at
846
+ * `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
847
+ * `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
848
+ * exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
849
+ * non-429 error, or a 429 with no attempts left, is logged and
850
+ * re-thrown immediately — unchanged from the pre-#489 behavior.
851
+ *
852
+ * **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
853
+ * bounds a single `apiCall()` invocation, not a whole generation. A
854
+ * generation issues many sequential `apiCall()` calls (coding turns,
855
+ * tool-result round-trips, eval rounds); under sustained throttling
856
+ * each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
857
+ * a generation with k rate-limited calls can add up to roughly `k *
858
+ * MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
859
+ * resources (e.g. the caller's concurrency slot) — not a flat 20s
860
+ * ceiling. No shared per-generation budget is threaded here by
861
+ * design (accepted ruling, not an oversight): the caller's own
862
+ * queue/admission backstop is what bounds how long other
863
+ * queued work waits, independent of any one generation's retry
864
+ * total.
772
865
  */
773
866
  async apiCall(fn) {
774
867
  const start = Date.now();
775
- try {
776
- return await fn();
777
- } catch (e) {
778
- const ms = Date.now() - start;
779
- console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
780
- throw e;
868
+ let totalDelayMs = 0;
869
+ for (let attempt = 0; ; attempt++) {
870
+ try {
871
+ return await fn();
872
+ } catch (e) {
873
+ const ms = Date.now() - start;
874
+ if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
875
+ console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
876
+ throw e;
877
+ }
878
+ const retryAfterSec = extractRetryAfterSec(e);
879
+ if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
880
+ console.error(
881
+ `[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
882
+ );
883
+ throw e;
884
+ }
885
+ const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
886
+ if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
887
+ console.error(
888
+ `[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
889
+ );
890
+ throw e;
891
+ }
892
+ totalDelayMs += delayMs;
893
+ const info = {
894
+ provider: this.provider,
895
+ attempt: attempt + 1,
896
+ maxAttempts: MAX_RETRY_ATTEMPTS,
897
+ status: extractStatus(e),
898
+ retryAfterSec,
899
+ delayMs,
900
+ message: errorSummary(e)
901
+ };
902
+ console.warn(
903
+ `[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
904
+ );
905
+ try {
906
+ this.onRetry?.(info);
907
+ } catch (observerError) {
908
+ console.warn(
909
+ `[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
910
+ );
911
+ }
912
+ await sleep(delayMs);
913
+ }
781
914
  }
782
915
  }
783
916
  };
784
917
  AnthropicAgent = class extends LLMAgent {
785
918
  provider = "anthropic";
786
919
  resolveModel(model) {
787
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
920
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
921
+ if (useBedrock) {
788
922
  return getBedrockModelId(model);
789
923
  }
790
924
  return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
791
925
  }
792
926
  async createClient() {
793
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
927
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
928
+ if (useBedrock) {
794
929
  const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk');
795
930
  return new AnthropicBedrock({
796
931
  awsRegion: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
797
932
  });
798
933
  }
799
- return createAnthropicClient(process.env.ANTHROPIC_API_KEY);
934
+ return createAnthropicClient(this.routeOverride?.apiKey ?? process.env.ANTHROPIC_API_KEY);
800
935
  }
801
936
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
802
937
  const client = await this.getClient();
@@ -868,6 +1003,73 @@ var init_llm_router = __esm({
868
1003
  throw e;
869
1004
  }
870
1005
  }
1006
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
1007
+ const client = await this.getClient();
1008
+ const traceId = newLlmTraceId();
1009
+ const startedAt = Date.now();
1010
+ const resolvedModel = this.resolveModel(model);
1011
+ const imageBlock = {
1012
+ type: "image",
1013
+ source: {
1014
+ type: "base64",
1015
+ media_type: image.mediaType,
1016
+ data: image.base64
1017
+ }
1018
+ };
1019
+ try {
1020
+ const response = await this.apiCall(
1021
+ () => client.messages.create({
1022
+ model: resolvedModel,
1023
+ max_tokens: maxTokens ?? 1500,
1024
+ system: systemPrompt,
1025
+ messages: [
1026
+ {
1027
+ role: "user",
1028
+ content: [imageBlock, { type: "text", text: userPrompt }]
1029
+ }
1030
+ ]
1031
+ })
1032
+ );
1033
+ const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
1034
+ const endedAt = Date.now();
1035
+ emitLlmTraceEvent({
1036
+ id: traceId,
1037
+ at: startedAt,
1038
+ endedAt,
1039
+ durationMs: endedAt - startedAt,
1040
+ provider: "anthropic",
1041
+ model: resolvedModel,
1042
+ kind: "callVision",
1043
+ systemPrompt,
1044
+ userPrompt,
1045
+ result: {
1046
+ inputTokens: response.usage.input_tokens,
1047
+ outputTokens: response.usage.output_tokens,
1048
+ text
1049
+ }
1050
+ });
1051
+ return {
1052
+ text,
1053
+ inputTokens: response.usage.input_tokens,
1054
+ outputTokens: response.usage.output_tokens
1055
+ };
1056
+ } catch (e) {
1057
+ const endedAt = Date.now();
1058
+ emitLlmTraceEvent({
1059
+ id: traceId,
1060
+ at: startedAt,
1061
+ endedAt,
1062
+ durationMs: endedAt - startedAt,
1063
+ provider: "anthropic",
1064
+ model: resolvedModel,
1065
+ kind: "callVision",
1066
+ systemPrompt,
1067
+ userPrompt,
1068
+ error: { message: e instanceof Error ? e.message : String(e) }
1069
+ });
1070
+ throw e;
1071
+ }
1072
+ }
871
1073
  async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
872
1074
  const client = await this.getClient();
873
1075
  const traceId = newLlmTraceId();
@@ -1067,7 +1269,7 @@ var init_llm_router = __esm({
1067
1269
  }
1068
1270
  async createClient() {
1069
1271
  const { default: OpenAISDK } = await import('openai');
1070
- return new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY });
1272
+ return new OpenAISDK({ apiKey: this.routeOverride?.apiKey ?? process.env.OPENAI_API_KEY });
1071
1273
  }
1072
1274
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
1073
1275
  const client = await this.getClient();
@@ -1217,7 +1419,7 @@ var init_llm_router = __esm({
1217
1419
  async createClient() {
1218
1420
  const { GoogleGenAI: GoogleGenAISDK } = await import('@google/genai');
1219
1421
  return new GoogleGenAISDK({
1220
- apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
1422
+ apiKey: this.routeOverride?.apiKey ?? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
1221
1423
  httpOptions: { timeout: 3e5 }
1222
1424
  // 5 min — Pro models can be slow
1223
1425
  });
@@ -1242,6 +1444,72 @@ var init_llm_router = __esm({
1242
1444
  outputTokens: interaction.usage?.total_output_tokens ?? 0
1243
1445
  };
1244
1446
  }
1447
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
1448
+ const client = await this.getClient();
1449
+ const traceId = newLlmTraceId();
1450
+ const startedAt = Date.now();
1451
+ const resolvedModel = this.resolveModel(model);
1452
+ try {
1453
+ const response = await this.apiCall(
1454
+ () => client.models.generateContent({
1455
+ model: resolvedModel,
1456
+ contents: [
1457
+ {
1458
+ role: "user",
1459
+ parts: [
1460
+ { inlineData: { mimeType: image.mediaType, data: image.base64 } },
1461
+ { text: userPrompt }
1462
+ ]
1463
+ }
1464
+ ],
1465
+ config: {
1466
+ systemInstruction: systemPrompt,
1467
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {}
1468
+ }
1469
+ })
1470
+ );
1471
+ const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
1472
+ const usage = response.usageMetadata;
1473
+ const result = {
1474
+ text,
1475
+ inputTokens: usage?.promptTokenCount ?? 0,
1476
+ outputTokens: usage?.candidatesTokenCount ?? 0
1477
+ };
1478
+ const endedAt = Date.now();
1479
+ emitLlmTraceEvent({
1480
+ id: traceId,
1481
+ at: startedAt,
1482
+ endedAt,
1483
+ durationMs: endedAt - startedAt,
1484
+ provider: "google",
1485
+ model: resolvedModel,
1486
+ kind: "callVision",
1487
+ systemPrompt,
1488
+ userPrompt,
1489
+ result: {
1490
+ inputTokens: result.inputTokens,
1491
+ outputTokens: result.outputTokens,
1492
+ text
1493
+ }
1494
+ });
1495
+ return result;
1496
+ } catch (e) {
1497
+ const endedAt = Date.now();
1498
+ emitLlmTraceEvent({
1499
+ id: traceId,
1500
+ at: startedAt,
1501
+ endedAt,
1502
+ durationMs: endedAt - startedAt,
1503
+ provider: "google",
1504
+ model: resolvedModel,
1505
+ kind: "callVision",
1506
+ systemPrompt,
1507
+ userPrompt,
1508
+ error: { message: e instanceof Error ? e.message : String(e) }
1509
+ });
1510
+ throw e;
1511
+ }
1512
+ }
1245
1513
  async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
1246
1514
  const client = await this.getClient();
1247
1515
  const interactionTools = tools.map((t) => ({
@@ -1444,7 +1712,7 @@ var init_llm_router = __esm({
1444
1712
  async createClient() {
1445
1713
  const { OpenRouterClient: OpenRouterClient2 } = await Promise.resolve().then(() => (init_client2(), client_exports));
1446
1714
  return new OpenRouterClient2({
1447
- apiKey: process.env.OPENROUTER_API_KEY
1715
+ apiKey: this.routeOverride?.apiKey ?? process.env.OPENROUTER_API_KEY
1448
1716
  });
1449
1717
  }
1450
1718
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
@@ -2239,8 +2507,8 @@ var init_ui_compiler = __esm({
2239
2507
  "@ggui-ai/design/*",
2240
2508
  "@ggui-ai/wire",
2241
2509
  "@ggui-ai/wire/*",
2242
- "@ggui-ai/react",
2243
- "@ggui-ai/react/*"
2510
+ "@ggui-ai/mcp-apps-react",
2511
+ "@ggui-ai/mcp-apps-react/*"
2244
2512
  ];
2245
2513
  MAX_BUNDLE_SIZE = 2 * 1024 * 1024;
2246
2514
  UiValidationError = class extends Error {
@@ -4708,1414 +4976,126 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
4708
4976
  const model = config.model || getDefaultEvalModel(providerName);
4709
4977
  const routerProvider = providerName === "claude" ? "anthropic" : providerName;
4710
4978
  const motherPrompt = preWarmedContext?.motherPrompt ?? buildMotherPrompt({
4711
- originalPrompt: context.originalPrompt,
4712
- contract: context.contract,
4713
- designSystemSummary: context.designContext
4714
- });
4715
- const numberedSource = context.sourceCode.split("\n").map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
4716
- const agent = createAgent(routerProvider);
4717
- const evalStart = Date.now();
4718
- const totalCriteriaCount = TIER_1_CRITERIA.length + TIER_2_CRITERIA.length;
4719
- const results = await Promise.all([
4720
- ...TIER_1_CRITERIA.map((c) => evalCriterion(c, 1, motherPrompt, numberedSource, agent, model)),
4721
- ...TIER_2_CRITERIA.map((c) => evalCriterion(c, 2, motherPrompt, numberedSource, agent, model))
4722
- ]);
4723
- const allIssues = [];
4724
- const passCategories = [];
4725
- let totalIn = 0;
4726
- let totalOut = 0;
4727
- let cachedTokensEstimate = 0;
4728
- for (const r of results) {
4729
- allIssues.push(...r.issues);
4730
- if (r.pass) {
4731
- passCategories.push(r.criterion);
4732
- }
4733
- totalIn += r.tokens.in;
4734
- totalOut += r.tokens.out;
4735
- }
4736
- const firstResult = results[0];
4737
- if (firstResult && firstResult.tokens.in > 0) {
4738
- cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
4739
- }
4740
- const wallMs = Date.now() - evalStart;
4741
- for (const r of results) {
4742
- const issueCount = r.issues.length;
4743
- const status = r.pass ? "PASS" : r.tier === 1 ? "FAIL" : r.issues.some((i) => i.result === "fail") ? "FAIL" : "WARN";
4744
- const issueLabel = issueCount > 0 ? ` (${issueCount} issue${issueCount > 1 ? "s" : ""})` : "";
4745
- console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
4746
- }
4747
- console.log(
4748
- `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cached~=${cachedTokensEstimate} | issues=${allIssues.length} pass=${passCategories.length}`
4749
- );
4750
- return {
4751
- issues: allIssues,
4752
- pass: passCategories,
4753
- inputTokens: totalIn,
4754
- outputTokens: totalOut
4755
- };
4756
- }
4757
- var TIER_1_CRITERIA, TIER_2_CRITERIA, TIER_1_CATEGORIES, TIER_2_CATEGORIES, VALID_CATEGORIES;
4758
- var init_llm_evaluator = __esm({
4759
- "src/evaluation/llm-evaluator.ts"() {
4760
- init_llm_router();
4761
- init_types_public();
4762
- TIER_1_CRITERIA = ["functionality", "crash"];
4763
- TIER_2_CRITERIA = ["interactivity", "accessibility", "layout", "loading", "visual"];
4764
- TIER_1_CATEGORIES = /* @__PURE__ */ new Set(["functionality", "crash"]);
4765
- TIER_2_CATEGORIES = /* @__PURE__ */ new Set([
4766
- "interactivity",
4767
- "accessibility",
4768
- "layout",
4769
- "loading",
4770
- "visual"
4771
- ]);
4772
- VALID_CATEGORIES = /* @__PURE__ */ new Set([
4773
- ...TIER_1_CATEGORIES,
4774
- ...TIER_2_CATEGORIES
4775
- ]);
4776
- }
4777
- });
4778
-
4779
- // src/evaluation/cost-tracker.ts
4780
- var cost_tracker_exports = {};
4781
- __export(cost_tracker_exports, {
4782
- CostTracker: () => CostTracker,
4783
- pricePer1k: () => pricePer1k
4784
- });
4785
- function resolveRegistryId(model) {
4786
- const keys = Object.keys(MODEL_REGISTRY);
4787
- if (keys.includes(model)) return model;
4788
- const bySuffix = keys.find((id) => id.endsWith(`/${model}`));
4789
- if (bySuffix) return bySuffix;
4790
- const undated = model.replace(/-\d{8}$/, "");
4791
- return keys.find((id) => id.endsWith(`/${undated}`)) ?? null;
4792
- }
4793
- function pricePer1k(model) {
4794
- const id = resolveRegistryId(model);
4795
- if (id === null) return FALLBACK_PER_1K;
4796
- const { costs } = MODEL_REGISTRY[id];
4797
- return { input: costs.inputPer1M / 1e3, output: costs.outputPer1M / 1e3 };
4798
- }
4799
- var FALLBACK_PER_1K, CostTracker;
4800
- var init_cost_tracker = __esm({
4801
- "src/evaluation/cost-tracker.ts"() {
4802
- FALLBACK_PER_1K = { input: 3e-3, output: 0.015 };
4803
- CostTracker = class {
4804
- constructor(maxBudget) {
4805
- this.maxBudget = maxBudget;
4806
- }
4807
- maxBudget;
4808
- totalCost = 0;
4809
- record(model, inputTokens, outputTokens) {
4810
- const prices = pricePer1k(model);
4811
- const cost = inputTokens / 1e3 * prices.input + outputTokens / 1e3 * prices.output;
4812
- this.totalCost += cost;
4813
- return cost;
4814
- }
4815
- canContinue() {
4816
- if (this.maxBudget === null) return true;
4817
- return this.totalCost < this.maxBudget;
4818
- }
4819
- getTotal() {
4820
- return this.totalCost;
4821
- }
4822
- getRemaining() {
4823
- if (this.maxBudget === null) return null;
4824
- return Math.max(0, this.maxBudget - this.totalCost);
4825
- }
4826
- };
4827
- }
4828
- });
4829
-
4830
- // ../../node_modules/ms/index.js
4831
- var require_ms = __commonJS({
4832
- "../../node_modules/ms/index.js"(exports, module) {
4833
- var s = 1e3;
4834
- var m = s * 60;
4835
- var h = m * 60;
4836
- var d = h * 24;
4837
- var w = d * 7;
4838
- var y = d * 365.25;
4839
- module.exports = function(val, options) {
4840
- options = options || {};
4841
- var type = typeof val;
4842
- if (type === "string" && val.length > 0) {
4843
- return parse(val);
4844
- } else if (type === "number" && isFinite(val)) {
4845
- return options.long ? fmtLong(val) : fmtShort(val);
4846
- }
4847
- throw new Error(
4848
- "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
4849
- );
4850
- };
4851
- function parse(str) {
4852
- str = String(str);
4853
- if (str.length > 100) {
4854
- return;
4855
- }
4856
- var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
4857
- str
4858
- );
4859
- if (!match) {
4860
- return;
4861
- }
4862
- var n = parseFloat(match[1]);
4863
- var type = (match[2] || "ms").toLowerCase();
4864
- switch (type) {
4865
- case "years":
4866
- case "year":
4867
- case "yrs":
4868
- case "yr":
4869
- case "y":
4870
- return n * y;
4871
- case "weeks":
4872
- case "week":
4873
- case "w":
4874
- return n * w;
4875
- case "days":
4876
- case "day":
4877
- case "d":
4878
- return n * d;
4879
- case "hours":
4880
- case "hour":
4881
- case "hrs":
4882
- case "hr":
4883
- case "h":
4884
- return n * h;
4885
- case "minutes":
4886
- case "minute":
4887
- case "mins":
4888
- case "min":
4889
- case "m":
4890
- return n * m;
4891
- case "seconds":
4892
- case "second":
4893
- case "secs":
4894
- case "sec":
4895
- case "s":
4896
- return n * s;
4897
- case "milliseconds":
4898
- case "millisecond":
4899
- case "msecs":
4900
- case "msec":
4901
- case "ms":
4902
- return n;
4903
- default:
4904
- return void 0;
4905
- }
4906
- }
4907
- function fmtShort(ms) {
4908
- var msAbs = Math.abs(ms);
4909
- if (msAbs >= d) {
4910
- return Math.round(ms / d) + "d";
4911
- }
4912
- if (msAbs >= h) {
4913
- return Math.round(ms / h) + "h";
4914
- }
4915
- if (msAbs >= m) {
4916
- return Math.round(ms / m) + "m";
4917
- }
4918
- if (msAbs >= s) {
4919
- return Math.round(ms / s) + "s";
4920
- }
4921
- return ms + "ms";
4922
- }
4923
- function fmtLong(ms) {
4924
- var msAbs = Math.abs(ms);
4925
- if (msAbs >= d) {
4926
- return plural(ms, msAbs, d, "day");
4927
- }
4928
- if (msAbs >= h) {
4929
- return plural(ms, msAbs, h, "hour");
4930
- }
4931
- if (msAbs >= m) {
4932
- return plural(ms, msAbs, m, "minute");
4933
- }
4934
- if (msAbs >= s) {
4935
- return plural(ms, msAbs, s, "second");
4936
- }
4937
- return ms + " ms";
4938
- }
4939
- function plural(ms, msAbs, n, name) {
4940
- var isPlural = msAbs >= n * 1.5;
4941
- return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
4942
- }
4943
- }
4944
- });
4945
-
4946
- // ../../node_modules/debug/src/common.js
4947
- var require_common = __commonJS({
4948
- "../../node_modules/debug/src/common.js"(exports, module) {
4949
- function setup(env) {
4950
- createDebug.debug = createDebug;
4951
- createDebug.default = createDebug;
4952
- createDebug.coerce = coerce;
4953
- createDebug.disable = disable;
4954
- createDebug.enable = enable;
4955
- createDebug.enabled = enabled;
4956
- createDebug.humanize = require_ms();
4957
- createDebug.destroy = destroy;
4958
- Object.keys(env).forEach((key) => {
4959
- createDebug[key] = env[key];
4960
- });
4961
- createDebug.names = [];
4962
- createDebug.skips = [];
4963
- createDebug.formatters = {};
4964
- function selectColor(namespace) {
4965
- let hash = 0;
4966
- for (let i = 0; i < namespace.length; i++) {
4967
- hash = (hash << 5) - hash + namespace.charCodeAt(i);
4968
- hash |= 0;
4969
- }
4970
- return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
4971
- }
4972
- createDebug.selectColor = selectColor;
4973
- function createDebug(namespace) {
4974
- let prevTime;
4975
- let enableOverride = null;
4976
- let namespacesCache;
4977
- let enabledCache;
4978
- function debug(...args) {
4979
- if (!debug.enabled) {
4980
- return;
4981
- }
4982
- const self = debug;
4983
- const curr = Number(/* @__PURE__ */ new Date());
4984
- const ms = curr - (prevTime || curr);
4985
- self.diff = ms;
4986
- self.prev = prevTime;
4987
- self.curr = curr;
4988
- prevTime = curr;
4989
- args[0] = createDebug.coerce(args[0]);
4990
- if (typeof args[0] !== "string") {
4991
- args.unshift("%O");
4992
- }
4993
- let index = 0;
4994
- args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
4995
- if (match === "%%") {
4996
- return "%";
4997
- }
4998
- index++;
4999
- const formatter = createDebug.formatters[format];
5000
- if (typeof formatter === "function") {
5001
- const val = args[index];
5002
- match = formatter.call(self, val);
5003
- args.splice(index, 1);
5004
- index--;
5005
- }
5006
- return match;
5007
- });
5008
- createDebug.formatArgs.call(self, args);
5009
- const logFn = self.log || createDebug.log;
5010
- logFn.apply(self, args);
5011
- }
5012
- debug.namespace = namespace;
5013
- debug.useColors = createDebug.useColors();
5014
- debug.color = createDebug.selectColor(namespace);
5015
- debug.extend = extend;
5016
- debug.destroy = createDebug.destroy;
5017
- Object.defineProperty(debug, "enabled", {
5018
- enumerable: true,
5019
- configurable: false,
5020
- get: () => {
5021
- if (enableOverride !== null) {
5022
- return enableOverride;
5023
- }
5024
- if (namespacesCache !== createDebug.namespaces) {
5025
- namespacesCache = createDebug.namespaces;
5026
- enabledCache = createDebug.enabled(namespace);
5027
- }
5028
- return enabledCache;
5029
- },
5030
- set: (v) => {
5031
- enableOverride = v;
5032
- }
5033
- });
5034
- if (typeof createDebug.init === "function") {
5035
- createDebug.init(debug);
5036
- }
5037
- return debug;
5038
- }
5039
- function extend(namespace, delimiter) {
5040
- const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
5041
- newDebug.log = this.log;
5042
- return newDebug;
5043
- }
5044
- function enable(namespaces) {
5045
- createDebug.save(namespaces);
5046
- createDebug.namespaces = namespaces;
5047
- createDebug.names = [];
5048
- createDebug.skips = [];
5049
- const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
5050
- for (const ns of split) {
5051
- if (ns[0] === "-") {
5052
- createDebug.skips.push(ns.slice(1));
5053
- } else {
5054
- createDebug.names.push(ns);
5055
- }
5056
- }
5057
- }
5058
- function matchesTemplate(search, template) {
5059
- let searchIndex = 0;
5060
- let templateIndex = 0;
5061
- let starIndex = -1;
5062
- let matchIndex = 0;
5063
- while (searchIndex < search.length) {
5064
- if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
5065
- if (template[templateIndex] === "*") {
5066
- starIndex = templateIndex;
5067
- matchIndex = searchIndex;
5068
- templateIndex++;
5069
- } else {
5070
- searchIndex++;
5071
- templateIndex++;
5072
- }
5073
- } else if (starIndex !== -1) {
5074
- templateIndex = starIndex + 1;
5075
- matchIndex++;
5076
- searchIndex = matchIndex;
5077
- } else {
5078
- return false;
5079
- }
5080
- }
5081
- while (templateIndex < template.length && template[templateIndex] === "*") {
5082
- templateIndex++;
5083
- }
5084
- return templateIndex === template.length;
5085
- }
5086
- function disable() {
5087
- const namespaces = [
5088
- ...createDebug.names,
5089
- ...createDebug.skips.map((namespace) => "-" + namespace)
5090
- ].join(",");
5091
- createDebug.enable("");
5092
- return namespaces;
5093
- }
5094
- function enabled(name) {
5095
- for (const skip of createDebug.skips) {
5096
- if (matchesTemplate(name, skip)) {
5097
- return false;
5098
- }
5099
- }
5100
- for (const ns of createDebug.names) {
5101
- if (matchesTemplate(name, ns)) {
5102
- return true;
5103
- }
5104
- }
5105
- return false;
5106
- }
5107
- function coerce(val) {
5108
- if (val instanceof Error) {
5109
- return val.stack || val.message;
5110
- }
5111
- return val;
5112
- }
5113
- function destroy() {
5114
- console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
5115
- }
5116
- createDebug.enable(createDebug.load());
5117
- return createDebug;
5118
- }
5119
- module.exports = setup;
5120
- }
5121
- });
5122
-
5123
- // ../../node_modules/debug/src/browser.js
5124
- var require_browser = __commonJS({
5125
- "../../node_modules/debug/src/browser.js"(exports, module) {
5126
- exports.formatArgs = formatArgs;
5127
- exports.save = save;
5128
- exports.load = load;
5129
- exports.useColors = useColors;
5130
- exports.storage = localstorage();
5131
- exports.destroy = /* @__PURE__ */ (() => {
5132
- let warned = false;
5133
- return () => {
5134
- if (!warned) {
5135
- warned = true;
5136
- console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
5137
- }
5138
- };
5139
- })();
5140
- exports.colors = [
5141
- "#0000CC",
5142
- "#0000FF",
5143
- "#0033CC",
5144
- "#0033FF",
5145
- "#0066CC",
5146
- "#0066FF",
5147
- "#0099CC",
5148
- "#0099FF",
5149
- "#00CC00",
5150
- "#00CC33",
5151
- "#00CC66",
5152
- "#00CC99",
5153
- "#00CCCC",
5154
- "#00CCFF",
5155
- "#3300CC",
5156
- "#3300FF",
5157
- "#3333CC",
5158
- "#3333FF",
5159
- "#3366CC",
5160
- "#3366FF",
5161
- "#3399CC",
5162
- "#3399FF",
5163
- "#33CC00",
5164
- "#33CC33",
5165
- "#33CC66",
5166
- "#33CC99",
5167
- "#33CCCC",
5168
- "#33CCFF",
5169
- "#6600CC",
5170
- "#6600FF",
5171
- "#6633CC",
5172
- "#6633FF",
5173
- "#66CC00",
5174
- "#66CC33",
5175
- "#9900CC",
5176
- "#9900FF",
5177
- "#9933CC",
5178
- "#9933FF",
5179
- "#99CC00",
5180
- "#99CC33",
5181
- "#CC0000",
5182
- "#CC0033",
5183
- "#CC0066",
5184
- "#CC0099",
5185
- "#CC00CC",
5186
- "#CC00FF",
5187
- "#CC3300",
5188
- "#CC3333",
5189
- "#CC3366",
5190
- "#CC3399",
5191
- "#CC33CC",
5192
- "#CC33FF",
5193
- "#CC6600",
5194
- "#CC6633",
5195
- "#CC9900",
5196
- "#CC9933",
5197
- "#CCCC00",
5198
- "#CCCC33",
5199
- "#FF0000",
5200
- "#FF0033",
5201
- "#FF0066",
5202
- "#FF0099",
5203
- "#FF00CC",
5204
- "#FF00FF",
5205
- "#FF3300",
5206
- "#FF3333",
5207
- "#FF3366",
5208
- "#FF3399",
5209
- "#FF33CC",
5210
- "#FF33FF",
5211
- "#FF6600",
5212
- "#FF6633",
5213
- "#FF9900",
5214
- "#FF9933",
5215
- "#FFCC00",
5216
- "#FFCC33"
5217
- ];
5218
- function useColors() {
5219
- if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) {
5220
- return true;
5221
- }
5222
- if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
5223
- return false;
5224
- }
5225
- let m;
5226
- return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
5227
- typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
5228
- // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
5229
- typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
5230
- typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
5231
- }
5232
- function formatArgs(args) {
5233
- args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff);
5234
- if (!this.useColors) {
5235
- return;
5236
- }
5237
- const c = "color: " + this.color;
5238
- args.splice(1, 0, c, "color: inherit");
5239
- let index = 0;
5240
- let lastC = 0;
5241
- args[0].replace(/%[a-zA-Z%]/g, (match) => {
5242
- if (match === "%%") {
5243
- return;
5244
- }
5245
- index++;
5246
- if (match === "%c") {
5247
- lastC = index;
5248
- }
5249
- });
5250
- args.splice(lastC, 0, c);
5251
- }
5252
- exports.log = console.debug || console.log || (() => {
5253
- });
5254
- function save(namespaces) {
5255
- try {
5256
- if (namespaces) {
5257
- exports.storage.setItem("debug", namespaces);
5258
- } else {
5259
- exports.storage.removeItem("debug");
5260
- }
5261
- } catch (error) {
5262
- }
5263
- }
5264
- function load() {
5265
- let r;
5266
- try {
5267
- r = exports.storage.getItem("debug") || exports.storage.getItem("DEBUG");
5268
- } catch (error) {
5269
- }
5270
- if (!r && typeof process !== "undefined" && "env" in process) {
5271
- r = process.env.DEBUG;
5272
- }
5273
- return r;
5274
- }
5275
- function localstorage() {
5276
- try {
5277
- return localStorage;
5278
- } catch (error) {
5279
- }
5280
- }
5281
- module.exports = require_common()(exports);
5282
- var { formatters } = module.exports;
5283
- formatters.j = function(v) {
5284
- try {
5285
- return JSON.stringify(v);
5286
- } catch (error) {
5287
- return "[UnexpectedJSONParseError]: " + error.message;
5288
- }
5289
- };
5290
- }
5291
- });
5292
-
5293
- // ../../node_modules/has-flag/index.js
5294
- var require_has_flag = __commonJS({
5295
- "../../node_modules/has-flag/index.js"(exports, module) {
5296
- module.exports = (flag, argv = process.argv) => {
5297
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
5298
- const position = argv.indexOf(prefix + flag);
5299
- const terminatorPosition = argv.indexOf("--");
5300
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
5301
- };
5302
- }
5303
- });
5304
-
5305
- // ../../node_modules/supports-color/index.js
5306
- var require_supports_color = __commonJS({
5307
- "../../node_modules/supports-color/index.js"(exports, module) {
5308
- var os = __require("os");
5309
- var tty = __require("tty");
5310
- var hasFlag = require_has_flag();
5311
- var { env } = process;
5312
- var forceColor;
5313
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
5314
- forceColor = 0;
5315
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
5316
- forceColor = 1;
5317
- }
5318
- if ("FORCE_COLOR" in env) {
5319
- if (env.FORCE_COLOR === "true") {
5320
- forceColor = 1;
5321
- } else if (env.FORCE_COLOR === "false") {
5322
- forceColor = 0;
5323
- } else {
5324
- forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
5325
- }
5326
- }
5327
- function translateLevel(level) {
5328
- if (level === 0) {
5329
- return false;
5330
- }
5331
- return {
5332
- level,
5333
- hasBasic: true,
5334
- has256: level >= 2,
5335
- has16m: level >= 3
5336
- };
5337
- }
5338
- function supportsColor(haveStream, streamIsTTY) {
5339
- if (forceColor === 0) {
5340
- return 0;
5341
- }
5342
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
5343
- return 3;
5344
- }
5345
- if (hasFlag("color=256")) {
5346
- return 2;
5347
- }
5348
- if (haveStream && !streamIsTTY && forceColor === void 0) {
5349
- return 0;
5350
- }
5351
- const min = forceColor || 0;
5352
- if (env.TERM === "dumb") {
5353
- return min;
5354
- }
5355
- if (process.platform === "win32") {
5356
- const osRelease = os.release().split(".");
5357
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
5358
- return Number(osRelease[2]) >= 14931 ? 3 : 2;
5359
- }
5360
- return 1;
5361
- }
5362
- if ("CI" in env) {
5363
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
5364
- return 1;
5365
- }
5366
- return min;
5367
- }
5368
- if ("TEAMCITY_VERSION" in env) {
5369
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
5370
- }
5371
- if (env.COLORTERM === "truecolor") {
5372
- return 3;
5373
- }
5374
- if ("TERM_PROGRAM" in env) {
5375
- const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
5376
- switch (env.TERM_PROGRAM) {
5377
- case "iTerm.app":
5378
- return version >= 3 ? 3 : 2;
5379
- case "Apple_Terminal":
5380
- return 2;
5381
- }
5382
- }
5383
- if (/-256(color)?$/i.test(env.TERM)) {
5384
- return 2;
5385
- }
5386
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
5387
- return 1;
5388
- }
5389
- if ("COLORTERM" in env) {
5390
- return 1;
5391
- }
5392
- return min;
5393
- }
5394
- function getSupportLevel(stream) {
5395
- const level = supportsColor(stream, stream && stream.isTTY);
5396
- return translateLevel(level);
5397
- }
5398
- module.exports = {
5399
- supportsColor: getSupportLevel,
5400
- stdout: translateLevel(supportsColor(true, tty.isatty(1))),
5401
- stderr: translateLevel(supportsColor(true, tty.isatty(2)))
5402
- };
5403
- }
5404
- });
5405
-
5406
- // ../../node_modules/debug/src/node.js
5407
- var require_node = __commonJS({
5408
- "../../node_modules/debug/src/node.js"(exports, module) {
5409
- var tty = __require("tty");
5410
- var util = __require("util");
5411
- exports.init = init;
5412
- exports.log = log;
5413
- exports.formatArgs = formatArgs;
5414
- exports.save = save;
5415
- exports.load = load;
5416
- exports.useColors = useColors;
5417
- exports.destroy = util.deprecate(
5418
- () => {
5419
- },
5420
- "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."
5421
- );
5422
- exports.colors = [6, 2, 3, 4, 5, 1];
5423
- try {
5424
- const supportsColor = require_supports_color();
5425
- if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
5426
- exports.colors = [
5427
- 20,
5428
- 21,
5429
- 26,
5430
- 27,
5431
- 32,
5432
- 33,
5433
- 38,
5434
- 39,
5435
- 40,
5436
- 41,
5437
- 42,
5438
- 43,
5439
- 44,
5440
- 45,
5441
- 56,
5442
- 57,
5443
- 62,
5444
- 63,
5445
- 68,
5446
- 69,
5447
- 74,
5448
- 75,
5449
- 76,
5450
- 77,
5451
- 78,
5452
- 79,
5453
- 80,
5454
- 81,
5455
- 92,
5456
- 93,
5457
- 98,
5458
- 99,
5459
- 112,
5460
- 113,
5461
- 128,
5462
- 129,
5463
- 134,
5464
- 135,
5465
- 148,
5466
- 149,
5467
- 160,
5468
- 161,
5469
- 162,
5470
- 163,
5471
- 164,
5472
- 165,
5473
- 166,
5474
- 167,
5475
- 168,
5476
- 169,
5477
- 170,
5478
- 171,
5479
- 172,
5480
- 173,
5481
- 178,
5482
- 179,
5483
- 184,
5484
- 185,
5485
- 196,
5486
- 197,
5487
- 198,
5488
- 199,
5489
- 200,
5490
- 201,
5491
- 202,
5492
- 203,
5493
- 204,
5494
- 205,
5495
- 206,
5496
- 207,
5497
- 208,
5498
- 209,
5499
- 214,
5500
- 215,
5501
- 220,
5502
- 221
5503
- ];
5504
- }
5505
- } catch (error) {
5506
- }
5507
- exports.inspectOpts = Object.keys(process.env).filter((key) => {
5508
- return /^debug_/i.test(key);
5509
- }).reduce((obj, key) => {
5510
- const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_, k) => {
5511
- return k.toUpperCase();
5512
- });
5513
- let val = process.env[key];
5514
- if (/^(yes|on|true|enabled)$/i.test(val)) {
5515
- val = true;
5516
- } else if (/^(no|off|false|disabled)$/i.test(val)) {
5517
- val = false;
5518
- } else if (val === "null") {
5519
- val = null;
5520
- } else {
5521
- val = Number(val);
5522
- }
5523
- obj[prop] = val;
5524
- return obj;
5525
- }, {});
5526
- function useColors() {
5527
- return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(process.stderr.fd);
5528
- }
5529
- function formatArgs(args) {
5530
- const { namespace: name, useColors: useColors2 } = this;
5531
- if (useColors2) {
5532
- const c = this.color;
5533
- const colorCode = "\x1B[3" + (c < 8 ? c : "8;5;" + c);
5534
- const prefix = ` ${colorCode};1m${name} \x1B[0m`;
5535
- args[0] = prefix + args[0].split("\n").join("\n" + prefix);
5536
- args.push(colorCode + "m+" + module.exports.humanize(this.diff) + "\x1B[0m");
5537
- } else {
5538
- args[0] = getDate() + name + " " + args[0];
5539
- }
5540
- }
5541
- function getDate() {
5542
- if (exports.inspectOpts.hideDate) {
5543
- return "";
5544
- }
5545
- return (/* @__PURE__ */ new Date()).toISOString() + " ";
5546
- }
5547
- function log(...args) {
5548
- return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
5549
- }
5550
- function save(namespaces) {
5551
- if (namespaces) {
5552
- process.env.DEBUG = namespaces;
5553
- } else {
5554
- delete process.env.DEBUG;
5555
- }
5556
- }
5557
- function load() {
5558
- return process.env.DEBUG;
5559
- }
5560
- function init(debug) {
5561
- debug.inspectOpts = {};
5562
- const keys = Object.keys(exports.inspectOpts);
5563
- for (let i = 0; i < keys.length; i++) {
5564
- debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
5565
- }
5566
- }
5567
- module.exports = require_common()(exports);
5568
- var { formatters } = module.exports;
5569
- formatters.o = function(v) {
5570
- this.inspectOpts.colors = this.useColors;
5571
- return util.inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
5572
- };
5573
- formatters.O = function(v) {
5574
- this.inspectOpts.colors = this.useColors;
5575
- return util.inspect(v, this.inspectOpts);
5576
- };
5577
- }
5578
- });
5579
-
5580
- // ../../node_modules/debug/src/index.js
5581
- var require_src = __commonJS({
5582
- "../../node_modules/debug/src/index.js"(exports, module) {
5583
- if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
5584
- module.exports = require_browser();
5585
- } else {
5586
- module.exports = require_node();
5587
- }
5588
- }
5589
- });
5590
-
5591
- // ../../node_modules/follow-redirects/debug.js
5592
- var require_debug = __commonJS({
5593
- "../../node_modules/follow-redirects/debug.js"(exports, module) {
5594
- var debug;
5595
- module.exports = function() {
5596
- if (!debug) {
5597
- try {
5598
- debug = require_src()("follow-redirects");
5599
- } catch (error) {
5600
- }
5601
- if (typeof debug !== "function") {
5602
- debug = function() {
5603
- };
5604
- }
5605
- }
5606
- debug.apply(null, arguments);
5607
- };
5608
- }
5609
- });
5610
-
5611
- // ../../node_modules/follow-redirects/index.js
5612
- var require_follow_redirects = __commonJS({
5613
- "../../node_modules/follow-redirects/index.js"(exports, module) {
5614
- var url = __require("url");
5615
- var URL2 = url.URL;
5616
- var http = __require("http");
5617
- var https = __require("https");
5618
- var Writable = __require("stream").Writable;
5619
- var assert = __require("assert");
5620
- var debug = require_debug();
5621
- (function detectUnsupportedEnvironment() {
5622
- var looksLikeNode = typeof process !== "undefined";
5623
- var looksLikeBrowser = typeof window !== "undefined" && typeof document !== "undefined";
5624
- var looksLikeV8 = isFunction(Error.captureStackTrace);
5625
- if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) {
5626
- console.warn("The follow-redirects package should be excluded from browser builds.");
5627
- }
5628
- })();
5629
- var useNativeURL = false;
5630
- try {
5631
- assert(new URL2(""));
5632
- } catch (error) {
5633
- useNativeURL = error.code === "ERR_INVALID_URL";
5634
- }
5635
- var sensitiveHeaders = [
5636
- "Authorization",
5637
- "Proxy-Authorization",
5638
- "Cookie"
5639
- ];
5640
- var preservedUrlFields = [
5641
- "auth",
5642
- "host",
5643
- "hostname",
5644
- "href",
5645
- "path",
5646
- "pathname",
5647
- "port",
5648
- "protocol",
5649
- "query",
5650
- "search",
5651
- "hash"
5652
- ];
5653
- var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
5654
- var eventHandlers = /* @__PURE__ */ Object.create(null);
5655
- events.forEach(function(event) {
5656
- eventHandlers[event] = function(arg1, arg2, arg3) {
5657
- this._redirectable.emit(event, arg1, arg2, arg3);
5658
- };
5659
- });
5660
- var InvalidUrlError = createErrorType(
5661
- "ERR_INVALID_URL",
5662
- "Invalid URL",
5663
- TypeError
5664
- );
5665
- var RedirectionError = createErrorType(
5666
- "ERR_FR_REDIRECTION_FAILURE",
5667
- "Redirected request failed"
5668
- );
5669
- var TooManyRedirectsError = createErrorType(
5670
- "ERR_FR_TOO_MANY_REDIRECTS",
5671
- "Maximum number of redirects exceeded",
5672
- RedirectionError
5673
- );
5674
- var MaxBodyLengthExceededError = createErrorType(
5675
- "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
5676
- "Request body larger than maxBodyLength limit"
5677
- );
5678
- var WriteAfterEndError = createErrorType(
5679
- "ERR_STREAM_WRITE_AFTER_END",
5680
- "write after end"
5681
- );
5682
- var destroy = Writable.prototype.destroy || noop;
5683
- function RedirectableRequest(options, responseCallback) {
5684
- Writable.call(this);
5685
- this._sanitizeOptions(options);
5686
- this._options = options;
5687
- this._ended = false;
5688
- this._ending = false;
5689
- this._redirectCount = 0;
5690
- this._redirects = [];
5691
- this._requestBodyLength = 0;
5692
- this._requestBodyBuffers = [];
5693
- if (responseCallback) {
5694
- this.on("response", responseCallback);
5695
- }
5696
- var self = this;
5697
- this._onNativeResponse = function(response) {
5698
- try {
5699
- self._processResponse(response);
5700
- } catch (cause) {
5701
- self.emit("error", cause instanceof RedirectionError ? cause : new RedirectionError({ cause }));
5702
- }
5703
- };
5704
- this._headerFilter = new RegExp("^(?:" + sensitiveHeaders.concat(options.sensitiveHeaders).map(escapeRegex).join("|") + ")$", "i");
5705
- this._performRequest();
5706
- }
5707
- RedirectableRequest.prototype = Object.create(Writable.prototype);
5708
- RedirectableRequest.prototype.abort = function() {
5709
- destroyRequest(this._currentRequest);
5710
- this._currentRequest.abort();
5711
- this.emit("abort");
5712
- };
5713
- RedirectableRequest.prototype.destroy = function(error) {
5714
- destroyRequest(this._currentRequest, error);
5715
- destroy.call(this, error);
5716
- return this;
5717
- };
5718
- RedirectableRequest.prototype.write = function(data, encoding, callback) {
5719
- if (this._ending) {
5720
- throw new WriteAfterEndError();
5721
- }
5722
- if (!isString(data) && !isBuffer(data)) {
5723
- throw new TypeError("data should be a string, Buffer or Uint8Array");
5724
- }
5725
- if (isFunction(encoding)) {
5726
- callback = encoding;
5727
- encoding = null;
5728
- }
5729
- if (data.length === 0) {
5730
- if (callback) {
5731
- callback();
5732
- }
5733
- return;
5734
- }
5735
- if (this._requestBodyLength + data.length <= this._options.maxBodyLength) {
5736
- this._requestBodyLength += data.length;
5737
- this._requestBodyBuffers.push({ data, encoding });
5738
- this._currentRequest.write(data, encoding, callback);
5739
- } else {
5740
- this.emit("error", new MaxBodyLengthExceededError());
5741
- this.abort();
5742
- }
5743
- };
5744
- RedirectableRequest.prototype.end = function(data, encoding, callback) {
5745
- if (isFunction(data)) {
5746
- callback = data;
5747
- data = encoding = null;
5748
- } else if (isFunction(encoding)) {
5749
- callback = encoding;
5750
- encoding = null;
5751
- }
5752
- if (!data) {
5753
- this._ended = this._ending = true;
5754
- this._currentRequest.end(null, null, callback);
5755
- } else {
5756
- var self = this;
5757
- var currentRequest = this._currentRequest;
5758
- this.write(data, encoding, function() {
5759
- self._ended = true;
5760
- currentRequest.end(null, null, callback);
5761
- });
5762
- this._ending = true;
5763
- }
5764
- };
5765
- RedirectableRequest.prototype.setHeader = function(name, value) {
5766
- this._options.headers[name] = value;
5767
- this._currentRequest.setHeader(name, value);
5768
- };
5769
- RedirectableRequest.prototype.removeHeader = function(name) {
5770
- delete this._options.headers[name];
5771
- this._currentRequest.removeHeader(name);
5772
- };
5773
- RedirectableRequest.prototype.setTimeout = function(msecs, callback) {
5774
- var self = this;
5775
- function destroyOnTimeout(socket) {
5776
- socket.setTimeout(msecs);
5777
- socket.removeListener("timeout", socket.destroy);
5778
- socket.addListener("timeout", socket.destroy);
5779
- }
5780
- function startTimer(socket) {
5781
- if (self._timeout) {
5782
- clearTimeout(self._timeout);
5783
- }
5784
- self._timeout = setTimeout(function() {
5785
- self.emit("timeout");
5786
- clearTimer();
5787
- }, msecs);
5788
- destroyOnTimeout(socket);
5789
- }
5790
- function clearTimer() {
5791
- if (self._timeout) {
5792
- clearTimeout(self._timeout);
5793
- self._timeout = null;
5794
- }
5795
- self.removeListener("abort", clearTimer);
5796
- self.removeListener("error", clearTimer);
5797
- self.removeListener("response", clearTimer);
5798
- self.removeListener("close", clearTimer);
5799
- if (callback) {
5800
- self.removeListener("timeout", callback);
5801
- }
5802
- if (!self.socket) {
5803
- self._currentRequest.removeListener("socket", startTimer);
5804
- }
5805
- }
5806
- if (callback) {
5807
- this.on("timeout", callback);
5808
- }
5809
- if (this.socket) {
5810
- startTimer(this.socket);
5811
- } else {
5812
- this._currentRequest.once("socket", startTimer);
5813
- }
5814
- this.on("socket", destroyOnTimeout);
5815
- this.on("abort", clearTimer);
5816
- this.on("error", clearTimer);
5817
- this.on("response", clearTimer);
5818
- this.on("close", clearTimer);
5819
- return this;
5820
- };
5821
- [
5822
- "flushHeaders",
5823
- "getHeader",
5824
- "setNoDelay",
5825
- "setSocketKeepAlive"
5826
- ].forEach(function(method) {
5827
- RedirectableRequest.prototype[method] = function(a, b) {
5828
- return this._currentRequest[method](a, b);
5829
- };
5830
- });
5831
- ["aborted", "connection", "socket"].forEach(function(property) {
5832
- Object.defineProperty(RedirectableRequest.prototype, property, {
5833
- get: function() {
5834
- return this._currentRequest[property];
5835
- }
5836
- });
5837
- });
5838
- RedirectableRequest.prototype._sanitizeOptions = function(options) {
5839
- if (!options.headers) {
5840
- options.headers = {};
5841
- }
5842
- if (!isArray(options.sensitiveHeaders)) {
5843
- options.sensitiveHeaders = [];
5844
- }
5845
- if (options.host) {
5846
- if (!options.hostname) {
5847
- options.hostname = options.host;
5848
- }
5849
- delete options.host;
5850
- }
5851
- if (!options.pathname && options.path) {
5852
- var searchPos = options.path.indexOf("?");
5853
- if (searchPos < 0) {
5854
- options.pathname = options.path;
5855
- } else {
5856
- options.pathname = options.path.substring(0, searchPos);
5857
- options.search = options.path.substring(searchPos);
5858
- }
5859
- }
5860
- };
5861
- RedirectableRequest.prototype._performRequest = function() {
5862
- var protocol = this._options.protocol;
5863
- var nativeProtocol = this._options.nativeProtocols[protocol];
5864
- if (!nativeProtocol) {
5865
- throw new TypeError("Unsupported protocol " + protocol);
5866
- }
5867
- if (this._options.agents) {
5868
- var scheme = protocol.slice(0, -1);
5869
- this._options.agent = this._options.agents[scheme];
5870
- }
5871
- var request = this._currentRequest = nativeProtocol.request(this._options, this._onNativeResponse);
5872
- request._redirectable = this;
5873
- for (var event of events) {
5874
- request.on(event, eventHandlers[event]);
5875
- }
5876
- this._currentUrl = /^\//.test(this._options.path) ? url.format(this._options) : (
5877
- // When making a request to a proxy, […]
5878
- // a client MUST send the target URI in absolute-form […].
5879
- this._options.path
5880
- );
5881
- if (this._isRedirect) {
5882
- var i = 0;
5883
- var self = this;
5884
- var buffers = this._requestBodyBuffers;
5885
- (function writeNext(error) {
5886
- if (request === self._currentRequest) {
5887
- if (error) {
5888
- self.emit("error", error);
5889
- } else if (i < buffers.length) {
5890
- var buffer = buffers[i++];
5891
- if (!request.finished) {
5892
- request.write(buffer.data, buffer.encoding, writeNext);
5893
- }
5894
- } else if (self._ended) {
5895
- request.end();
5896
- }
5897
- }
5898
- })();
5899
- }
5900
- };
5901
- RedirectableRequest.prototype._processResponse = function(response) {
5902
- var statusCode = response.statusCode;
5903
- if (this._options.trackRedirects) {
5904
- this._redirects.push({
5905
- url: this._currentUrl,
5906
- headers: response.headers,
5907
- statusCode
5908
- });
5909
- }
5910
- var location = response.headers.location;
5911
- if (!location || this._options.followRedirects === false || statusCode < 300 || statusCode >= 400) {
5912
- response.responseUrl = this._currentUrl;
5913
- response.redirects = this._redirects;
5914
- this.emit("response", response);
5915
- this._requestBodyBuffers = [];
5916
- return;
5917
- }
5918
- destroyRequest(this._currentRequest);
5919
- response.destroy();
5920
- if (++this._redirectCount > this._options.maxRedirects) {
5921
- throw new TooManyRedirectsError();
5922
- }
5923
- var requestHeaders;
5924
- var beforeRedirect = this._options.beforeRedirect;
5925
- if (beforeRedirect) {
5926
- requestHeaders = Object.assign({
5927
- // The Host header was set by nativeProtocol.request
5928
- Host: response.req.getHeader("host")
5929
- }, this._options.headers);
5930
- }
5931
- var method = this._options.method;
5932
- if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || // RFC7231§6.4.4: The 303 (See Other) status code indicates that
5933
- // the server is redirecting the user agent to a different resource […]
5934
- // A user agent can perform a retrieval request targeting that URI
5935
- // (a GET or HEAD request if using HTTP) […]
5936
- statusCode === 303 && !/^(?:GET|HEAD)$/.test(this._options.method)) {
5937
- this._options.method = "GET";
5938
- this._requestBodyBuffers = [];
5939
- removeMatchingHeaders(/^content-/i, this._options.headers);
5940
- }
5941
- var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
5942
- var currentUrlParts = parseUrl(this._currentUrl);
5943
- var currentHost = currentHostHeader || currentUrlParts.host;
5944
- var currentUrl = /^\w+:/.test(location) ? this._currentUrl : url.format(Object.assign(currentUrlParts, { host: currentHost }));
5945
- var redirectUrl = resolveUrl(location, currentUrl);
5946
- debug("redirecting to", redirectUrl.href);
5947
- this._isRedirect = true;
5948
- spreadUrlObject(redirectUrl, this._options);
5949
- if (redirectUrl.protocol !== currentUrlParts.protocol && redirectUrl.protocol !== "https:" || redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost)) {
5950
- removeMatchingHeaders(this._headerFilter, this._options.headers);
5951
- }
5952
- if (isFunction(beforeRedirect)) {
5953
- var responseDetails = {
5954
- headers: response.headers,
5955
- statusCode
5956
- };
5957
- var requestDetails = {
5958
- url: currentUrl,
5959
- method,
5960
- headers: requestHeaders
5961
- };
5962
- beforeRedirect(this._options, responseDetails, requestDetails);
5963
- this._sanitizeOptions(this._options);
5964
- }
5965
- this._performRequest();
5966
- };
5967
- function wrap(protocols) {
5968
- var exports2 = {
5969
- maxRedirects: 21,
5970
- maxBodyLength: 10 * 1024 * 1024
5971
- };
5972
- var nativeProtocols = {};
5973
- Object.keys(protocols).forEach(function(scheme) {
5974
- var protocol = scheme + ":";
5975
- var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];
5976
- var wrappedProtocol = exports2[scheme] = Object.create(nativeProtocol);
5977
- function request(input, options, callback) {
5978
- if (isURL(input)) {
5979
- input = spreadUrlObject(input);
5980
- } else if (isString(input)) {
5981
- input = spreadUrlObject(parseUrl(input));
5982
- } else {
5983
- callback = options;
5984
- options = validateUrl(input);
5985
- input = { protocol };
5986
- }
5987
- if (isFunction(options)) {
5988
- callback = options;
5989
- options = null;
5990
- }
5991
- options = Object.assign({
5992
- maxRedirects: exports2.maxRedirects,
5993
- maxBodyLength: exports2.maxBodyLength
5994
- }, input, options);
5995
- options.nativeProtocols = nativeProtocols;
5996
- if (!isString(options.host) && !isString(options.hostname)) {
5997
- options.hostname = "::1";
5998
- }
5999
- assert.equal(options.protocol, protocol, "protocol mismatch");
6000
- debug("options", options);
6001
- return new RedirectableRequest(options, callback);
6002
- }
6003
- function get(input, options, callback) {
6004
- var wrappedRequest = wrappedProtocol.request(input, options, callback);
6005
- wrappedRequest.end();
6006
- return wrappedRequest;
6007
- }
6008
- Object.defineProperties(wrappedProtocol, {
6009
- request: { value: request, configurable: true, enumerable: true, writable: true },
6010
- get: { value: get, configurable: true, enumerable: true, writable: true }
6011
- });
6012
- });
6013
- return exports2;
6014
- }
6015
- function noop() {
6016
- }
6017
- function parseUrl(input) {
6018
- var parsed;
6019
- if (useNativeURL) {
6020
- parsed = new URL2(input);
6021
- } else {
6022
- parsed = validateUrl(url.parse(input));
6023
- if (!isString(parsed.protocol)) {
6024
- throw new InvalidUrlError({ input });
6025
- }
6026
- }
6027
- return parsed;
6028
- }
6029
- function resolveUrl(relative, base) {
6030
- return useNativeURL ? new URL2(relative, base) : parseUrl(url.resolve(base, relative));
6031
- }
6032
- function validateUrl(input) {
6033
- if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
6034
- throw new InvalidUrlError({ input: input.href || input });
6035
- }
6036
- if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
6037
- throw new InvalidUrlError({ input: input.href || input });
6038
- }
6039
- return input;
4979
+ originalPrompt: context.originalPrompt,
4980
+ contract: context.contract,
4981
+ designSystemSummary: context.designContext
4982
+ });
4983
+ const numberedSource = context.sourceCode.split("\n").map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
4984
+ const agent = createAgent({
4985
+ provider: routerProvider,
4986
+ routeOverride: config.routeOverride,
4987
+ onRetry: config.onRetry
4988
+ });
4989
+ const evalStart = Date.now();
4990
+ const totalCriteriaCount = TIER_1_CRITERIA.length + TIER_2_CRITERIA.length;
4991
+ const results = await Promise.all([
4992
+ ...TIER_1_CRITERIA.map((c) => evalCriterion(c, 1, motherPrompt, numberedSource, agent, model)),
4993
+ ...TIER_2_CRITERIA.map((c) => evalCriterion(c, 2, motherPrompt, numberedSource, agent, model))
4994
+ ]);
4995
+ const allIssues = [];
4996
+ const passCategories = [];
4997
+ let totalIn = 0;
4998
+ let totalOut = 0;
4999
+ let cachedTokensEstimate = 0;
5000
+ for (const r of results) {
5001
+ allIssues.push(...r.issues);
5002
+ if (r.pass) {
5003
+ passCategories.push(r.criterion);
6040
5004
  }
6041
- function spreadUrlObject(urlObject, target) {
6042
- var spread = target || {};
6043
- for (var key of preservedUrlFields) {
6044
- spread[key] = urlObject[key];
6045
- }
6046
- if (spread.hostname.startsWith("[")) {
6047
- spread.hostname = spread.hostname.slice(1, -1);
5005
+ totalIn += r.tokens.in;
5006
+ totalOut += r.tokens.out;
5007
+ }
5008
+ const firstResult = results[0];
5009
+ if (firstResult && firstResult.tokens.in > 0) {
5010
+ cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
5011
+ }
5012
+ const wallMs = Date.now() - evalStart;
5013
+ for (const r of results) {
5014
+ const issueCount = r.issues.length;
5015
+ const status = r.pass ? "PASS" : r.tier === 1 ? "FAIL" : r.issues.some((i) => i.result === "fail") ? "FAIL" : "WARN";
5016
+ const issueLabel = issueCount > 0 ? ` (${issueCount} issue${issueCount > 1 ? "s" : ""})` : "";
5017
+ console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
5018
+ }
5019
+ console.log(
5020
+ `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cached~=${cachedTokensEstimate} | issues=${allIssues.length} pass=${passCategories.length}`
5021
+ );
5022
+ return {
5023
+ issues: allIssues,
5024
+ pass: passCategories,
5025
+ inputTokens: totalIn,
5026
+ outputTokens: totalOut
5027
+ };
5028
+ }
5029
+ var TIER_1_CRITERIA, TIER_2_CRITERIA, TIER_1_CATEGORIES, TIER_2_CATEGORIES, VALID_CATEGORIES;
5030
+ var init_llm_evaluator = __esm({
5031
+ "src/evaluation/llm-evaluator.ts"() {
5032
+ init_llm_router();
5033
+ init_types_public();
5034
+ TIER_1_CRITERIA = ["functionality", "crash"];
5035
+ TIER_2_CRITERIA = ["interactivity", "accessibility", "layout", "loading", "visual"];
5036
+ TIER_1_CATEGORIES = /* @__PURE__ */ new Set(["functionality", "crash"]);
5037
+ TIER_2_CATEGORIES = /* @__PURE__ */ new Set([
5038
+ "interactivity",
5039
+ "accessibility",
5040
+ "layout",
5041
+ "loading",
5042
+ "visual"
5043
+ ]);
5044
+ VALID_CATEGORIES = /* @__PURE__ */ new Set([
5045
+ ...TIER_1_CATEGORIES,
5046
+ ...TIER_2_CATEGORIES
5047
+ ]);
5048
+ }
5049
+ });
5050
+
5051
+ // src/evaluation/cost-tracker.ts
5052
+ var cost_tracker_exports = {};
5053
+ __export(cost_tracker_exports, {
5054
+ CostTracker: () => CostTracker,
5055
+ pricePer1k: () => pricePer1k
5056
+ });
5057
+ function resolveRegistryId(model) {
5058
+ const keys = Object.keys(MODEL_REGISTRY);
5059
+ if (keys.includes(model)) return model;
5060
+ const bySuffix = keys.find((id) => id.endsWith(`/${model}`));
5061
+ if (bySuffix) return bySuffix;
5062
+ const undated = model.replace(/-\d{8}$/, "");
5063
+ return keys.find((id) => id.endsWith(`/${undated}`)) ?? null;
5064
+ }
5065
+ function pricePer1k(model) {
5066
+ const id = resolveRegistryId(model);
5067
+ if (id === null) return FALLBACK_PER_1K;
5068
+ const { costs } = MODEL_REGISTRY[id];
5069
+ return { input: costs.inputPer1M / 1e3, output: costs.outputPer1M / 1e3 };
5070
+ }
5071
+ var FALLBACK_PER_1K, CostTracker;
5072
+ var init_cost_tracker = __esm({
5073
+ "src/evaluation/cost-tracker.ts"() {
5074
+ FALLBACK_PER_1K = { input: 3e-3, output: 0.015 };
5075
+ CostTracker = class {
5076
+ constructor(maxBudget) {
5077
+ this.maxBudget = maxBudget;
6048
5078
  }
6049
- if (spread.port !== "") {
6050
- spread.port = Number(spread.port);
5079
+ maxBudget;
5080
+ totalCost = 0;
5081
+ record(model, inputTokens, outputTokens) {
5082
+ const prices = pricePer1k(model);
5083
+ const cost = inputTokens / 1e3 * prices.input + outputTokens / 1e3 * prices.output;
5084
+ this.totalCost += cost;
5085
+ return cost;
6051
5086
  }
6052
- spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
6053
- return spread;
6054
- }
6055
- function removeMatchingHeaders(regex, headers) {
6056
- var lastValue;
6057
- for (var header in headers) {
6058
- if (regex.test(header)) {
6059
- lastValue = headers[header];
6060
- delete headers[header];
6061
- }
5087
+ canContinue() {
5088
+ if (this.maxBudget === null) return true;
5089
+ return this.totalCost < this.maxBudget;
6062
5090
  }
6063
- return lastValue === null || typeof lastValue === "undefined" ? void 0 : String(lastValue).trim();
6064
- }
6065
- function createErrorType(code, message, baseClass) {
6066
- function CustomError(properties) {
6067
- if (isFunction(Error.captureStackTrace)) {
6068
- Error.captureStackTrace(this, this.constructor);
6069
- }
6070
- Object.assign(this, properties || {});
6071
- this.code = code;
6072
- this.message = this.cause ? message + ": " + this.cause.message : message;
5091
+ getTotal() {
5092
+ return this.totalCost;
6073
5093
  }
6074
- CustomError.prototype = new (baseClass || Error)();
6075
- Object.defineProperties(CustomError.prototype, {
6076
- constructor: {
6077
- value: CustomError,
6078
- enumerable: false
6079
- },
6080
- name: {
6081
- value: "Error [" + code + "]",
6082
- enumerable: false
6083
- }
6084
- });
6085
- return CustomError;
6086
- }
6087
- function destroyRequest(request, error) {
6088
- for (var event of events) {
6089
- request.removeListener(event, eventHandlers[event]);
5094
+ getRemaining() {
5095
+ if (this.maxBudget === null) return null;
5096
+ return Math.max(0, this.maxBudget - this.totalCost);
6090
5097
  }
6091
- request.on("error", noop);
6092
- request.destroy(error);
6093
- }
6094
- function isSubdomain(subdomain, domain) {
6095
- assert(isString(subdomain) && isString(domain));
6096
- var dot = subdomain.length - domain.length - 1;
6097
- return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
6098
- }
6099
- function isArray(value) {
6100
- return value instanceof Array;
6101
- }
6102
- function isString(value) {
6103
- return typeof value === "string" || value instanceof String;
6104
- }
6105
- function isFunction(value) {
6106
- return typeof value === "function";
6107
- }
6108
- function isBuffer(value) {
6109
- return typeof value === "object" && "length" in value;
6110
- }
6111
- function isURL(value) {
6112
- return URL2 && value instanceof URL2;
6113
- }
6114
- function escapeRegex(regex) {
6115
- return regex.replace(/[\]\\/()*+?.$]/g, "\\$&");
6116
- }
6117
- module.exports = wrap({ http, https });
6118
- module.exports.wrap = wrap;
5098
+ };
6119
5099
  }
6120
5100
  });
6121
5101
 
@@ -7244,7 +6224,7 @@ var require_streamx = __commonJS({
7244
6224
  }
7245
6225
  }
7246
6226
  };
7247
- var Readable = class _Readable extends Stream {
6227
+ var Readable2 = class _Readable extends Stream {
7248
6228
  constructor(opts) {
7249
6229
  super(opts);
7250
6230
  this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD;
@@ -7261,7 +6241,7 @@ var require_streamx = __commonJS({
7261
6241
  fn().then((src) => {
7262
6242
  if (src === null) return out.end();
7263
6243
  if (out.destroying) return;
7264
- pipeline(src, out, noop);
6244
+ pipeline2(src, out, noop);
7265
6245
  }).catch((err) => out.destroy(err));
7266
6246
  return out;
7267
6247
  }
@@ -7455,7 +6435,7 @@ var require_streamx = __commonJS({
7455
6435
  return this;
7456
6436
  }
7457
6437
  };
7458
- var Duplex = class extends Readable {
6438
+ var Duplex = class extends Readable2 {
7459
6439
  // and Writable
7460
6440
  constructor(opts) {
7461
6441
  super(opts);
@@ -7548,13 +6528,13 @@ var require_streamx = __commonJS({
7548
6528
  }
7549
6529
  function pipelinePromise(...streams) {
7550
6530
  return new Promise((resolve3, reject) => {
7551
- return pipeline(...streams, (err) => {
6531
+ return pipeline2(...streams, (err) => {
7552
6532
  if (err) return reject(err);
7553
6533
  resolve3();
7554
6534
  });
7555
6535
  });
7556
6536
  }
7557
- function pipeline(stream, ...streams) {
6537
+ function pipeline2(stream, ...streams) {
7558
6538
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams];
7559
6539
  const done = all.length && typeof all[all.length - 1] === "function" ? all.pop() : null;
7560
6540
  if (all.length < 2) throw StreamError.BAD_ARGUMENT("Pipeline requires at least 2 streams");
@@ -7652,7 +6632,7 @@ var require_streamx = __commonJS({
7652
6632
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev;
7653
6633
  }
7654
6634
  module.exports = {
7655
- pipeline,
6635
+ pipeline: pipeline2,
7656
6636
  pipelinePromise,
7657
6637
  isStream,
7658
6638
  isStreamx,
@@ -7664,7 +6644,7 @@ var require_streamx = __commonJS({
7664
6644
  getStreamError,
7665
6645
  Stream,
7666
6646
  Writable,
7667
- Readable,
6647
+ Readable: Readable2,
7668
6648
  Duplex,
7669
6649
  Transform,
7670
6650
  // Export PassThrough for compatibility with Node.js core's stream module
@@ -7673,9 +6653,9 @@ var require_streamx = __commonJS({
7673
6653
  }
7674
6654
  });
7675
6655
 
7676
- // ../../node_modules/tar-fs/node_modules/tar-stream/headers.js
6656
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/headers.js
7677
6657
  var require_headers = __commonJS({
7678
- "../../node_modules/tar-fs/node_modules/tar-stream/headers.js"(exports) {
6658
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/headers.js"(exports) {
7679
6659
  var b4a = require_b4a();
7680
6660
  var ZEROS = "0000000000000000000";
7681
6661
  var SEVENS = "7777777777777777777";
@@ -7933,10 +6913,10 @@ var require_headers = __commonJS({
7933
6913
  }
7934
6914
  });
7935
6915
 
7936
- // ../../node_modules/tar-fs/node_modules/tar-stream/extract.js
6916
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/extract.js
7937
6917
  var require_extract = __commonJS({
7938
- "../../node_modules/tar-fs/node_modules/tar-stream/extract.js"(exports, module) {
7939
- var { Writable, Readable, getStreamError } = require_streamx();
6918
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/extract.js"(exports, module) {
6919
+ var { Writable, Readable: Readable2, getStreamError } = require_streamx();
7940
6920
  var FIFO = require_fast_fifo();
7941
6921
  var b4a = require_b4a();
7942
6922
  var headers = require_headers();
@@ -7983,7 +6963,7 @@ var require_extract = __commonJS({
7983
6963
  return buf.subarray(this._offset, this._offset += size);
7984
6964
  }
7985
6965
  };
7986
- var Source = class extends Readable {
6966
+ var Source = class extends Readable2 {
7987
6967
  constructor(self, header, offset) {
7988
6968
  super();
7989
6969
  this.header = header;
@@ -8267,9 +7247,9 @@ var require_extract = __commonJS({
8267
7247
  }
8268
7248
  });
8269
7249
 
8270
- // ../../node_modules/tar-fs/node_modules/tar-stream/constants.js
7250
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/constants.js
8271
7251
  var require_constants = __commonJS({
8272
- "../../node_modules/tar-fs/node_modules/tar-stream/constants.js"(exports, module) {
7252
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/constants.js"(exports, module) {
8273
7253
  var constants = {
8274
7254
  // just for envs without fs
8275
7255
  S_IFMT: 61440,
@@ -8287,10 +7267,10 @@ var require_constants = __commonJS({
8287
7267
  }
8288
7268
  });
8289
7269
 
8290
- // ../../node_modules/tar-fs/node_modules/tar-stream/pack.js
7270
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/pack.js
8291
7271
  var require_pack = __commonJS({
8292
- "../../node_modules/tar-fs/node_modules/tar-stream/pack.js"(exports, module) {
8293
- var { Readable, Writable, getStreamError } = require_streamx();
7272
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/pack.js"(exports, module) {
7273
+ var { Readable: Readable2, Writable, getStreamError } = require_streamx();
8294
7274
  var b4a = require_b4a();
8295
7275
  var constants = require_constants();
8296
7276
  var headers = require_headers();
@@ -8383,7 +7363,7 @@ var require_pack = __commonJS({
8383
7363
  cb();
8384
7364
  }
8385
7365
  };
8386
- var Pack = class extends Readable {
7366
+ var Pack = class extends Readable2 {
8387
7367
  constructor(opts) {
8388
7368
  super(opts);
8389
7369
  this._drain = noop;
@@ -8521,9 +7501,9 @@ var require_pack = __commonJS({
8521
7501
  }
8522
7502
  });
8523
7503
 
8524
- // ../../node_modules/tar-fs/node_modules/tar-stream/index.js
7504
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/index.js
8525
7505
  var require_tar_stream = __commonJS({
8526
- "../../node_modules/tar-fs/node_modules/tar-stream/index.js"(exports) {
7506
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/index.js"(exports) {
8527
7507
  exports.extract = require_extract();
8528
7508
  exports.pack = require_pack();
8529
7509
  }
@@ -8761,9 +7741,9 @@ var require_pump = __commonJS({
8761
7741
  }
8762
7742
  });
8763
7743
 
8764
- // ../../node_modules/tar-fs/index.js
7744
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-fs/index.js
8765
7745
  var require_tar_fs = __commonJS({
8766
- "../../node_modules/tar-fs/index.js"(exports) {
7746
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-fs/index.js"(exports) {
8767
7747
  var tar = require_tar_stream();
8768
7748
  var pump = require_pump();
8769
7749
  var fs2 = __require("fs");
@@ -9089,10 +8069,9 @@ var require_tar_fs = __commonJS({
9089
8069
  }
9090
8070
  }
9091
8071
  });
9092
- var import_follow_redirects, import_tar_fs, setupLambdaEnvironment, isValidUrl, isRunningInAmazonLinux2023, downloadAndExtract;
8072
+ var import_tar_fs, setupLambdaEnvironment, isValidUrl, isRunningInAmazonLinux2023, downloadAndExtract;
9093
8073
  var init_helper = __esm({
9094
- "../../node_modules/@sparticuz/chromium/build/esm/helper.js"() {
9095
- import_follow_redirects = __toESM(require_follow_redirects());
8074
+ "../../node_modules/@sparticuz/chromium/build/helper.js"() {
9096
8075
  import_tar_fs = __toESM(require_tar_fs());
9097
8076
  setupLambdaEnvironment = (baseLibPath) => {
9098
8077
  process.env["FONTCONFIG_PATH"] ??= join(tmpdir(), "fonts");
@@ -9108,7 +8087,13 @@ var init_helper = __esm({
9108
8087
  };
9109
8088
  isValidUrl = (input) => {
9110
8089
  try {
9111
- return Boolean(new URL(input));
8090
+ const url = new URL(input);
8091
+ if (url.protocol === "https:")
8092
+ return true;
8093
+ if (url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]")) {
8094
+ return true;
8095
+ }
8096
+ return false;
9112
8097
  } catch {
9113
8098
  return false;
9114
8099
  }
@@ -9126,43 +8111,41 @@ var init_helper = __esm({
9126
8111
  return false;
9127
8112
  };
9128
8113
  downloadAndExtract = async (url) => {
9129
- const getOptions = new URL(url);
9130
- getOptions.maxBodyLength = 60 * 1024 * 1024;
9131
8114
  const destDir = join(tmpdir(), "chromium-pack");
9132
- return new Promise((resolve3, reject) => {
9133
- const extractObj = (0, import_tar_fs.extract)(destDir);
9134
- const cleanupOnError = (err) => {
8115
+ const response = await fetch(url, {
8116
+ redirect: "follow",
8117
+ signal: AbortSignal.timeout(3e5)
8118
+ });
8119
+ if (!response.ok) {
8120
+ throw new Error(`Unexpected status code: ${String(response.status)}.`);
8121
+ }
8122
+ if (!response.body) {
8123
+ throw new Error("Response body is empty.");
8124
+ }
8125
+ try {
8126
+ await pipeline(Readable.fromWeb(response.body), (0, import_tar_fs.extract)(destDir));
8127
+ } catch (error) {
8128
+ await new Promise((resolve3) => {
9135
8129
  rm(destDir, { force: true, recursive: true }, () => {
9136
- reject(err);
8130
+ resolve3();
9137
8131
  });
9138
- };
9139
- extractObj.once("error", cleanupOnError);
9140
- extractObj.once("finish", () => {
9141
- resolve3(destDir);
9142
- });
9143
- const req = import_follow_redirects.default.https.get(url, (response) => {
9144
- if (response.statusCode !== 200) {
9145
- reject(new Error(`Unexpected status code: ${response.statusCode?.toFixed(0) ?? "UNK"}.`));
9146
- return;
9147
- }
9148
- response.pipe(extractObj);
9149
- response.once("error", cleanupOnError);
9150
- });
9151
- req.once("error", cleanupOnError);
9152
- req.setTimeout(60 * 1e3, () => {
9153
- req.destroy();
9154
- cleanupOnError(new Error("Request timeout"));
9155
8132
  });
9156
- });
8133
+ throw error;
8134
+ }
8135
+ return destDir;
9157
8136
  };
9158
8137
  }
9159
8138
  });
9160
- var import_tar_fs2, inflate;
8139
+ var import_tar_fs2, ARCHIVE_EXTENSION_REGEX, TAR_EXTENSION_REGEX, BROTLI_EXTENSION_REGEX, GZIP_EXTENSION_REGEX, inflate;
9161
8140
  var init_lambdafs = __esm({
9162
- "../../node_modules/@sparticuz/chromium/build/esm/lambdafs.js"() {
8141
+ "../../node_modules/@sparticuz/chromium/build/lambdafs.js"() {
9163
8142
  import_tar_fs2 = __toESM(require_tar_fs());
8143
+ ARCHIVE_EXTENSION_REGEX = /\.(?:t(?:ar(?:\.(?:br|gz))?|br|gz)|br|gz)$/i;
8144
+ TAR_EXTENSION_REGEX = /\.t(?:ar(?:\.(?:br|gz))?|br|gz)$/i;
8145
+ BROTLI_EXTENSION_REGEX = /br$/i;
8146
+ GZIP_EXTENSION_REGEX = /gz$/i;
9164
8147
  inflate = (filePath) => {
9165
- const output = filePath.includes("swiftshader") ? tmpdir() : join(tmpdir(), basename(filePath).replace(/\.(?:t(?:ar(?:\.(?:br|gz))?|br|gz)|br|gz)$/i, ""));
8148
+ const output = filePath.includes("swiftshader") ? tmpdir() : join(tmpdir(), basename(filePath).replace(ARCHIVE_EXTENSION_REGEX, ""));
9166
8149
  return new Promise((resolve3, reject) => {
9167
8150
  if (filePath.includes("swiftshader")) {
9168
8151
  if (existsSync(`${output}/libGLESv2.so`)) {
@@ -9173,9 +8156,9 @@ var init_lambdafs = __esm({
9173
8156
  resolve3(output);
9174
8157
  return;
9175
8158
  }
9176
- const isBrotli = /br$/i.test(filePath);
9177
- const isGzip = /gz$/i.test(filePath);
9178
- const isTar = /\.t(?:ar(?:\.(?:br|gz))?|br|gz)$/i.test(filePath);
8159
+ const isBrotli = BROTLI_EXTENSION_REGEX.test(filePath);
8160
+ const isGzip = GZIP_EXTENSION_REGEX.test(filePath);
8161
+ const isTar = TAR_EXTENSION_REGEX.test(filePath);
9179
8162
  const highWaterMark = 2 ** 22;
9180
8163
  const source = createReadStream(filePath, { highWaterMark });
9181
8164
  let target;
@@ -9207,24 +8190,28 @@ var init_lambdafs = __esm({
9207
8190
  }
9208
8191
  });
9209
8192
  function getBinPath() {
9210
- return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "bin");
8193
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "bin");
9211
8194
  }
9212
- var init_paths_esm = __esm({
9213
- "../../node_modules/@sparticuz/chromium/build/esm/paths.esm.js"() {
8195
+ var init_paths = __esm({
8196
+ "../../node_modules/@sparticuz/chromium/build/paths.js"() {
9214
8197
  }
9215
8198
  });
9216
8199
 
9217
- // ../../node_modules/@sparticuz/chromium/build/esm/index.js
9218
- var esm_exports = {};
9219
- __export(esm_exports, {
9220
- default: () => esm_default
8200
+ // ../../node_modules/@sparticuz/chromium/build/index.js
8201
+ var build_exports = {};
8202
+ __export(build_exports, {
8203
+ default: () => build_default,
8204
+ inflate: () => inflate,
8205
+ setupLambdaEnvironment: () => setupLambdaEnvironment
9221
8206
  });
9222
- var nodeMajorVersion, Chromium, esm_default;
9223
- var init_esm = __esm({
9224
- "../../node_modules/@sparticuz/chromium/build/esm/index.js"() {
8207
+ var nodeMajorVersion, Chromium, build_default;
8208
+ var init_build = __esm({
8209
+ "../../node_modules/@sparticuz/chromium/build/index.js"() {
8210
+ init_helper();
8211
+ init_lambdafs();
8212
+ init_paths();
9225
8213
  init_helper();
9226
8214
  init_lambdafs();
9227
- init_paths_esm();
9228
8215
  nodeMajorVersion = Number.parseInt(process.versions.node.split(".")[0] ?? "");
9229
8216
  if (isRunningInAmazonLinux2023(nodeMajorVersion)) {
9230
8217
  setupLambdaEnvironment(join(tmpdir(), "al2023", "lib"));
@@ -9343,7 +8330,7 @@ var init_esm = __esm({
9343
8330
  }
9344
8331
  input ??= getBinPath();
9345
8332
  if (!existsSync(input)) {
9346
- throw new Error(`The input directory "${input}" does not exist. Please provide the location of the brotli files.`);
8333
+ throw new Error(`The input directory "${input}" does not exist. If you are using a bundler (esbuild, webpack, etc.), you must externalize @sparticuz/chromium so it is not relocated. See: https://github.com/Sparticuz/chromium#bundler-configuration`);
9347
8334
  }
9348
8335
  const promises = [
9349
8336
  inflate(join(input, "chromium.br")),
@@ -9357,7 +8344,7 @@ var init_esm = __esm({
9357
8344
  return result.shift();
9358
8345
  }
9359
8346
  };
9360
- esm_default = Chromium;
8347
+ build_default = Chromium;
9361
8348
  }
9362
8349
  });
9363
8350
 
@@ -9366,6 +8353,7 @@ var visual_evaluator_exports = {};
9366
8353
  __export(visual_evaluator_exports, {
9367
8354
  buildRenderHTML: () => buildRenderHTML,
9368
8355
  bundleForRendering: () => bundleForRendering,
8356
+ callMultimodalLLM: () => callMultimodalLLM,
9369
8357
  runVisualEval: () => runVisualEval,
9370
8358
  runVisualEvaluation: () => runVisualEvaluation
9371
8359
  });
@@ -9464,7 +8452,7 @@ ${bundledCode}
9464
8452
  async function captureScreenshot(html, viewport = { width: 1280, height: 800 }) {
9465
8453
  try {
9466
8454
  const puppeteer = await import('puppeteer-core');
9467
- const chromium = await Promise.resolve().then(() => (init_esm(), esm_exports));
8455
+ const chromium = await Promise.resolve().then(() => (init_build(), build_exports));
9468
8456
  const launchOptions = {
9469
8457
  args: chromium.default.args,
9470
8458
  defaultViewport: viewport,
@@ -9474,7 +8462,9 @@ async function captureScreenshot(html, viewport = { width: 1280, height: 800 })
9474
8462
  const browser = await puppeteer.default.launch(launchOptions);
9475
8463
  try {
9476
8464
  const page = await browser.newPage();
9477
- await page.setContent(html, { waitUntil: "networkidle0", timeout: 15e3 });
8465
+ await page.setContent(html, { waitUntil: "load", timeout: 15e3 });
8466
+ await page.waitForNetworkIdle({ idleTime: 500, timeout: 15e3 }).catch(() => {
8467
+ });
9478
8468
  await page.waitForSelector("#root > *", { timeout: 1e4 }).catch(() => {
9479
8469
  });
9480
8470
  await new Promise((r) => setTimeout(r, 1e3));
@@ -9488,63 +8478,23 @@ async function captureScreenshot(html, viewport = { width: 1280, height: 800 })
9488
8478
  return null;
9489
8479
  }
9490
8480
  }
9491
- async function callMultimodalLLM(provider, model, prompt, screenshot, originalPrompt) {
8481
+ async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt) {
9492
8482
  const userPrompt = `## Original Request
9493
8483
  ${originalPrompt}
9494
8484
 
9495
8485
  Evaluate the screenshot of the generated component.`;
9496
- if (provider === "claude") {
9497
- const { default: Anthropic2 } = await import('@anthropic-ai/sdk');
9498
- const client2 = new Anthropic2({
9499
- apiKey: process.env.ANTHROPIC_API_KEY,
9500
- baseURL: "https://api.anthropic.com"
9501
- });
9502
- const response2 = await client2.messages.create({
9503
- model: model.startsWith("anthropic/") ? model.slice(10) : model,
9504
- max_tokens: 1500,
9505
- system: prompt,
9506
- messages: [{
9507
- role: "user",
9508
- content: [
9509
- {
9510
- type: "image",
9511
- source: {
9512
- type: "base64",
9513
- media_type: "image/png",
9514
- data: screenshot.toString("base64")
9515
- }
9516
- },
9517
- { type: "text", text: userPrompt }
9518
- ]
9519
- }]
9520
- });
9521
- return {
9522
- text: response2.content.filter((b) => b.type === "text").map((b) => b.text).join(""),
9523
- inputTokens: response2.usage.input_tokens,
9524
- outputTokens: response2.usage.output_tokens
9525
- };
9526
- }
9527
- const { GoogleGenAI } = await import('@google/genai');
9528
- const client = new GoogleGenAI({
9529
- apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
9530
- });
9531
- const response = await client.models.generateContent({
9532
- model: model.startsWith("google/") ? model.slice(7) : model,
9533
- contents: [{
9534
- role: "user",
9535
- parts: [
9536
- { inlineData: { mimeType: "image/png", data: screenshot.toString("base64") } },
9537
- { text: userPrompt }
9538
- ]
9539
- }],
9540
- config: { systemInstruction: prompt }
8486
+ const agent = createVisionAgent({
8487
+ provider: config.provider === "claude" ? "anthropic" : config.provider,
8488
+ routeOverride: config.routeOverride,
8489
+ onRetry: config.onRetry
9541
8490
  });
9542
- const usage = response.usageMetadata;
9543
- return {
9544
- text: response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "",
9545
- inputTokens: usage?.promptTokenCount ?? 0,
9546
- outputTokens: usage?.candidatesTokenCount ?? 0
9547
- };
8491
+ return agent.callVision(
8492
+ model,
8493
+ prompt,
8494
+ userPrompt,
8495
+ { mediaType: "image/png", base64: screenshot.toString("base64") },
8496
+ 1500
8497
+ );
9548
8498
  }
9549
8499
  async function runVisualEvaluation(context, config) {
9550
8500
  const startTime = Date.now();
@@ -9562,7 +8512,7 @@ async function runVisualEvaluation(context, config) {
9562
8512
  console.log(`[visual-eval] screenshot: ${screenshot.length}B (${Date.now() - startTime}ms)`);
9563
8513
  const model = config.model ?? getDefaultVisualModel(config.provider);
9564
8514
  const response = await callMultimodalLLM(
9565
- config.provider,
8515
+ config,
9566
8516
  model,
9567
8517
  VISUAL_EVAL_PROMPT,
9568
8518
  screenshot,
@@ -9636,6 +8586,7 @@ async function runVisualEval(context, config) {
9636
8586
  var VISUAL_EVAL_PROMPT;
9637
8587
  var init_visual_evaluator = __esm({
9638
8588
  "src/evaluation/visual-evaluator.ts"() {
8589
+ init_llm_router();
9639
8590
  VISUAL_EVAL_PROMPT = `You are a visual quality evaluator for ggui \u2014 a platform where AI agents generate React UI components on demand.
9640
8591
 
9641
8592
  ## Context
@@ -10873,11 +9824,11 @@ var stateFragments = {
10873
9824
  axis: "state",
10874
9825
  value: "merge",
10875
9826
  cacheTier: "axisDelta",
10876
- promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])`.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
9827
+ promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])` AND re-seed on prop change: `useEffect(() => setItems(props.items ?? []), [props.items])` \u2014 a `ggui_amend` repaint must move the list.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
10877
9828
  boilerplateMarker: [
10878
9829
  "",
10879
9830
  " // \u2500\u2500 Live entity state (merge-by-id) \u2500\u2500",
10880
- " // useState(props.items ?? []); merge stream events by item.id; never append.",
9831
+ " // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.",
10881
9832
  ""
10882
9833
  ].join("\n")
10883
9834
  },
@@ -10995,13 +9946,13 @@ var realtimeFragments = {
10995
9946
  axis: "realtime",
10996
9947
  value: "merge",
10997
9948
  cacheTier: "axisDelta",
10998
- promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
9949
+ promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
10999
9950
  },
11000
9951
  append: {
11001
9952
  axis: "realtime",
11002
9953
  value: "append",
11003
9954
  cacheTier: "axisDelta",
11004
- promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
9955
+ promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
11005
9956
  },
11006
9957
  status: {
11007
9958
  axis: "realtime",
@@ -11019,7 +9970,7 @@ var realtimeFragments = {
11019
9970
  axis: "realtime",
11020
9971
  value: "mixed",
11021
9972
  cacheTier: "axisDelta",
11022
- promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
9973
+ promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => setMessageList(props.messages), [props.messages]); // re-seed on ggui_amend repaint\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
11023
9974
  boilerplateMarker: [
11024
9975
  "",
11025
9976
  " // \u2500\u2500 Mixed stream handlers \u2500\u2500",
@@ -11330,13 +10281,14 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
11330
10281
  const propsFields = [];
11331
10282
  const propsProperties = contract?.propsSpec?.properties ?? {};
11332
10283
  for (const [key, entry] of Object.entries(propsProperties)) {
11333
- const required = entry.required !== false;
10284
+ const required = entry.required === true;
11334
10285
  const nullable = entry.schema.nullable === true;
11335
10286
  const tsType = jsonSchemaTypeToTs(entry.schema);
11336
10287
  const fullType = nullable ? `${tsType} | null` : tsType;
11337
10288
  const parts = [];
11338
10289
  if (entry.description) parts.push(entry.description);
11339
10290
  if (entry.default !== void 0) parts.push(`(default: ${JSON.stringify(entry.default)})`);
10291
+ if (!required) parts.push("(optional \u2014 the agent may omit it; guard or fall back)");
11340
10292
  const desc = parts.length > 0 ? ` // ${parts.join(" ")}` : "";
11341
10293
  propsFields.push(` ${key}${required ? "" : "?"}: ${fullType};${desc}`);
11342
10294
  }
@@ -11893,13 +10845,15 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
11893
10845
 
11894
10846
  ## Defensive coding for absent / late-arriving data
11895
10847
 
11896
- Props arrive via \`ggui_update\` and may be partial on first render. Stream channels start empty and fill over time. Context slots start at their declared default (often \`null\`). **Never assume a field exists before you read it.**
10848
+ Props arrive at mount and may be partial on first render; later prop changes reach the SAME mounted component via \`ggui_amend\` (in-place repaint) on the live channel. Stream channels start empty and fill over time. Context slots start at their declared default (often \`null\`). **Never assume a field exists before you read it.**
11897
10849
 
10850
+ - **Optional contract props**: a \`Props\` field marked \`?\` in the boilerplate may be omitted by the agent ENTIRELY \u2014 the wire gate only enforces \`required: true\` entries, and the harness verifies your component with those props stripped. Guard them (\`?? default\`, \`?.\`, or an empty-state branch) from the FIRST render.
11898
10851
  - **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
11899
10852
  - **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
11900
10853
  - **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
11901
10854
  - **Stream latest**: \`useStream\` returns \`{latest: T | null, all: T[], isComplete: boolean}\`. The default \`all\` is \`[]\` so it's safe to map; \`latest\` is null until the first frame arrives \u2014 guard before reading \`.foo\`.
11902
10855
  - **Stream reconciliation**: when a stream event carries an \`action\` discriminant (e.g. \`create | move | edit | delete\`), the channel is a CRUD feed \u2014 your handler MUST branch on EVERY value: append on \`create\`, drop on \`delete\`, replace-by-id on \`move\` / \`edit\`. Merging only the "edit" case silently loses created and deleted items. Reconcile into the SAME state that seeds from \`props\` (e.g. \`useState(() => props.tasks ?? [])\`) so the seed data and the live feed render as one list \u2014 and handle an event whose id is not yet present (a \`create\` for an unknown item) by inserting it, not ignoring it.
10856
+ - **Props re-sync on repaint**: props the agent owns are the source of truth for display \u2014 prefer rendering DIRECTLY from \`props\` (controlled, no local copy). When local optimistic state genuinely needs a prop seed (a click bumps a count before the agent confirms), it MUST re-sync on prop change \u2014 \`useEffect(() => setCount(props.count ?? 0), [props.count])\` \u2014 or a \`ggui_amend\` repaint applies the new props while the display keeps the stale local value. BAD: \`const [count, setCount] = useState(props.count ?? 0)\` alone. GOOD: the same seed + the one-line re-sync effect.
11903
10857
  - **Loading state**: while data is still absent, render \`<Skeleton>\` placeholders \u2014 never a blank screen. \`<Skeleton variant="text" />\` for a text line, \`variant="circle"\` for an avatar slot, default \`rect\` for a block.
11904
10858
  - **Empty state**: when a list or results array is empty, render \`<EmptyState title="\u2026" description="\u2026" />\` \u2014 a region that renders nothing when empty looks broken to the user.
11905
10859
 
@@ -12027,7 +10981,7 @@ Every value change is mirrored to the host LLM's context automatically (debounce
12027
10981
 
12028
10982
  **When NOT to use it.** Local UI state the contract did NOT declare \u2014 \`isDropdownOpen\`, hover flags, animation phase, ephemeral toggles. For those, use a plain \`useState\` directly. The runtime ignores undeclared state.
12029
10983
 
12030
- **\`contextSpec\` direction is one-way: client \u2192 agent.** The agent uses \`propsSpec\` (via \`ggui_update\`) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` \u2014 there is no return path.
10984
+ **\`contextSpec\` direction is one-way: client \u2192 agent.** The agent uses \`propsSpec\` (via \`ggui_amend\` for in-place repaints of this mount, or \`ggui_update\` when it mints a new history card) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` \u2014 there is no return path.
12031
10985
 
12032
10986
  **Schema mismatches drop silently.** If you set a value that doesn't match the slot's schema (e.g. a string into a \`{type: 'number'}\` slot), the runtime logs a dev \`console.warn\` and skips the post. Make sure your setter calls produce values that match the declared shape.
12033
10987
 
@@ -12835,10 +11789,12 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
12835
11789
  - Borders/dividers \u2192 \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
12836
11790
 
12837
11791
  ### State Colors
12838
- - var(--ggui-color-success) \u2014 success states
12839
- - var(--ggui-color-warning) \u2014 warning states
12840
- - var(--ggui-color-error) \u2014 error states, destructive actions
12841
- - var(--ggui-color-info) \u2014 informational
11792
+ Semantic families ship as SCALES; the \`500\` stop is the role color (per-mode brightness), \`50\` the tint, \`800\` the on-tint text:
11793
+ - var(--ggui-color-success-500) \u2014 success states (tint bg: success-50, text on tint: success-800)
11794
+ - var(--ggui-color-warning-500) \u2014 warning states (warning-50 / warning-800)
11795
+ - var(--ggui-color-error-500) \u2014 error states, destructive actions (error-50 / error-800)
11796
+ - var(--ggui-color-info-500) \u2014 informational (info-50 / info-800)
11797
+ Prefer the primitives' \`tone="success|warning|error|info"\` prop over hand-written vars \u2014 it resolves to the same stops.
12842
11798
 
12843
11799
  ### IMPORTANT: Color Rules
12844
11800
  - **NEVER** use hardcoded hex colors. ONLY use var(--ggui-color-*) tokens.
@@ -13458,6 +12414,31 @@ function runNoPhantomUseState(input) {
13458
12414
  }
13459
12415
  return issues;
13460
12416
  }
12417
+ function runPropSeedNoResync(input) {
12418
+ if (input.compiledCode === null) return [];
12419
+ const src = input.sourceCode;
12420
+ const issues = [];
12421
+ const re = /const\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useState(?:<[^>]*>)?\s*\(\s*props\??\.(\w+)/g;
12422
+ for (const m of src.matchAll(re)) {
12423
+ const [full, stateVar, setter, propName] = m;
12424
+ const idx = m.index ?? 0;
12425
+ const after = src.slice(idx + full.length);
12426
+ if (!new RegExp(`\\b${setter}\\s*\\(`).test(after)) continue;
12427
+ const resync = new RegExp(
12428
+ `\\[[^\\[\\]]*props\\??\\.${propName}\\b[^\\[\\]]*\\]`
12429
+ );
12430
+ if (resync.test(src)) continue;
12431
+ issues.push(
12432
+ mkIssue(
12433
+ "universal.prop_seed_no_resync",
12434
+ `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).`,
12435
+ `Add \`useEffect(() => ${setter}(props.${propName}), [props.${propName}]);\` so agent repaints re-seed the state, or derive the display directly from props.${propName}.`,
12436
+ "warn"
12437
+ )
12438
+ );
12439
+ }
12440
+ return issues;
12441
+ }
13461
12442
  var UNIVERSAL_CHECKS = [
13462
12443
  {
13463
12444
  id: "universal.prop_coverage",
@@ -13476,6 +12457,12 @@ var UNIVERSAL_CHECKS = [
13476
12457
  axis: "render",
13477
12458
  values: ALL_RENDER_VALUES,
13478
12459
  run: runNoPhantomUseState
12460
+ },
12461
+ {
12462
+ id: "universal.prop_seed_no_resync",
12463
+ axis: "render",
12464
+ values: ALL_RENDER_VALUES,
12465
+ run: runPropSeedNoResync
13479
12466
  }
13480
12467
  ];
13481
12468
 
@@ -13495,7 +12482,7 @@ function runStateSeededFromProps(input) {
13495
12482
  mkIssue(
13496
12483
  "state.merge.seeded_from_props",
13497
12484
  `Entity collection "${e.name}" is not seeded from props \u2014 no useState initializer reads props.${e.name}.`,
13498
- `Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` so stream/action updates can merge into live state.`
12485
+ `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).`
13499
12486
  )
13500
12487
  );
13501
12488
  }
@@ -13599,6 +12586,106 @@ var STATE_MERGE_CHECKS = [
13599
12586
  }
13600
12587
  ];
13601
12588
 
12589
+ // src/evaluation/axis-checks/checks/state-ui-affordance.ts
12590
+ var STATE_ARIA_OR_ROLE = /aria-(checked|pressed|selected|expanded|current)\s*=|role\s*=/;
12591
+ var STATE_PRIMITIVE_IN_SUBTREE = /<(Checkbox|Toggle|Tabs|Accordion)[\s/>]/;
12592
+ var SEMANTIC_PRIMITIVES = /* @__PURE__ */ new Set(["Button", "Checkbox", "Toggle", "Link", "MenuItem"]);
12593
+ function collectSetters(src) {
12594
+ const setters = /* @__PURE__ */ new Set();
12595
+ for (const m of src.matchAll(/const\s*\[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState/g)) {
12596
+ setters.add(m[1]);
12597
+ }
12598
+ return setters;
12599
+ }
12600
+ function collectHandlers(src, setters) {
12601
+ const handlers = /* @__PURE__ */ new Set();
12602
+ for (const m of src.matchAll(/const\s+(\w+)\s*=\s*(?:\([^)]*\)|\w+)\s*=>\s*\{?/g)) {
12603
+ const body = src.slice(m.index + m[0].length, m.index + m[0].length + 400);
12604
+ for (const s of setters) {
12605
+ if (body.includes(s)) {
12606
+ handlers.add(m[1]);
12607
+ break;
12608
+ }
12609
+ }
12610
+ }
12611
+ return handlers;
12612
+ }
12613
+ function attrBlock(src, start) {
12614
+ let depth = 0;
12615
+ for (let i = start; i < src.length; i++) {
12616
+ const c = src[i];
12617
+ if (c === "{") depth++;
12618
+ else if (c === "}") depth--;
12619
+ else if (c === ">" && depth === 0) return { block: src.slice(start, i), end: i };
12620
+ }
12621
+ return { block: src.slice(start, start + 900), end: start + 900 };
12622
+ }
12623
+ function onClickValue(block) {
12624
+ const m = block.match(/onClick\s*=\s*\{/);
12625
+ if (!m || m.index === void 0) return null;
12626
+ const open = m.index + m[0].length - 1;
12627
+ let depth = 0;
12628
+ for (let i = open; i < block.length; i++) {
12629
+ if (block[i] === "{") depth++;
12630
+ else if (block[i] === "}") {
12631
+ depth--;
12632
+ if (depth === 0) return block.slice(open + 1, i);
12633
+ }
12634
+ }
12635
+ return block.slice(open + 1);
12636
+ }
12637
+ function subtree(src, pos, tag) {
12638
+ const close = src.indexOf(`</${tag}>`, pos);
12639
+ return src.slice(pos, close === -1 ? pos + 700 : close);
12640
+ }
12641
+ function runStateAriaPresent(input) {
12642
+ if (input.compiledCode === null) return [];
12643
+ const src = input.sourceCode;
12644
+ const setters = collectSetters(src);
12645
+ const handlers = collectHandlers(src, setters);
12646
+ const issues = [];
12647
+ const flaggedLines = /* @__PURE__ */ new Set();
12648
+ const localComponents = /* @__PURE__ */ new Set();
12649
+ for (const m of src.matchAll(/(?:function\s+([A-Z]\w*)\s*\(|const\s+([A-Z]\w*)\s*=)/g)) {
12650
+ localComponents.add(m[1] ?? m[2]);
12651
+ }
12652
+ for (const m of src.matchAll(/<([A-Za-z][A-Za-z0-9]*)\b/g)) {
12653
+ const tag = m[1];
12654
+ if (m.index === void 0) continue;
12655
+ if (localComponents.has(tag)) continue;
12656
+ const { block, end } = attrBlock(src, m.index + m[0].length);
12657
+ const clickValue = onClickValue(block);
12658
+ if (clickValue === null) continue;
12659
+ 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
12660
+ // `on[A-Z]…` callback prop and is invoked from onClick — inside a
12661
+ // ui-affordance component that IS the affordance mutation.
12662
+ /\bon[A-Z]\w*\b/.test(clickValue);
12663
+ if (!mutatesAffordance) continue;
12664
+ if (SEMANTIC_PRIMITIVES.has(tag)) continue;
12665
+ if (STATE_ARIA_OR_ROLE.test(block)) continue;
12666
+ if (STATE_PRIMITIVE_IN_SUBTREE.test(subtree(src, end, tag))) continue;
12667
+ const line = src.slice(0, m.index).split("\n").length;
12668
+ if (flaggedLines.has(line)) continue;
12669
+ flaggedLines.add(line);
12670
+ issues.push(
12671
+ mkIssue(
12672
+ "state.ui_affordance.state_aria_present",
12673
+ `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).`,
12674
+ `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.`
12675
+ )
12676
+ );
12677
+ }
12678
+ return issues;
12679
+ }
12680
+ var STATE_UI_AFFORDANCE_CHECKS = [
12681
+ {
12682
+ id: "state.ui_affordance.state_aria_present",
12683
+ axis: "state",
12684
+ values: ["ui-affordance"],
12685
+ run: runStateAriaPresent
12686
+ }
12687
+ ];
12688
+
13602
12689
  // src/evaluation/axis-checks/checks/realtime.ts
13603
12690
  var REALTIME_ACTIVE = ["merge", "append", "status", "presence", "mixed"];
13604
12691
  function runStreamHandlerPerEvent(input) {
@@ -14288,6 +13375,7 @@ var EXTRA_CHECKS = [
14288
13375
  var REGISTRY = [
14289
13376
  ...UNIVERSAL_CHECKS,
14290
13377
  ...STATE_MERGE_CHECKS,
13378
+ ...STATE_UI_AFFORDANCE_CHECKS,
14291
13379
  ...REALTIME_CHECKS,
14292
13380
  ...WRITES_CHECKS,
14293
13381
  ...STATE_PAYLOAD_CHECKS,
@@ -14458,8 +13546,8 @@ ${cjsResult.code}
14458
13546
  // src/harness/check/runtime-render/load-component.ts
14459
13547
  async function loadComponent(input) {
14460
13548
  const { sourceCode, moduleResolutions = {} } = input;
14461
- const { createRequire: createRequire3 } = await import('module');
14462
- const require_ = createRequire3(import.meta.url);
13549
+ const { createRequire: createRequire4 } = await import('module');
13550
+ const require_ = createRequire4(import.meta.url);
14463
13551
  const sandboxRequire = (id) => {
14464
13552
  if (id in moduleResolutions) return moduleResolutions[id];
14465
13553
  if (id === "react/jsx-runtime" || id === "react/jsx-dev-runtime") {
@@ -14715,6 +13803,47 @@ function wrapDetection(kind, jsx, native, custom) {
14715
13803
  function dedupe(arr) {
14716
13804
  return Array.from(new Set(arr));
14717
13805
  }
13806
+ var installed = false;
13807
+ function installProductionActShim() {
13808
+ if (installed) return;
13809
+ installed = true;
13810
+ const require_ = createRequire(import.meta.url);
13811
+ let reactCjs;
13812
+ try {
13813
+ reactCjs = require_("react");
13814
+ } catch {
13815
+ return;
13816
+ }
13817
+ if (typeof reactCjs.act === "function") {
13818
+ return;
13819
+ }
13820
+ try {
13821
+ const testUtils = require_("react-dom/test-utils");
13822
+ const reactDom = require_("react-dom");
13823
+ testUtils.act = createFlushSyncAct(reactDom.flushSync);
13824
+ } catch {
13825
+ }
13826
+ }
13827
+ function createFlushSyncAct(flushSync) {
13828
+ return (callback) => {
13829
+ let callbackResult;
13830
+ flushSync(() => {
13831
+ callbackResult = callback();
13832
+ });
13833
+ if (isThenable(callbackResult)) {
13834
+ const pending = callbackResult;
13835
+ return new Promise((resolve3, reject) => {
13836
+ pending.then(() => resolve3(), reject);
13837
+ });
13838
+ }
13839
+ return new Promise((resolve3) => {
13840
+ queueMicrotask(resolve3);
13841
+ });
13842
+ };
13843
+ }
13844
+ function isThenable(value) {
13845
+ return typeof value === "object" && value !== null && typeof value.then === "function";
13846
+ }
14718
13847
 
14719
13848
  // src/internal/open-record.ts
14720
13849
  function openRecord(o) {
@@ -14832,6 +13961,7 @@ async function runRenderCheck(input) {
14832
13961
  const probe = createProbe();
14833
13962
  const wireConfig = createProbeWireConfig(probe);
14834
13963
  uninstallSpy = probe.installPostMessageSpy();
13964
+ installProductionActShim();
14835
13965
  const { render, cleanup } = await import('@testing-library/react');
14836
13966
  const userEventModule = await import('@testing-library/user-event');
14837
13967
  const userEvent = userEventModule.default ?? userEventModule;
@@ -14987,6 +14117,50 @@ async function runRenderCheck(input) {
14987
14117
  if (issue) issues.push(issue);
14988
14118
  }
14989
14119
  }
14120
+ const declaredProps = input.contract?.propsSpec?.properties;
14121
+ if (declaredProps) {
14122
+ const optionalFilled = Object.entries(declaredProps).filter(
14123
+ ([name, entry]) => entry.required !== true && input.mockupProps[name] !== void 0
14124
+ ).map(([name]) => name);
14125
+ if (optionalFilled.length > 0) {
14126
+ const requiredOnlyProps = { ...input.mockupProps };
14127
+ for (const name of optionalFilled) delete requiredOnlyProps[name];
14128
+ const omitBoundaryRef = {};
14129
+ class OmitErrorBoundary extends React.Component {
14130
+ state = { caught: false };
14131
+ static getDerivedStateFromError() {
14132
+ return { caught: true };
14133
+ }
14134
+ componentDidCatch(error) {
14135
+ omitBoundaryRef.error ??= error;
14136
+ }
14137
+ render() {
14138
+ if (this.state.caught) return null;
14139
+ return this.props.children;
14140
+ }
14141
+ }
14142
+ try {
14143
+ render(
14144
+ React.createElement(GguiWireProvider, {
14145
+ config: wireConfig,
14146
+ children: React.createElement(OmitErrorBoundary, {
14147
+ children: React.createElement(Component, requiredOnlyProps)
14148
+ })
14149
+ })
14150
+ );
14151
+ } catch (e) {
14152
+ omitBoundaryRef.error ??= e instanceof Error ? e : new Error(String(e));
14153
+ }
14154
+ if (omitBoundaryRef.error) {
14155
+ issues.push({
14156
+ check: "optional-props-omitted",
14157
+ outcome: "failed",
14158
+ subject: optionalFilled.join(", "),
14159
+ reason: `Render crashed when the optional prop(s) ${optionalFilled.map((n) => `'${n}'`).join(", ")} were omitted: ${omitBoundaryRef.error.message}. The contract marks them optional (no \`required: true\`), so a legitimate render may leave them out.`
14160
+ });
14161
+ }
14162
+ }
14163
+ }
14990
14164
  } finally {
14991
14165
  cleanup();
14992
14166
  }
@@ -15154,8 +14328,8 @@ async function setupHappyDom() {
15154
14328
  };
15155
14329
  }
15156
14330
  const { Window } = await import('happy-dom');
15157
- const window2 = new Window({ url: "https://render-check.local" });
15158
- const windowAny = openRecord(window2);
14331
+ const window = new Window({ url: "https://render-check.local" });
14332
+ const windowAny = openRecord(window);
15159
14333
  const keys = [
15160
14334
  "window",
15161
14335
  "document",
@@ -15558,6 +14732,15 @@ function synthesizeFromSchema(schema, hint, warnings, depth) {
15558
14732
  if (schema.anyOf && schema.anyOf.length > 0) {
15559
14733
  return synthesizeFromSchema(schema.anyOf[0], hint, warnings, depth + 1);
15560
14734
  }
14735
+ if (Array.isArray(schema.type)) {
14736
+ const primary = schema.type.find((t) => t !== "null") ?? "null";
14737
+ return synthesizeFromSchema(
14738
+ { ...schema, type: primary },
14739
+ hint,
14740
+ warnings,
14741
+ depth + 1
14742
+ );
14743
+ }
15561
14744
  switch (schema.type) {
15562
14745
  case "string": {
15563
14746
  switch (schema.format) {
@@ -15728,6 +14911,16 @@ function toEvalIssue(issue) {
15728
14911
  description: `Component crashed at runtime: ${issue.reason}`,
15729
14912
  fix: classifyRenderCrashFix(issue.reason)
15730
14913
  };
14914
+ case "optional-props-omitted":
14915
+ return {
14916
+ tier: 0,
14917
+ result,
14918
+ category: "crash",
14919
+ subcategory,
14920
+ severity: "critical",
14921
+ description: `Component crashed when optional props were omitted: ${issue.reason}`,
14922
+ fix: `Guard every optional prop before member access (props.${subject.split(",")[0]?.trim() ?? "x"}?.map(...), a default via destructuring, or an early empty-state return). Optional props (no \`required: true\` in the contract) may be absent at runtime even though the harness fills them.`
14923
+ };
15731
14924
  case "action-wiring": {
15732
14925
  const fix = issue.outcome === "unverified" ? `Source shows the action callback flowing into a non-native or custom-component prop. If wiring is intentional (e.g., Dropdown.onChange, drag-drop), this warn is informational \u2014 manual/browser verification is required to confirm. Otherwise wire to a native onClick={() => ${subject}(payload)} on <button> or design-system <Button>.` : `Wire ${subject}() to a native event prop. Common fix: <Button onClick={() => ${subject}(payload)}>Label</Button>. Source-AST analysis didn't find this wiring in your JSX.`;
15733
14926
  return {
@@ -15774,6 +14967,7 @@ async function warmupRuntimeRenderProbe() {
15774
14967
  missing += 1;
15775
14968
  }
15776
14969
  };
14970
+ installProductionActShim();
15777
14971
  await Promise.all([
15778
14972
  tryLoad("happy-dom"),
15779
14973
  tryLoad("@testing-library/react"),
@@ -16858,6 +16052,14 @@ function mapProviderForEvaluator(provider) {
16858
16052
 
16859
16053
  // src/harness/coding/run-eval-round.ts
16860
16054
  var MAX_FEEDBACK_ISSUES = 3;
16055
+ function selectActionableFeedback(issues) {
16056
+ const pool = issues.filter((i) => i.result === "fail" || i.result === "warn");
16057
+ const reserved = pool.find((i) => i.tier === 0 && i.result === "fail");
16058
+ return [
16059
+ ...reserved ? [reserved] : [],
16060
+ ...pool.filter((i) => i !== reserved).sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1))
16061
+ ].slice(0, MAX_FEEDBACK_ISSUES);
16062
+ }
16861
16063
  function fingerprintFail(issue) {
16862
16064
  const cat = issue.category ?? "";
16863
16065
  const sub = issue.subcategory ?? "";
@@ -17026,7 +16228,20 @@ async function runEvalRound(ctx, input) {
17026
16228
  },
17027
16229
  {
17028
16230
  provider: mapProviderForEvaluator(evaluationAgent.provider),
17029
- model: evaluationAgent.model
16231
+ model: evaluationAgent.model,
16232
+ // #484 — thread the coding-agent's routing override
16233
+ // through to the evaluator so its LLM call never falls
16234
+ // back to `process.env` either (the eval calls run
16235
+ // inside the same concurrent generation, so the race is
16236
+ // real here too, not just on the coding turn).
16237
+ routeOverride: evaluationAgent.routeOverride,
16238
+ // #489 — thread the same provider-429-retry observer.
16239
+ // Without this, a rate-limited retry inside the
16240
+ // evaluation agent's apiCall() happens (the base-class
16241
+ // retry loop is unconditional) but never reaches the
16242
+ // caller, silently missing the host application's
16243
+ // structured log.
16244
+ onRetry: evaluationAgent.onRetry
17030
16245
  },
17031
16246
  preWarmedContext
17032
16247
  ) : null,
@@ -17037,7 +16252,15 @@ async function runEvalRound(ctx, input) {
17037
16252
  model: visualEvalAgent.model,
17038
16253
  passThreshold: visualThreshold,
17039
16254
  sampleProps: visualEvaluation?.sampleProps,
17040
- viewport: visualEvaluation?.viewport
16255
+ viewport: visualEvaluation?.viewport,
16256
+ // #484/#489 — same threading as the LLM-eval leg above:
16257
+ // the visual eval's multimodal call runs inside the same
16258
+ // concurrent generation, so it needs the same routing
16259
+ // override (never fall back to `process.env`) and the
16260
+ // same provider-429-retry observer (retries reach the
16261
+ // host application's structured log).
16262
+ routeOverride: visualEvalAgent.routeOverride,
16263
+ onRetry: visualEvalAgent.onRetry
17041
16264
  }
17042
16265
  ) : null
17043
16266
  ]);
@@ -17256,7 +16479,7 @@ async function runEvalRound(ctx, input) {
17256
16479
  lastDiffFailed: false
17257
16480
  };
17258
16481
  }
17259
- const allActionable = evalResult.issues.filter((i) => i.result === "fail" || i.result === "warn").sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1)).slice(0, MAX_FEEDBACK_ISSUES);
16482
+ const allActionable = selectActionableFeedback(evalResult.issues);
17260
16483
  const issueLines = allActionable.map((issue) => {
17261
16484
  const tag = issue.result === "fail" ? "FAIL" : "WARN";
17262
16485
  return `[${tag}] ${issue.category}${issue.subcategory ? "/" + issue.subcategory : ""}: ${issue.description}
@@ -17271,7 +16494,16 @@ async function runEvalRound(ctx, input) {
17271
16494
  evalResult,
17272
16495
  evalRoundsUsed,
17273
16496
  prevModeSubcats: updatedPrevModeSubcats,
17274
- prevFailFingerprints: currFailFingerprints,
16497
+ // DELIVERED fails only (Exp 54 P1 addendum, proposal 2): the
16498
+ // stuck detector's premise is "the LLM saw these and did not
16499
+ // resolve them" — recording fails that the MAX_FEEDBACK_ISSUES
16500
+ // cap cut from the feedback turns truncation into a premature
16501
+ // stuck-exit (the model never saw the issue it is blamed for
16502
+ // not fixing). An undelivered fail recurring next round now
16503
+ // reads as NEW, gets delivered, and earns its fix round.
16504
+ prevFailFingerprints: new Set(
16505
+ allActionable.filter((i) => i.result === "fail").map(fingerprintFail)
16506
+ ),
17275
16507
  preWarmedContext,
17276
16508
  evalTokens,
17277
16509
  evalLlmMs,
@@ -17674,7 +16906,7 @@ async function initSession(input) {
17674
16906
  await workspace.stage();
17675
16907
  await workspace.commit("scaffold: boilerplate");
17676
16908
  const systemPrompt = input.systemPromptOverride ?? harness.how.systemPrompt;
17677
- const codingAgent = createAgent(agents.coding.provider);
16909
+ const codingAgent = createAgent(agents.coding);
17678
16910
  await codingAgent.warmCache(
17679
16911
  agents.coding.model,
17680
16912
  systemPrompt,
@@ -17763,8 +16995,18 @@ async function dispatchGeneration(params) {
17763
16995
  }
17764
16996
  const routerProvider = mapProviderForLLMRouter(params.provider);
17765
16997
  const resolvedModel = params.models?.coding ?? params.models?.default ?? params.model;
17766
- const codingAgent = { provider: routerProvider, model: resolvedModel };
17767
- const evaluationAgent = params.models?.evaluation ? { provider: routerProvider, model: params.models.evaluation } : void 0;
16998
+ const codingAgent = {
16999
+ provider: routerProvider,
17000
+ model: resolvedModel,
17001
+ routeOverride: params.routeOverride,
17002
+ onRetry: params.onRetry
17003
+ };
17004
+ const evaluationAgent = params.models?.evaluation ? {
17005
+ provider: routerProvider,
17006
+ model: params.models.evaluation,
17007
+ routeOverride: params.routeOverride,
17008
+ onRetry: params.onRetry
17009
+ } : void 0;
17768
17010
  const effectiveContract = params.contract;
17769
17011
  const sessionParams = {
17770
17012
  userPrompt: params.originalPrompt ?? params.userPrompt,