@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, pathToFileURL } from 'url';
16
19
  import { PlaywrightNotAvailableError, validateContractBehavior } from '@ggui-ai/ui-visual-tester';
@@ -1151,14 +1154,16 @@ var OpenRouterError;
1151
1154
  var init_types = __esm({
1152
1155
  "src/adapters/openrouter/types.ts"() {
1153
1156
  OpenRouterError = class extends Error {
1154
- constructor(message, status, code) {
1157
+ constructor(message, status, code, headers) {
1155
1158
  super(message);
1156
1159
  this.status = status;
1157
1160
  this.code = code;
1161
+ this.headers = headers;
1158
1162
  this.name = "OpenRouterError";
1159
1163
  }
1160
1164
  status;
1161
1165
  code;
1166
+ headers;
1162
1167
  get isTransient() {
1163
1168
  return this.status === 408 || this.status === 429 || this.status === 502 || this.status === 503;
1164
1169
  }
@@ -1203,7 +1208,7 @@ var init_client2 = __esm({
1203
1208
  if (!response.ok) {
1204
1209
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
1205
1210
  const message = errorBody.error?.message ?? response.statusText;
1206
- throw new OpenRouterError(message, response.status);
1211
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
1207
1212
  }
1208
1213
  return response.json();
1209
1214
  }
@@ -1221,7 +1226,7 @@ var init_client2 = __esm({
1221
1226
  if (!response.ok) {
1222
1227
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
1223
1228
  const message = errorBody.error?.message ?? response.statusText;
1224
- throw new OpenRouterError(message, response.status);
1229
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
1225
1230
  }
1226
1231
  if (!response.body) {
1227
1232
  throw new OpenRouterError("No response body for streaming request", 500);
@@ -1888,8 +1893,8 @@ var init_ui_compiler = __esm({
1888
1893
  "@ggui-ai/design/*",
1889
1894
  "@ggui-ai/wire",
1890
1895
  "@ggui-ai/wire/*",
1891
- "@ggui-ai/react",
1892
- "@ggui-ai/react/*"
1896
+ "@ggui-ai/mcp-apps-react",
1897
+ "@ggui-ai/mcp-apps-react/*"
1893
1898
  ];
1894
1899
  MAX_BUNDLE_SIZE = 2 * 1024 * 1024;
1895
1900
  UiValidationError = class extends Error {
@@ -2074,6 +2079,15 @@ function jsonSchemaTypeToTs(schema) {
2074
2079
  if (schema.enum?.length) {
2075
2080
  return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
2076
2081
  }
2082
+ if (Array.isArray(schema.type)) {
2083
+ const { nullable: _nullable, ...rest } = schema;
2084
+ const members = [...new Set(schema.type)].map(
2085
+ (member) => jsonSchemaTypeToTs({ ...rest, type: member })
2086
+ );
2087
+ const unique = [...new Set(members)];
2088
+ if (schema.nullable && !unique.includes("null")) unique.push("null");
2089
+ return unique.join(" | ");
2090
+ }
2077
2091
  let result;
2078
2092
  switch (schema.type) {
2079
2093
  case "string":
@@ -2091,7 +2105,8 @@ function jsonSchemaTypeToTs(schema) {
2091
2105
  case "array": {
2092
2106
  if (schema.items) {
2093
2107
  const itemType = jsonSchemaTypeToTs(schema.items);
2094
- result = schema.items.type === "object" && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
2108
+ const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
2109
+ result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
2095
2110
  } else {
2096
2111
  result = "unknown[]";
2097
2112
  }
@@ -4109,6 +4124,29 @@ function errorSummary(e) {
4109
4124
  }
4110
4125
  return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
4111
4126
  }
4127
+ function isRateLimited(e) {
4128
+ if (!(e instanceof Error) || !("status" in e)) return false;
4129
+ return e.status === 429;
4130
+ }
4131
+ function extractStatus(e) {
4132
+ if (!(e instanceof Error) || !("status" in e)) return void 0;
4133
+ const status = e.status;
4134
+ return typeof status === "number" ? status : void 0;
4135
+ }
4136
+ function extractRetryAfterSec(e) {
4137
+ if (!(e instanceof Error) || !("headers" in e)) return void 0;
4138
+ const headers = e.headers;
4139
+ if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
4140
+ return void 0;
4141
+ }
4142
+ const raw = headers.get("retry-after");
4143
+ if (raw === null) return void 0;
4144
+ const parsed = Number(raw);
4145
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
4146
+ }
4147
+ function sleep(ms) {
4148
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
4149
+ }
4112
4150
  function interactionText(interaction) {
4113
4151
  let text = "";
4114
4152
  for (const step of interaction.steps ?? []) {
@@ -4130,25 +4168,41 @@ function functionCallSteps(interaction) {
4130
4168
  function toUserInputStep(text) {
4131
4169
  return { type: "user_input", content: [{ type: "text", text }] };
4132
4170
  }
4133
- function createAgent(provider) {
4171
+ function createAgent(providerOrConfig) {
4172
+ const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
4173
+ const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
4174
+ const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
4134
4175
  switch (provider) {
4135
4176
  case "anthropic":
4136
- return new AnthropicAgent();
4177
+ return new AnthropicAgent(routeOverride, onRetry);
4137
4178
  case "openai":
4138
- return new OpenAIAgent();
4179
+ return new OpenAIAgent(routeOverride, onRetry);
4139
4180
  case "google":
4140
- return new GoogleAgent();
4181
+ return new GoogleAgent(routeOverride, onRetry);
4141
4182
  case "openrouter":
4142
- return new OpenRouterAgent();
4183
+ return new OpenRouterAgent(routeOverride, onRetry);
4184
+ }
4185
+ }
4186
+ function createVisionAgent(config) {
4187
+ switch (config.provider) {
4188
+ case "anthropic":
4189
+ return new AnthropicAgent(config.routeOverride, config.onRetry);
4190
+ case "google":
4191
+ return new GoogleAgent(config.routeOverride, config.onRetry);
4143
4192
  }
4144
4193
  }
4145
- var LLMAgent, AnthropicAgent, OpenAIAgent, GoogleAgent, OpenRouterAgent;
4194
+ 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;
4146
4195
  var init_llm_router = __esm({
4147
4196
  "src/harness/llm-router.ts"() {
4148
4197
  init_client();
4149
4198
  init_provider_router();
4150
4199
  init_tool_bridge();
4151
4200
  init_llm_trace_sink();
4201
+ MAX_RETRY_ATTEMPTS = 2;
4202
+ RETRY_AFTER_CAP_SEC = 15;
4203
+ DEFAULT_BACKOFF_BASE_MS = 1e3;
4204
+ DEFAULT_BACKOFF_MAX_MS = 8e3;
4205
+ MAX_TOTAL_RETRY_DELAY_MS = 2e4;
4152
4206
  LLMAgent = class {
4153
4207
  client = null;
4154
4208
  // ── Session state for server-side chaining ──────────────
@@ -4156,6 +4210,19 @@ var init_llm_router = __esm({
4156
4210
  // Enables turn 2+ to skip re-sending system prompt + history.
4157
4211
  // Call resetSession() between independent generation runs.
4158
4212
  lastSessionId;
4213
+ /**
4214
+ * Explicit per-call routing override — see `AgentConfig.routeOverride`.
4215
+ * Absent when constructed via the bare `new XAgent()` form (every
4216
+ * existing call site), so `createClient()`/`resolveModel()` fall
4217
+ * back to `process.env` exactly as before.
4218
+ */
4219
+ routeOverride;
4220
+ /** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
4221
+ onRetry;
4222
+ constructor(routeOverride, onRetry) {
4223
+ this.routeOverride = routeOverride;
4224
+ this.onRetry = onRetry;
4225
+ }
4159
4226
  async getClient() {
4160
4227
  if (!this.client) {
4161
4228
  this.client = await this.createClient();
@@ -4189,36 +4256,104 @@ var init_llm_router = __esm({
4189
4256
  async sendToolResult(_results) {
4190
4257
  }
4191
4258
  /**
4192
- * Execute an API call. No retry — if it fails, it fails.
4193
- * Logs the error with provider context and re-throws.
4259
+ * Execute an API call, retrying once or twice on HTTP 429
4260
+ * (rate-limited) before giving up (#489).
4261
+ *
4262
+ * Policy: on a 429, honor the provider's `Retry-After` header when
4263
+ * present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
4264
+ * longer wait is treated as "don't retry", not "wait longer": making
4265
+ * the caller sit through 15+ more seconds on a single failed call is
4266
+ * a bad experience regardless of what's driving the request);
4267
+ * otherwise fall back to exponential backoff with jitter, capped at
4268
+ * `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
4269
+ * `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
4270
+ * exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
4271
+ * non-429 error, or a 429 with no attempts left, is logged and
4272
+ * re-thrown immediately — unchanged from the pre-#489 behavior.
4273
+ *
4274
+ * **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
4275
+ * bounds a single `apiCall()` invocation, not a whole generation. A
4276
+ * generation issues many sequential `apiCall()` calls (coding turns,
4277
+ * tool-result round-trips, eval rounds); under sustained throttling
4278
+ * each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
4279
+ * a generation with k rate-limited calls can add up to roughly `k *
4280
+ * MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
4281
+ * resources (e.g. the caller's concurrency slot) — not a flat 20s
4282
+ * ceiling. No shared per-generation budget is threaded here by
4283
+ * design (accepted ruling, not an oversight): the caller's own
4284
+ * queue/admission backstop is what bounds how long other
4285
+ * queued work waits, independent of any one generation's retry
4286
+ * total.
4194
4287
  */
4195
4288
  async apiCall(fn) {
4196
4289
  const start = Date.now();
4197
- try {
4198
- return await fn();
4199
- } catch (e) {
4200
- const ms = Date.now() - start;
4201
- console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
4202
- throw e;
4290
+ let totalDelayMs = 0;
4291
+ for (let attempt = 0; ; attempt++) {
4292
+ try {
4293
+ return await fn();
4294
+ } catch (e) {
4295
+ const ms = Date.now() - start;
4296
+ if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
4297
+ console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
4298
+ throw e;
4299
+ }
4300
+ const retryAfterSec = extractRetryAfterSec(e);
4301
+ if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
4302
+ console.error(
4303
+ `[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
4304
+ );
4305
+ throw e;
4306
+ }
4307
+ const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
4308
+ if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
4309
+ console.error(
4310
+ `[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
4311
+ );
4312
+ throw e;
4313
+ }
4314
+ totalDelayMs += delayMs;
4315
+ const info = {
4316
+ provider: this.provider,
4317
+ attempt: attempt + 1,
4318
+ maxAttempts: MAX_RETRY_ATTEMPTS,
4319
+ status: extractStatus(e),
4320
+ retryAfterSec,
4321
+ delayMs,
4322
+ message: errorSummary(e)
4323
+ };
4324
+ console.warn(
4325
+ `[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
4326
+ );
4327
+ try {
4328
+ this.onRetry?.(info);
4329
+ } catch (observerError) {
4330
+ console.warn(
4331
+ `[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
4332
+ );
4333
+ }
4334
+ await sleep(delayMs);
4335
+ }
4203
4336
  }
4204
4337
  }
4205
4338
  };
4206
4339
  AnthropicAgent = class extends LLMAgent {
4207
4340
  provider = "anthropic";
4208
4341
  resolveModel(model) {
4209
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
4342
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
4343
+ if (useBedrock) {
4210
4344
  return getBedrockModelId(model);
4211
4345
  }
4212
4346
  return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
4213
4347
  }
4214
4348
  async createClient() {
4215
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
4349
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
4350
+ if (useBedrock) {
4216
4351
  const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk');
4217
4352
  return new AnthropicBedrock({
4218
4353
  awsRegion: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
4219
4354
  });
4220
4355
  }
4221
- return createAnthropicClient(process.env.ANTHROPIC_API_KEY);
4356
+ return createAnthropicClient(this.routeOverride?.apiKey ?? process.env.ANTHROPIC_API_KEY);
4222
4357
  }
4223
4358
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
4224
4359
  const client = await this.getClient();
@@ -4290,6 +4425,73 @@ var init_llm_router = __esm({
4290
4425
  throw e;
4291
4426
  }
4292
4427
  }
4428
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
4429
+ const client = await this.getClient();
4430
+ const traceId = newLlmTraceId();
4431
+ const startedAt = Date.now();
4432
+ const resolvedModel = this.resolveModel(model);
4433
+ const imageBlock = {
4434
+ type: "image",
4435
+ source: {
4436
+ type: "base64",
4437
+ media_type: image.mediaType,
4438
+ data: image.base64
4439
+ }
4440
+ };
4441
+ try {
4442
+ const response = await this.apiCall(
4443
+ () => client.messages.create({
4444
+ model: resolvedModel,
4445
+ max_tokens: maxTokens ?? 1500,
4446
+ system: systemPrompt,
4447
+ messages: [
4448
+ {
4449
+ role: "user",
4450
+ content: [imageBlock, { type: "text", text: userPrompt }]
4451
+ }
4452
+ ]
4453
+ })
4454
+ );
4455
+ const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
4456
+ const endedAt = Date.now();
4457
+ emitLlmTraceEvent({
4458
+ id: traceId,
4459
+ at: startedAt,
4460
+ endedAt,
4461
+ durationMs: endedAt - startedAt,
4462
+ provider: "anthropic",
4463
+ model: resolvedModel,
4464
+ kind: "callVision",
4465
+ systemPrompt,
4466
+ userPrompt,
4467
+ result: {
4468
+ inputTokens: response.usage.input_tokens,
4469
+ outputTokens: response.usage.output_tokens,
4470
+ text
4471
+ }
4472
+ });
4473
+ return {
4474
+ text,
4475
+ inputTokens: response.usage.input_tokens,
4476
+ outputTokens: response.usage.output_tokens
4477
+ };
4478
+ } catch (e) {
4479
+ const endedAt = Date.now();
4480
+ emitLlmTraceEvent({
4481
+ id: traceId,
4482
+ at: startedAt,
4483
+ endedAt,
4484
+ durationMs: endedAt - startedAt,
4485
+ provider: "anthropic",
4486
+ model: resolvedModel,
4487
+ kind: "callVision",
4488
+ systemPrompt,
4489
+ userPrompt,
4490
+ error: { message: e instanceof Error ? e.message : String(e) }
4491
+ });
4492
+ throw e;
4493
+ }
4494
+ }
4293
4495
  async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
4294
4496
  const client = await this.getClient();
4295
4497
  const traceId = newLlmTraceId();
@@ -4489,7 +4691,7 @@ var init_llm_router = __esm({
4489
4691
  }
4490
4692
  async createClient() {
4491
4693
  const { default: OpenAISDK } = await import('openai');
4492
- return new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY });
4694
+ return new OpenAISDK({ apiKey: this.routeOverride?.apiKey ?? process.env.OPENAI_API_KEY });
4493
4695
  }
4494
4696
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
4495
4697
  const client = await this.getClient();
@@ -4639,7 +4841,7 @@ var init_llm_router = __esm({
4639
4841
  async createClient() {
4640
4842
  const { GoogleGenAI: GoogleGenAISDK } = await import('@google/genai');
4641
4843
  return new GoogleGenAISDK({
4642
- apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
4844
+ apiKey: this.routeOverride?.apiKey ?? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
4643
4845
  httpOptions: { timeout: 3e5 }
4644
4846
  // 5 min — Pro models can be slow
4645
4847
  });
@@ -4664,6 +4866,72 @@ var init_llm_router = __esm({
4664
4866
  outputTokens: interaction.usage?.total_output_tokens ?? 0
4665
4867
  };
4666
4868
  }
4869
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
4870
+ const client = await this.getClient();
4871
+ const traceId = newLlmTraceId();
4872
+ const startedAt = Date.now();
4873
+ const resolvedModel = this.resolveModel(model);
4874
+ try {
4875
+ const response = await this.apiCall(
4876
+ () => client.models.generateContent({
4877
+ model: resolvedModel,
4878
+ contents: [
4879
+ {
4880
+ role: "user",
4881
+ parts: [
4882
+ { inlineData: { mimeType: image.mediaType, data: image.base64 } },
4883
+ { text: userPrompt }
4884
+ ]
4885
+ }
4886
+ ],
4887
+ config: {
4888
+ systemInstruction: systemPrompt,
4889
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {}
4890
+ }
4891
+ })
4892
+ );
4893
+ const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
4894
+ const usage = response.usageMetadata;
4895
+ const result = {
4896
+ text,
4897
+ inputTokens: usage?.promptTokenCount ?? 0,
4898
+ outputTokens: usage?.candidatesTokenCount ?? 0
4899
+ };
4900
+ const endedAt = Date.now();
4901
+ emitLlmTraceEvent({
4902
+ id: traceId,
4903
+ at: startedAt,
4904
+ endedAt,
4905
+ durationMs: endedAt - startedAt,
4906
+ provider: "google",
4907
+ model: resolvedModel,
4908
+ kind: "callVision",
4909
+ systemPrompt,
4910
+ userPrompt,
4911
+ result: {
4912
+ inputTokens: result.inputTokens,
4913
+ outputTokens: result.outputTokens,
4914
+ text
4915
+ }
4916
+ });
4917
+ return result;
4918
+ } catch (e) {
4919
+ const endedAt = Date.now();
4920
+ emitLlmTraceEvent({
4921
+ id: traceId,
4922
+ at: startedAt,
4923
+ endedAt,
4924
+ durationMs: endedAt - startedAt,
4925
+ provider: "google",
4926
+ model: resolvedModel,
4927
+ kind: "callVision",
4928
+ systemPrompt,
4929
+ userPrompt,
4930
+ error: { message: e instanceof Error ? e.message : String(e) }
4931
+ });
4932
+ throw e;
4933
+ }
4934
+ }
4667
4935
  async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
4668
4936
  const client = await this.getClient();
4669
4937
  const interactionTools = tools.map((t) => ({
@@ -4866,7 +5134,7 @@ var init_llm_router = __esm({
4866
5134
  async createClient() {
4867
5135
  const { OpenRouterClient: OpenRouterClient2 } = await Promise.resolve().then(() => (init_client2(), client_exports));
4868
5136
  return new OpenRouterClient2({
4869
- apiKey: process.env.OPENROUTER_API_KEY
5137
+ apiKey: this.routeOverride?.apiKey ?? process.env.OPENROUTER_API_KEY
4870
5138
  });
4871
5139
  }
4872
5140
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
@@ -6008,7 +6276,11 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
6008
6276
  designSystemSummary: context.designContext
6009
6277
  });
6010
6278
  const numberedSource = context.sourceCode.split("\n").map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
6011
- const agent = createAgent(routerProvider);
6279
+ const agent = createAgent({
6280
+ provider: routerProvider,
6281
+ routeOverride: config.routeOverride,
6282
+ onRetry: config.onRetry
6283
+ });
6012
6284
  const evalStart = Date.now();
6013
6285
  const totalCriteriaCount = TIER_1_CRITERIA.length + TIER_2_CRITERIA.length;
6014
6286
  const results = await Promise.all([
@@ -6018,1399 +6290,107 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
6018
6290
  const allIssues = [];
6019
6291
  const passCategories = [];
6020
6292
  let totalIn = 0;
6021
- let totalOut = 0;
6022
- let cachedTokensEstimate = 0;
6023
- for (const r of results) {
6024
- allIssues.push(...r.issues);
6025
- if (r.pass) {
6026
- passCategories.push(r.criterion);
6027
- }
6028
- totalIn += r.tokens.in;
6029
- totalOut += r.tokens.out;
6030
- }
6031
- const firstResult = results[0];
6032
- if (firstResult && firstResult.tokens.in > 0) {
6033
- cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
6034
- }
6035
- const wallMs = Date.now() - evalStart;
6036
- for (const r of results) {
6037
- const issueCount = r.issues.length;
6038
- const status = r.pass ? "PASS" : r.tier === 1 ? "FAIL" : r.issues.some((i) => i.result === "fail") ? "FAIL" : "WARN";
6039
- const issueLabel = issueCount > 0 ? ` (${issueCount} issue${issueCount > 1 ? "s" : ""})` : "";
6040
- console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
6041
- }
6042
- console.log(
6043
- `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cached~=${cachedTokensEstimate} | issues=${allIssues.length} pass=${passCategories.length}`
6044
- );
6045
- return {
6046
- issues: allIssues,
6047
- pass: passCategories,
6048
- inputTokens: totalIn,
6049
- outputTokens: totalOut
6050
- };
6051
- }
6052
- var TIER_1_CRITERIA, TIER_2_CRITERIA, TIER_1_CATEGORIES, TIER_2_CATEGORIES, VALID_CATEGORIES;
6053
- var init_llm_evaluator = __esm({
6054
- "src/evaluation/llm-evaluator.ts"() {
6055
- init_llm_router();
6056
- init_types_public();
6057
- TIER_1_CRITERIA = ["functionality", "crash"];
6058
- TIER_2_CRITERIA = ["interactivity", "accessibility", "layout", "loading", "visual"];
6059
- TIER_1_CATEGORIES = /* @__PURE__ */ new Set(["functionality", "crash"]);
6060
- TIER_2_CATEGORIES = /* @__PURE__ */ new Set([
6061
- "interactivity",
6062
- "accessibility",
6063
- "layout",
6064
- "loading",
6065
- "visual"
6066
- ]);
6067
- VALID_CATEGORIES = /* @__PURE__ */ new Set([
6068
- ...TIER_1_CATEGORIES,
6069
- ...TIER_2_CATEGORIES
6070
- ]);
6071
- }
6072
- });
6073
-
6074
- // src/evaluation/cost-tracker.ts
6075
- var cost_tracker_exports = {};
6076
- __export(cost_tracker_exports, {
6077
- CostTracker: () => CostTracker,
6078
- pricePer1k: () => pricePer1k
6079
- });
6080
- function resolveRegistryId(model) {
6081
- const keys = Object.keys(MODEL_REGISTRY);
6082
- if (keys.includes(model)) return model;
6083
- const bySuffix = keys.find((id) => id.endsWith(`/${model}`));
6084
- if (bySuffix) return bySuffix;
6085
- const undated = model.replace(/-\d{8}$/, "");
6086
- return keys.find((id) => id.endsWith(`/${undated}`)) ?? null;
6087
- }
6088
- function pricePer1k(model) {
6089
- const id = resolveRegistryId(model);
6090
- if (id === null) return FALLBACK_PER_1K;
6091
- const { costs } = MODEL_REGISTRY[id];
6092
- return { input: costs.inputPer1M / 1e3, output: costs.outputPer1M / 1e3 };
6093
- }
6094
- var FALLBACK_PER_1K, CostTracker;
6095
- var init_cost_tracker = __esm({
6096
- "src/evaluation/cost-tracker.ts"() {
6097
- FALLBACK_PER_1K = { input: 3e-3, output: 0.015 };
6098
- CostTracker = class {
6099
- constructor(maxBudget) {
6100
- this.maxBudget = maxBudget;
6101
- }
6102
- maxBudget;
6103
- totalCost = 0;
6104
- record(model, inputTokens, outputTokens) {
6105
- const prices = pricePer1k(model);
6106
- const cost = inputTokens / 1e3 * prices.input + outputTokens / 1e3 * prices.output;
6107
- this.totalCost += cost;
6108
- return cost;
6109
- }
6110
- canContinue() {
6111
- if (this.maxBudget === null) return true;
6112
- return this.totalCost < this.maxBudget;
6113
- }
6114
- getTotal() {
6115
- return this.totalCost;
6116
- }
6117
- getRemaining() {
6118
- if (this.maxBudget === null) return null;
6119
- return Math.max(0, this.maxBudget - this.totalCost);
6120
- }
6121
- };
6122
- }
6123
- });
6124
-
6125
- // ../../node_modules/ms/index.js
6126
- var require_ms = __commonJS({
6127
- "../../node_modules/ms/index.js"(exports, module) {
6128
- var s = 1e3;
6129
- var m = s * 60;
6130
- var h = m * 60;
6131
- var d = h * 24;
6132
- var w = d * 7;
6133
- var y = d * 365.25;
6134
- module.exports = function(val, options) {
6135
- options = options || {};
6136
- var type = typeof val;
6137
- if (type === "string" && val.length > 0) {
6138
- return parse(val);
6139
- } else if (type === "number" && isFinite(val)) {
6140
- return options.long ? fmtLong(val) : fmtShort(val);
6141
- }
6142
- throw new Error(
6143
- "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
6144
- );
6145
- };
6146
- function parse(str) {
6147
- str = String(str);
6148
- if (str.length > 100) {
6149
- return;
6150
- }
6151
- 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(
6152
- str
6153
- );
6154
- if (!match) {
6155
- return;
6156
- }
6157
- var n = parseFloat(match[1]);
6158
- var type = (match[2] || "ms").toLowerCase();
6159
- switch (type) {
6160
- case "years":
6161
- case "year":
6162
- case "yrs":
6163
- case "yr":
6164
- case "y":
6165
- return n * y;
6166
- case "weeks":
6167
- case "week":
6168
- case "w":
6169
- return n * w;
6170
- case "days":
6171
- case "day":
6172
- case "d":
6173
- return n * d;
6174
- case "hours":
6175
- case "hour":
6176
- case "hrs":
6177
- case "hr":
6178
- case "h":
6179
- return n * h;
6180
- case "minutes":
6181
- case "minute":
6182
- case "mins":
6183
- case "min":
6184
- case "m":
6185
- return n * m;
6186
- case "seconds":
6187
- case "second":
6188
- case "secs":
6189
- case "sec":
6190
- case "s":
6191
- return n * s;
6192
- case "milliseconds":
6193
- case "millisecond":
6194
- case "msecs":
6195
- case "msec":
6196
- case "ms":
6197
- return n;
6198
- default:
6199
- return void 0;
6200
- }
6201
- }
6202
- function fmtShort(ms) {
6203
- var msAbs = Math.abs(ms);
6204
- if (msAbs >= d) {
6205
- return Math.round(ms / d) + "d";
6206
- }
6207
- if (msAbs >= h) {
6208
- return Math.round(ms / h) + "h";
6209
- }
6210
- if (msAbs >= m) {
6211
- return Math.round(ms / m) + "m";
6212
- }
6213
- if (msAbs >= s) {
6214
- return Math.round(ms / s) + "s";
6215
- }
6216
- return ms + "ms";
6217
- }
6218
- function fmtLong(ms) {
6219
- var msAbs = Math.abs(ms);
6220
- if (msAbs >= d) {
6221
- return plural(ms, msAbs, d, "day");
6222
- }
6223
- if (msAbs >= h) {
6224
- return plural(ms, msAbs, h, "hour");
6225
- }
6226
- if (msAbs >= m) {
6227
- return plural(ms, msAbs, m, "minute");
6228
- }
6229
- if (msAbs >= s) {
6230
- return plural(ms, msAbs, s, "second");
6231
- }
6232
- return ms + " ms";
6233
- }
6234
- function plural(ms, msAbs, n, name) {
6235
- var isPlural = msAbs >= n * 1.5;
6236
- return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
6237
- }
6238
- }
6239
- });
6240
-
6241
- // ../../node_modules/debug/src/common.js
6242
- var require_common = __commonJS({
6243
- "../../node_modules/debug/src/common.js"(exports, module) {
6244
- function setup(env) {
6245
- createDebug.debug = createDebug;
6246
- createDebug.default = createDebug;
6247
- createDebug.coerce = coerce;
6248
- createDebug.disable = disable;
6249
- createDebug.enable = enable;
6250
- createDebug.enabled = enabled;
6251
- createDebug.humanize = require_ms();
6252
- createDebug.destroy = destroy;
6253
- Object.keys(env).forEach((key) => {
6254
- createDebug[key] = env[key];
6255
- });
6256
- createDebug.names = [];
6257
- createDebug.skips = [];
6258
- createDebug.formatters = {};
6259
- function selectColor(namespace) {
6260
- let hash = 0;
6261
- for (let i = 0; i < namespace.length; i++) {
6262
- hash = (hash << 5) - hash + namespace.charCodeAt(i);
6263
- hash |= 0;
6264
- }
6265
- return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
6266
- }
6267
- createDebug.selectColor = selectColor;
6268
- function createDebug(namespace) {
6269
- let prevTime;
6270
- let enableOverride = null;
6271
- let namespacesCache;
6272
- let enabledCache;
6273
- function debug(...args) {
6274
- if (!debug.enabled) {
6275
- return;
6276
- }
6277
- const self = debug;
6278
- const curr = Number(/* @__PURE__ */ new Date());
6279
- const ms = curr - (prevTime || curr);
6280
- self.diff = ms;
6281
- self.prev = prevTime;
6282
- self.curr = curr;
6283
- prevTime = curr;
6284
- args[0] = createDebug.coerce(args[0]);
6285
- if (typeof args[0] !== "string") {
6286
- args.unshift("%O");
6287
- }
6288
- let index = 0;
6289
- args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
6290
- if (match === "%%") {
6291
- return "%";
6292
- }
6293
- index++;
6294
- const formatter = createDebug.formatters[format];
6295
- if (typeof formatter === "function") {
6296
- const val = args[index];
6297
- match = formatter.call(self, val);
6298
- args.splice(index, 1);
6299
- index--;
6300
- }
6301
- return match;
6302
- });
6303
- createDebug.formatArgs.call(self, args);
6304
- const logFn = self.log || createDebug.log;
6305
- logFn.apply(self, args);
6306
- }
6307
- debug.namespace = namespace;
6308
- debug.useColors = createDebug.useColors();
6309
- debug.color = createDebug.selectColor(namespace);
6310
- debug.extend = extend;
6311
- debug.destroy = createDebug.destroy;
6312
- Object.defineProperty(debug, "enabled", {
6313
- enumerable: true,
6314
- configurable: false,
6315
- get: () => {
6316
- if (enableOverride !== null) {
6317
- return enableOverride;
6318
- }
6319
- if (namespacesCache !== createDebug.namespaces) {
6320
- namespacesCache = createDebug.namespaces;
6321
- enabledCache = createDebug.enabled(namespace);
6322
- }
6323
- return enabledCache;
6324
- },
6325
- set: (v) => {
6326
- enableOverride = v;
6327
- }
6328
- });
6329
- if (typeof createDebug.init === "function") {
6330
- createDebug.init(debug);
6331
- }
6332
- return debug;
6333
- }
6334
- function extend(namespace, delimiter) {
6335
- const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
6336
- newDebug.log = this.log;
6337
- return newDebug;
6338
- }
6339
- function enable(namespaces) {
6340
- createDebug.save(namespaces);
6341
- createDebug.namespaces = namespaces;
6342
- createDebug.names = [];
6343
- createDebug.skips = [];
6344
- const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
6345
- for (const ns of split) {
6346
- if (ns[0] === "-") {
6347
- createDebug.skips.push(ns.slice(1));
6348
- } else {
6349
- createDebug.names.push(ns);
6350
- }
6351
- }
6352
- }
6353
- function matchesTemplate(search, template) {
6354
- let searchIndex = 0;
6355
- let templateIndex = 0;
6356
- let starIndex = -1;
6357
- let matchIndex = 0;
6358
- while (searchIndex < search.length) {
6359
- if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
6360
- if (template[templateIndex] === "*") {
6361
- starIndex = templateIndex;
6362
- matchIndex = searchIndex;
6363
- templateIndex++;
6364
- } else {
6365
- searchIndex++;
6366
- templateIndex++;
6367
- }
6368
- } else if (starIndex !== -1) {
6369
- templateIndex = starIndex + 1;
6370
- matchIndex++;
6371
- searchIndex = matchIndex;
6372
- } else {
6373
- return false;
6374
- }
6375
- }
6376
- while (templateIndex < template.length && template[templateIndex] === "*") {
6377
- templateIndex++;
6378
- }
6379
- return templateIndex === template.length;
6380
- }
6381
- function disable() {
6382
- const namespaces = [
6383
- ...createDebug.names,
6384
- ...createDebug.skips.map((namespace) => "-" + namespace)
6385
- ].join(",");
6386
- createDebug.enable("");
6387
- return namespaces;
6388
- }
6389
- function enabled(name) {
6390
- for (const skip of createDebug.skips) {
6391
- if (matchesTemplate(name, skip)) {
6392
- return false;
6393
- }
6394
- }
6395
- for (const ns of createDebug.names) {
6396
- if (matchesTemplate(name, ns)) {
6397
- return true;
6398
- }
6399
- }
6400
- return false;
6401
- }
6402
- function coerce(val) {
6403
- if (val instanceof Error) {
6404
- return val.stack || val.message;
6405
- }
6406
- return val;
6407
- }
6408
- function destroy() {
6409
- console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
6410
- }
6411
- createDebug.enable(createDebug.load());
6412
- return createDebug;
6413
- }
6414
- module.exports = setup;
6415
- }
6416
- });
6417
-
6418
- // ../../node_modules/debug/src/browser.js
6419
- var require_browser = __commonJS({
6420
- "../../node_modules/debug/src/browser.js"(exports, module) {
6421
- exports.formatArgs = formatArgs;
6422
- exports.save = save;
6423
- exports.load = load;
6424
- exports.useColors = useColors;
6425
- exports.storage = localstorage();
6426
- exports.destroy = /* @__PURE__ */ (() => {
6427
- let warned = false;
6428
- return () => {
6429
- if (!warned) {
6430
- warned = true;
6431
- console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
6432
- }
6433
- };
6434
- })();
6435
- exports.colors = [
6436
- "#0000CC",
6437
- "#0000FF",
6438
- "#0033CC",
6439
- "#0033FF",
6440
- "#0066CC",
6441
- "#0066FF",
6442
- "#0099CC",
6443
- "#0099FF",
6444
- "#00CC00",
6445
- "#00CC33",
6446
- "#00CC66",
6447
- "#00CC99",
6448
- "#00CCCC",
6449
- "#00CCFF",
6450
- "#3300CC",
6451
- "#3300FF",
6452
- "#3333CC",
6453
- "#3333FF",
6454
- "#3366CC",
6455
- "#3366FF",
6456
- "#3399CC",
6457
- "#3399FF",
6458
- "#33CC00",
6459
- "#33CC33",
6460
- "#33CC66",
6461
- "#33CC99",
6462
- "#33CCCC",
6463
- "#33CCFF",
6464
- "#6600CC",
6465
- "#6600FF",
6466
- "#6633CC",
6467
- "#6633FF",
6468
- "#66CC00",
6469
- "#66CC33",
6470
- "#9900CC",
6471
- "#9900FF",
6472
- "#9933CC",
6473
- "#9933FF",
6474
- "#99CC00",
6475
- "#99CC33",
6476
- "#CC0000",
6477
- "#CC0033",
6478
- "#CC0066",
6479
- "#CC0099",
6480
- "#CC00CC",
6481
- "#CC00FF",
6482
- "#CC3300",
6483
- "#CC3333",
6484
- "#CC3366",
6485
- "#CC3399",
6486
- "#CC33CC",
6487
- "#CC33FF",
6488
- "#CC6600",
6489
- "#CC6633",
6490
- "#CC9900",
6491
- "#CC9933",
6492
- "#CCCC00",
6493
- "#CCCC33",
6494
- "#FF0000",
6495
- "#FF0033",
6496
- "#FF0066",
6497
- "#FF0099",
6498
- "#FF00CC",
6499
- "#FF00FF",
6500
- "#FF3300",
6501
- "#FF3333",
6502
- "#FF3366",
6503
- "#FF3399",
6504
- "#FF33CC",
6505
- "#FF33FF",
6506
- "#FF6600",
6507
- "#FF6633",
6508
- "#FF9900",
6509
- "#FF9933",
6510
- "#FFCC00",
6511
- "#FFCC33"
6512
- ];
6513
- function useColors() {
6514
- if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) {
6515
- return true;
6516
- }
6517
- if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
6518
- return false;
6519
- }
6520
- let m;
6521
- return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
6522
- typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
6523
- // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
6524
- 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
6525
- typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
6526
- }
6527
- function formatArgs(args) {
6528
- args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff);
6529
- if (!this.useColors) {
6530
- return;
6531
- }
6532
- const c = "color: " + this.color;
6533
- args.splice(1, 0, c, "color: inherit");
6534
- let index = 0;
6535
- let lastC = 0;
6536
- args[0].replace(/%[a-zA-Z%]/g, (match) => {
6537
- if (match === "%%") {
6538
- return;
6539
- }
6540
- index++;
6541
- if (match === "%c") {
6542
- lastC = index;
6543
- }
6544
- });
6545
- args.splice(lastC, 0, c);
6546
- }
6547
- exports.log = console.debug || console.log || (() => {
6548
- });
6549
- function save(namespaces) {
6550
- try {
6551
- if (namespaces) {
6552
- exports.storage.setItem("debug", namespaces);
6553
- } else {
6554
- exports.storage.removeItem("debug");
6555
- }
6556
- } catch (error) {
6557
- }
6558
- }
6559
- function load() {
6560
- let r;
6561
- try {
6562
- r = exports.storage.getItem("debug") || exports.storage.getItem("DEBUG");
6563
- } catch (error) {
6564
- }
6565
- if (!r && typeof process !== "undefined" && "env" in process) {
6566
- r = process.env.DEBUG;
6567
- }
6568
- return r;
6569
- }
6570
- function localstorage() {
6571
- try {
6572
- return localStorage;
6573
- } catch (error) {
6574
- }
6575
- }
6576
- module.exports = require_common()(exports);
6577
- var { formatters } = module.exports;
6578
- formatters.j = function(v) {
6579
- try {
6580
- return JSON.stringify(v);
6581
- } catch (error) {
6582
- return "[UnexpectedJSONParseError]: " + error.message;
6583
- }
6584
- };
6585
- }
6586
- });
6587
-
6588
- // ../../node_modules/has-flag/index.js
6589
- var require_has_flag = __commonJS({
6590
- "../../node_modules/has-flag/index.js"(exports, module) {
6591
- module.exports = (flag, argv = process.argv) => {
6592
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
6593
- const position = argv.indexOf(prefix + flag);
6594
- const terminatorPosition = argv.indexOf("--");
6595
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
6596
- };
6597
- }
6598
- });
6599
-
6600
- // ../../node_modules/supports-color/index.js
6601
- var require_supports_color = __commonJS({
6602
- "../../node_modules/supports-color/index.js"(exports, module) {
6603
- var os = __require("os");
6604
- var tty = __require("tty");
6605
- var hasFlag = require_has_flag();
6606
- var { env } = process;
6607
- var forceColor;
6608
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
6609
- forceColor = 0;
6610
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
6611
- forceColor = 1;
6612
- }
6613
- if ("FORCE_COLOR" in env) {
6614
- if (env.FORCE_COLOR === "true") {
6615
- forceColor = 1;
6616
- } else if (env.FORCE_COLOR === "false") {
6617
- forceColor = 0;
6618
- } else {
6619
- forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
6620
- }
6621
- }
6622
- function translateLevel(level) {
6623
- if (level === 0) {
6624
- return false;
6625
- }
6626
- return {
6627
- level,
6628
- hasBasic: true,
6629
- has256: level >= 2,
6630
- has16m: level >= 3
6631
- };
6632
- }
6633
- function supportsColor(haveStream, streamIsTTY) {
6634
- if (forceColor === 0) {
6635
- return 0;
6636
- }
6637
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
6638
- return 3;
6639
- }
6640
- if (hasFlag("color=256")) {
6641
- return 2;
6642
- }
6643
- if (haveStream && !streamIsTTY && forceColor === void 0) {
6644
- return 0;
6645
- }
6646
- const min = forceColor || 0;
6647
- if (env.TERM === "dumb") {
6648
- return min;
6649
- }
6650
- if (process.platform === "win32") {
6651
- const osRelease = os.release().split(".");
6652
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
6653
- return Number(osRelease[2]) >= 14931 ? 3 : 2;
6654
- }
6655
- return 1;
6656
- }
6657
- if ("CI" in env) {
6658
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
6659
- return 1;
6660
- }
6661
- return min;
6662
- }
6663
- if ("TEAMCITY_VERSION" in env) {
6664
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
6665
- }
6666
- if (env.COLORTERM === "truecolor") {
6667
- return 3;
6668
- }
6669
- if ("TERM_PROGRAM" in env) {
6670
- const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
6671
- switch (env.TERM_PROGRAM) {
6672
- case "iTerm.app":
6673
- return version >= 3 ? 3 : 2;
6674
- case "Apple_Terminal":
6675
- return 2;
6676
- }
6677
- }
6678
- if (/-256(color)?$/i.test(env.TERM)) {
6679
- return 2;
6680
- }
6681
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
6682
- return 1;
6683
- }
6684
- if ("COLORTERM" in env) {
6685
- return 1;
6686
- }
6687
- return min;
6688
- }
6689
- function getSupportLevel(stream) {
6690
- const level = supportsColor(stream, stream && stream.isTTY);
6691
- return translateLevel(level);
6692
- }
6693
- module.exports = {
6694
- supportsColor: getSupportLevel,
6695
- stdout: translateLevel(supportsColor(true, tty.isatty(1))),
6696
- stderr: translateLevel(supportsColor(true, tty.isatty(2)))
6697
- };
6698
- }
6699
- });
6700
-
6701
- // ../../node_modules/debug/src/node.js
6702
- var require_node = __commonJS({
6703
- "../../node_modules/debug/src/node.js"(exports, module) {
6704
- var tty = __require("tty");
6705
- var util = __require("util");
6706
- exports.init = init;
6707
- exports.log = log;
6708
- exports.formatArgs = formatArgs;
6709
- exports.save = save;
6710
- exports.load = load;
6711
- exports.useColors = useColors;
6712
- exports.destroy = util.deprecate(
6713
- () => {
6714
- },
6715
- "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."
6716
- );
6717
- exports.colors = [6, 2, 3, 4, 5, 1];
6718
- try {
6719
- const supportsColor = require_supports_color();
6720
- if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
6721
- exports.colors = [
6722
- 20,
6723
- 21,
6724
- 26,
6725
- 27,
6726
- 32,
6727
- 33,
6728
- 38,
6729
- 39,
6730
- 40,
6731
- 41,
6732
- 42,
6733
- 43,
6734
- 44,
6735
- 45,
6736
- 56,
6737
- 57,
6738
- 62,
6739
- 63,
6740
- 68,
6741
- 69,
6742
- 74,
6743
- 75,
6744
- 76,
6745
- 77,
6746
- 78,
6747
- 79,
6748
- 80,
6749
- 81,
6750
- 92,
6751
- 93,
6752
- 98,
6753
- 99,
6754
- 112,
6755
- 113,
6756
- 128,
6757
- 129,
6758
- 134,
6759
- 135,
6760
- 148,
6761
- 149,
6762
- 160,
6763
- 161,
6764
- 162,
6765
- 163,
6766
- 164,
6767
- 165,
6768
- 166,
6769
- 167,
6770
- 168,
6771
- 169,
6772
- 170,
6773
- 171,
6774
- 172,
6775
- 173,
6776
- 178,
6777
- 179,
6778
- 184,
6779
- 185,
6780
- 196,
6781
- 197,
6782
- 198,
6783
- 199,
6784
- 200,
6785
- 201,
6786
- 202,
6787
- 203,
6788
- 204,
6789
- 205,
6790
- 206,
6791
- 207,
6792
- 208,
6793
- 209,
6794
- 214,
6795
- 215,
6796
- 220,
6797
- 221
6798
- ];
6799
- }
6800
- } catch (error) {
6801
- }
6802
- exports.inspectOpts = Object.keys(process.env).filter((key) => {
6803
- return /^debug_/i.test(key);
6804
- }).reduce((obj, key) => {
6805
- const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_, k) => {
6806
- return k.toUpperCase();
6807
- });
6808
- let val = process.env[key];
6809
- if (/^(yes|on|true|enabled)$/i.test(val)) {
6810
- val = true;
6811
- } else if (/^(no|off|false|disabled)$/i.test(val)) {
6812
- val = false;
6813
- } else if (val === "null") {
6814
- val = null;
6815
- } else {
6816
- val = Number(val);
6817
- }
6818
- obj[prop] = val;
6819
- return obj;
6820
- }, {});
6821
- function useColors() {
6822
- return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(process.stderr.fd);
6823
- }
6824
- function formatArgs(args) {
6825
- const { namespace: name, useColors: useColors2 } = this;
6826
- if (useColors2) {
6827
- const c = this.color;
6828
- const colorCode = "\x1B[3" + (c < 8 ? c : "8;5;" + c);
6829
- const prefix = ` ${colorCode};1m${name} \x1B[0m`;
6830
- args[0] = prefix + args[0].split("\n").join("\n" + prefix);
6831
- args.push(colorCode + "m+" + module.exports.humanize(this.diff) + "\x1B[0m");
6832
- } else {
6833
- args[0] = getDate() + name + " " + args[0];
6834
- }
6835
- }
6836
- function getDate() {
6837
- if (exports.inspectOpts.hideDate) {
6838
- return "";
6839
- }
6840
- return (/* @__PURE__ */ new Date()).toISOString() + " ";
6841
- }
6842
- function log(...args) {
6843
- return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
6844
- }
6845
- function save(namespaces) {
6846
- if (namespaces) {
6847
- process.env.DEBUG = namespaces;
6848
- } else {
6849
- delete process.env.DEBUG;
6850
- }
6851
- }
6852
- function load() {
6853
- return process.env.DEBUG;
6854
- }
6855
- function init(debug) {
6856
- debug.inspectOpts = {};
6857
- const keys = Object.keys(exports.inspectOpts);
6858
- for (let i = 0; i < keys.length; i++) {
6859
- debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
6860
- }
6861
- }
6862
- module.exports = require_common()(exports);
6863
- var { formatters } = module.exports;
6864
- formatters.o = function(v) {
6865
- this.inspectOpts.colors = this.useColors;
6866
- return util.inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
6867
- };
6868
- formatters.O = function(v) {
6869
- this.inspectOpts.colors = this.useColors;
6870
- return util.inspect(v, this.inspectOpts);
6871
- };
6872
- }
6873
- });
6874
-
6875
- // ../../node_modules/debug/src/index.js
6876
- var require_src = __commonJS({
6877
- "../../node_modules/debug/src/index.js"(exports, module) {
6878
- if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
6879
- module.exports = require_browser();
6880
- } else {
6881
- module.exports = require_node();
6882
- }
6883
- }
6884
- });
6885
-
6886
- // ../../node_modules/follow-redirects/debug.js
6887
- var require_debug = __commonJS({
6888
- "../../node_modules/follow-redirects/debug.js"(exports, module) {
6889
- var debug;
6890
- module.exports = function() {
6891
- if (!debug) {
6892
- try {
6893
- debug = require_src()("follow-redirects");
6894
- } catch (error) {
6895
- }
6896
- if (typeof debug !== "function") {
6897
- debug = function() {
6898
- };
6899
- }
6900
- }
6901
- debug.apply(null, arguments);
6902
- };
6903
- }
6904
- });
6905
-
6906
- // ../../node_modules/follow-redirects/index.js
6907
- var require_follow_redirects = __commonJS({
6908
- "../../node_modules/follow-redirects/index.js"(exports, module) {
6909
- var url = __require("url");
6910
- var URL2 = url.URL;
6911
- var http = __require("http");
6912
- var https = __require("https");
6913
- var Writable = __require("stream").Writable;
6914
- var assert = __require("assert");
6915
- var debug = require_debug();
6916
- (function detectUnsupportedEnvironment() {
6917
- var looksLikeNode = typeof process !== "undefined";
6918
- var looksLikeBrowser = typeof window !== "undefined" && typeof document !== "undefined";
6919
- var looksLikeV8 = isFunction(Error.captureStackTrace);
6920
- if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) {
6921
- console.warn("The follow-redirects package should be excluded from browser builds.");
6922
- }
6923
- })();
6924
- var useNativeURL = false;
6925
- try {
6926
- assert(new URL2(""));
6927
- } catch (error) {
6928
- useNativeURL = error.code === "ERR_INVALID_URL";
6929
- }
6930
- var sensitiveHeaders = [
6931
- "Authorization",
6932
- "Proxy-Authorization",
6933
- "Cookie"
6934
- ];
6935
- var preservedUrlFields = [
6936
- "auth",
6937
- "host",
6938
- "hostname",
6939
- "href",
6940
- "path",
6941
- "pathname",
6942
- "port",
6943
- "protocol",
6944
- "query",
6945
- "search",
6946
- "hash"
6947
- ];
6948
- var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
6949
- var eventHandlers = /* @__PURE__ */ Object.create(null);
6950
- events.forEach(function(event) {
6951
- eventHandlers[event] = function(arg1, arg2, arg3) {
6952
- this._redirectable.emit(event, arg1, arg2, arg3);
6953
- };
6954
- });
6955
- var InvalidUrlError = createErrorType(
6956
- "ERR_INVALID_URL",
6957
- "Invalid URL",
6958
- TypeError
6959
- );
6960
- var RedirectionError = createErrorType(
6961
- "ERR_FR_REDIRECTION_FAILURE",
6962
- "Redirected request failed"
6963
- );
6964
- var TooManyRedirectsError = createErrorType(
6965
- "ERR_FR_TOO_MANY_REDIRECTS",
6966
- "Maximum number of redirects exceeded",
6967
- RedirectionError
6968
- );
6969
- var MaxBodyLengthExceededError = createErrorType(
6970
- "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
6971
- "Request body larger than maxBodyLength limit"
6972
- );
6973
- var WriteAfterEndError = createErrorType(
6974
- "ERR_STREAM_WRITE_AFTER_END",
6975
- "write after end"
6976
- );
6977
- var destroy = Writable.prototype.destroy || noop;
6978
- function RedirectableRequest(options, responseCallback) {
6979
- Writable.call(this);
6980
- this._sanitizeOptions(options);
6981
- this._options = options;
6982
- this._ended = false;
6983
- this._ending = false;
6984
- this._redirectCount = 0;
6985
- this._redirects = [];
6986
- this._requestBodyLength = 0;
6987
- this._requestBodyBuffers = [];
6988
- if (responseCallback) {
6989
- this.on("response", responseCallback);
6990
- }
6991
- var self = this;
6992
- this._onNativeResponse = function(response) {
6993
- try {
6994
- self._processResponse(response);
6995
- } catch (cause) {
6996
- self.emit("error", cause instanceof RedirectionError ? cause : new RedirectionError({ cause }));
6997
- }
6998
- };
6999
- this._headerFilter = new RegExp("^(?:" + sensitiveHeaders.concat(options.sensitiveHeaders).map(escapeRegex).join("|") + ")$", "i");
7000
- this._performRequest();
7001
- }
7002
- RedirectableRequest.prototype = Object.create(Writable.prototype);
7003
- RedirectableRequest.prototype.abort = function() {
7004
- destroyRequest(this._currentRequest);
7005
- this._currentRequest.abort();
7006
- this.emit("abort");
7007
- };
7008
- RedirectableRequest.prototype.destroy = function(error) {
7009
- destroyRequest(this._currentRequest, error);
7010
- destroy.call(this, error);
7011
- return this;
7012
- };
7013
- RedirectableRequest.prototype.write = function(data, encoding, callback) {
7014
- if (this._ending) {
7015
- throw new WriteAfterEndError();
7016
- }
7017
- if (!isString(data) && !isBuffer(data)) {
7018
- throw new TypeError("data should be a string, Buffer or Uint8Array");
7019
- }
7020
- if (isFunction(encoding)) {
7021
- callback = encoding;
7022
- encoding = null;
7023
- }
7024
- if (data.length === 0) {
7025
- if (callback) {
7026
- callback();
7027
- }
7028
- return;
7029
- }
7030
- if (this._requestBodyLength + data.length <= this._options.maxBodyLength) {
7031
- this._requestBodyLength += data.length;
7032
- this._requestBodyBuffers.push({ data, encoding });
7033
- this._currentRequest.write(data, encoding, callback);
7034
- } else {
7035
- this.emit("error", new MaxBodyLengthExceededError());
7036
- this.abort();
7037
- }
7038
- };
7039
- RedirectableRequest.prototype.end = function(data, encoding, callback) {
7040
- if (isFunction(data)) {
7041
- callback = data;
7042
- data = encoding = null;
7043
- } else if (isFunction(encoding)) {
7044
- callback = encoding;
7045
- encoding = null;
7046
- }
7047
- if (!data) {
7048
- this._ended = this._ending = true;
7049
- this._currentRequest.end(null, null, callback);
7050
- } else {
7051
- var self = this;
7052
- var currentRequest = this._currentRequest;
7053
- this.write(data, encoding, function() {
7054
- self._ended = true;
7055
- currentRequest.end(null, null, callback);
7056
- });
7057
- this._ending = true;
7058
- }
7059
- };
7060
- RedirectableRequest.prototype.setHeader = function(name, value) {
7061
- this._options.headers[name] = value;
7062
- this._currentRequest.setHeader(name, value);
7063
- };
7064
- RedirectableRequest.prototype.removeHeader = function(name) {
7065
- delete this._options.headers[name];
7066
- this._currentRequest.removeHeader(name);
7067
- };
7068
- RedirectableRequest.prototype.setTimeout = function(msecs, callback) {
7069
- var self = this;
7070
- function destroyOnTimeout(socket) {
7071
- socket.setTimeout(msecs);
7072
- socket.removeListener("timeout", socket.destroy);
7073
- socket.addListener("timeout", socket.destroy);
7074
- }
7075
- function startTimer(socket) {
7076
- if (self._timeout) {
7077
- clearTimeout(self._timeout);
7078
- }
7079
- self._timeout = setTimeout(function() {
7080
- self.emit("timeout");
7081
- clearTimer();
7082
- }, msecs);
7083
- destroyOnTimeout(socket);
7084
- }
7085
- function clearTimer() {
7086
- if (self._timeout) {
7087
- clearTimeout(self._timeout);
7088
- self._timeout = null;
7089
- }
7090
- self.removeListener("abort", clearTimer);
7091
- self.removeListener("error", clearTimer);
7092
- self.removeListener("response", clearTimer);
7093
- self.removeListener("close", clearTimer);
7094
- if (callback) {
7095
- self.removeListener("timeout", callback);
7096
- }
7097
- if (!self.socket) {
7098
- self._currentRequest.removeListener("socket", startTimer);
7099
- }
7100
- }
7101
- if (callback) {
7102
- this.on("timeout", callback);
7103
- }
7104
- if (this.socket) {
7105
- startTimer(this.socket);
7106
- } else {
7107
- this._currentRequest.once("socket", startTimer);
7108
- }
7109
- this.on("socket", destroyOnTimeout);
7110
- this.on("abort", clearTimer);
7111
- this.on("error", clearTimer);
7112
- this.on("response", clearTimer);
7113
- this.on("close", clearTimer);
7114
- return this;
7115
- };
7116
- [
7117
- "flushHeaders",
7118
- "getHeader",
7119
- "setNoDelay",
7120
- "setSocketKeepAlive"
7121
- ].forEach(function(method) {
7122
- RedirectableRequest.prototype[method] = function(a, b) {
7123
- return this._currentRequest[method](a, b);
7124
- };
7125
- });
7126
- ["aborted", "connection", "socket"].forEach(function(property) {
7127
- Object.defineProperty(RedirectableRequest.prototype, property, {
7128
- get: function() {
7129
- return this._currentRequest[property];
7130
- }
7131
- });
7132
- });
7133
- RedirectableRequest.prototype._sanitizeOptions = function(options) {
7134
- if (!options.headers) {
7135
- options.headers = {};
7136
- }
7137
- if (!isArray(options.sensitiveHeaders)) {
7138
- options.sensitiveHeaders = [];
7139
- }
7140
- if (options.host) {
7141
- if (!options.hostname) {
7142
- options.hostname = options.host;
7143
- }
7144
- delete options.host;
7145
- }
7146
- if (!options.pathname && options.path) {
7147
- var searchPos = options.path.indexOf("?");
7148
- if (searchPos < 0) {
7149
- options.pathname = options.path;
7150
- } else {
7151
- options.pathname = options.path.substring(0, searchPos);
7152
- options.search = options.path.substring(searchPos);
7153
- }
7154
- }
7155
- };
7156
- RedirectableRequest.prototype._performRequest = function() {
7157
- var protocol = this._options.protocol;
7158
- var nativeProtocol = this._options.nativeProtocols[protocol];
7159
- if (!nativeProtocol) {
7160
- throw new TypeError("Unsupported protocol " + protocol);
7161
- }
7162
- if (this._options.agents) {
7163
- var scheme = protocol.slice(0, -1);
7164
- this._options.agent = this._options.agents[scheme];
7165
- }
7166
- var request = this._currentRequest = nativeProtocol.request(this._options, this._onNativeResponse);
7167
- request._redirectable = this;
7168
- for (var event of events) {
7169
- request.on(event, eventHandlers[event]);
7170
- }
7171
- this._currentUrl = /^\//.test(this._options.path) ? url.format(this._options) : (
7172
- // When making a request to a proxy, […]
7173
- // a client MUST send the target URI in absolute-form […].
7174
- this._options.path
7175
- );
7176
- if (this._isRedirect) {
7177
- var i = 0;
7178
- var self = this;
7179
- var buffers = this._requestBodyBuffers;
7180
- (function writeNext(error) {
7181
- if (request === self._currentRequest) {
7182
- if (error) {
7183
- self.emit("error", error);
7184
- } else if (i < buffers.length) {
7185
- var buffer = buffers[i++];
7186
- if (!request.finished) {
7187
- request.write(buffer.data, buffer.encoding, writeNext);
7188
- }
7189
- } else if (self._ended) {
7190
- request.end();
7191
- }
7192
- }
7193
- })();
7194
- }
7195
- };
7196
- RedirectableRequest.prototype._processResponse = function(response) {
7197
- var statusCode = response.statusCode;
7198
- if (this._options.trackRedirects) {
7199
- this._redirects.push({
7200
- url: this._currentUrl,
7201
- headers: response.headers,
7202
- statusCode
7203
- });
7204
- }
7205
- var location = response.headers.location;
7206
- if (!location || this._options.followRedirects === false || statusCode < 300 || statusCode >= 400) {
7207
- response.responseUrl = this._currentUrl;
7208
- response.redirects = this._redirects;
7209
- this.emit("response", response);
7210
- this._requestBodyBuffers = [];
7211
- return;
7212
- }
7213
- destroyRequest(this._currentRequest);
7214
- response.destroy();
7215
- if (++this._redirectCount > this._options.maxRedirects) {
7216
- throw new TooManyRedirectsError();
7217
- }
7218
- var requestHeaders;
7219
- var beforeRedirect = this._options.beforeRedirect;
7220
- if (beforeRedirect) {
7221
- requestHeaders = Object.assign({
7222
- // The Host header was set by nativeProtocol.request
7223
- Host: response.req.getHeader("host")
7224
- }, this._options.headers);
7225
- }
7226
- var method = this._options.method;
7227
- if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || // RFC7231§6.4.4: The 303 (See Other) status code indicates that
7228
- // the server is redirecting the user agent to a different resource […]
7229
- // A user agent can perform a retrieval request targeting that URI
7230
- // (a GET or HEAD request if using HTTP) […]
7231
- statusCode === 303 && !/^(?:GET|HEAD)$/.test(this._options.method)) {
7232
- this._options.method = "GET";
7233
- this._requestBodyBuffers = [];
7234
- removeMatchingHeaders(/^content-/i, this._options.headers);
7235
- }
7236
- var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
7237
- var currentUrlParts = parseUrl(this._currentUrl);
7238
- var currentHost = currentHostHeader || currentUrlParts.host;
7239
- var currentUrl = /^\w+:/.test(location) ? this._currentUrl : url.format(Object.assign(currentUrlParts, { host: currentHost }));
7240
- var redirectUrl = resolveUrl(location, currentUrl);
7241
- debug("redirecting to", redirectUrl.href);
7242
- this._isRedirect = true;
7243
- spreadUrlObject(redirectUrl, this._options);
7244
- if (redirectUrl.protocol !== currentUrlParts.protocol && redirectUrl.protocol !== "https:" || redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost)) {
7245
- removeMatchingHeaders(this._headerFilter, this._options.headers);
7246
- }
7247
- if (isFunction(beforeRedirect)) {
7248
- var responseDetails = {
7249
- headers: response.headers,
7250
- statusCode
7251
- };
7252
- var requestDetails = {
7253
- url: currentUrl,
7254
- method,
7255
- headers: requestHeaders
7256
- };
7257
- beforeRedirect(this._options, responseDetails, requestDetails);
7258
- this._sanitizeOptions(this._options);
7259
- }
7260
- this._performRequest();
7261
- };
7262
- function wrap(protocols) {
7263
- var exports2 = {
7264
- maxRedirects: 21,
7265
- maxBodyLength: 10 * 1024 * 1024
7266
- };
7267
- var nativeProtocols = {};
7268
- Object.keys(protocols).forEach(function(scheme) {
7269
- var protocol = scheme + ":";
7270
- var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];
7271
- var wrappedProtocol = exports2[scheme] = Object.create(nativeProtocol);
7272
- function request(input, options, callback) {
7273
- if (isURL(input)) {
7274
- input = spreadUrlObject(input);
7275
- } else if (isString(input)) {
7276
- input = spreadUrlObject(parseUrl(input));
7277
- } else {
7278
- callback = options;
7279
- options = validateUrl(input);
7280
- input = { protocol };
7281
- }
7282
- if (isFunction(options)) {
7283
- callback = options;
7284
- options = null;
7285
- }
7286
- options = Object.assign({
7287
- maxRedirects: exports2.maxRedirects,
7288
- maxBodyLength: exports2.maxBodyLength
7289
- }, input, options);
7290
- options.nativeProtocols = nativeProtocols;
7291
- if (!isString(options.host) && !isString(options.hostname)) {
7292
- options.hostname = "::1";
7293
- }
7294
- assert.equal(options.protocol, protocol, "protocol mismatch");
7295
- debug("options", options);
7296
- return new RedirectableRequest(options, callback);
7297
- }
7298
- function get(input, options, callback) {
7299
- var wrappedRequest = wrappedProtocol.request(input, options, callback);
7300
- wrappedRequest.end();
7301
- return wrappedRequest;
7302
- }
7303
- Object.defineProperties(wrappedProtocol, {
7304
- request: { value: request, configurable: true, enumerable: true, writable: true },
7305
- get: { value: get, configurable: true, enumerable: true, writable: true }
7306
- });
7307
- });
7308
- return exports2;
7309
- }
7310
- function noop() {
7311
- }
7312
- function parseUrl(input) {
7313
- var parsed;
7314
- if (useNativeURL) {
7315
- parsed = new URL2(input);
7316
- } else {
7317
- parsed = validateUrl(url.parse(input));
7318
- if (!isString(parsed.protocol)) {
7319
- throw new InvalidUrlError({ input });
7320
- }
7321
- }
7322
- return parsed;
7323
- }
7324
- function resolveUrl(relative, base) {
7325
- return useNativeURL ? new URL2(relative, base) : parseUrl(url.resolve(base, relative));
7326
- }
7327
- function validateUrl(input) {
7328
- if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
7329
- throw new InvalidUrlError({ input: input.href || input });
7330
- }
7331
- if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
7332
- throw new InvalidUrlError({ input: input.href || input });
7333
- }
7334
- return input;
6293
+ let totalOut = 0;
6294
+ let cachedTokensEstimate = 0;
6295
+ for (const r of results) {
6296
+ allIssues.push(...r.issues);
6297
+ if (r.pass) {
6298
+ passCategories.push(r.criterion);
7335
6299
  }
7336
- function spreadUrlObject(urlObject, target) {
7337
- var spread = target || {};
7338
- for (var key of preservedUrlFields) {
7339
- spread[key] = urlObject[key];
7340
- }
7341
- if (spread.hostname.startsWith("[")) {
7342
- spread.hostname = spread.hostname.slice(1, -1);
6300
+ totalIn += r.tokens.in;
6301
+ totalOut += r.tokens.out;
6302
+ }
6303
+ const firstResult = results[0];
6304
+ if (firstResult && firstResult.tokens.in > 0) {
6305
+ cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
6306
+ }
6307
+ const wallMs = Date.now() - evalStart;
6308
+ for (const r of results) {
6309
+ const issueCount = r.issues.length;
6310
+ const status = r.pass ? "PASS" : r.tier === 1 ? "FAIL" : r.issues.some((i) => i.result === "fail") ? "FAIL" : "WARN";
6311
+ const issueLabel = issueCount > 0 ? ` (${issueCount} issue${issueCount > 1 ? "s" : ""})` : "";
6312
+ console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
6313
+ }
6314
+ console.log(
6315
+ `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cached~=${cachedTokensEstimate} | issues=${allIssues.length} pass=${passCategories.length}`
6316
+ );
6317
+ return {
6318
+ issues: allIssues,
6319
+ pass: passCategories,
6320
+ inputTokens: totalIn,
6321
+ outputTokens: totalOut
6322
+ };
6323
+ }
6324
+ var TIER_1_CRITERIA, TIER_2_CRITERIA, TIER_1_CATEGORIES, TIER_2_CATEGORIES, VALID_CATEGORIES;
6325
+ var init_llm_evaluator = __esm({
6326
+ "src/evaluation/llm-evaluator.ts"() {
6327
+ init_llm_router();
6328
+ init_types_public();
6329
+ TIER_1_CRITERIA = ["functionality", "crash"];
6330
+ TIER_2_CRITERIA = ["interactivity", "accessibility", "layout", "loading", "visual"];
6331
+ TIER_1_CATEGORIES = /* @__PURE__ */ new Set(["functionality", "crash"]);
6332
+ TIER_2_CATEGORIES = /* @__PURE__ */ new Set([
6333
+ "interactivity",
6334
+ "accessibility",
6335
+ "layout",
6336
+ "loading",
6337
+ "visual"
6338
+ ]);
6339
+ VALID_CATEGORIES = /* @__PURE__ */ new Set([
6340
+ ...TIER_1_CATEGORIES,
6341
+ ...TIER_2_CATEGORIES
6342
+ ]);
6343
+ }
6344
+ });
6345
+
6346
+ // src/evaluation/cost-tracker.ts
6347
+ var cost_tracker_exports = {};
6348
+ __export(cost_tracker_exports, {
6349
+ CostTracker: () => CostTracker,
6350
+ pricePer1k: () => pricePer1k
6351
+ });
6352
+ function resolveRegistryId(model) {
6353
+ const keys = Object.keys(MODEL_REGISTRY);
6354
+ if (keys.includes(model)) return model;
6355
+ const bySuffix = keys.find((id) => id.endsWith(`/${model}`));
6356
+ if (bySuffix) return bySuffix;
6357
+ const undated = model.replace(/-\d{8}$/, "");
6358
+ return keys.find((id) => id.endsWith(`/${undated}`)) ?? null;
6359
+ }
6360
+ function pricePer1k(model) {
6361
+ const id = resolveRegistryId(model);
6362
+ if (id === null) return FALLBACK_PER_1K;
6363
+ const { costs } = MODEL_REGISTRY[id];
6364
+ return { input: costs.inputPer1M / 1e3, output: costs.outputPer1M / 1e3 };
6365
+ }
6366
+ var FALLBACK_PER_1K, CostTracker;
6367
+ var init_cost_tracker = __esm({
6368
+ "src/evaluation/cost-tracker.ts"() {
6369
+ FALLBACK_PER_1K = { input: 3e-3, output: 0.015 };
6370
+ CostTracker = class {
6371
+ constructor(maxBudget) {
6372
+ this.maxBudget = maxBudget;
7343
6373
  }
7344
- if (spread.port !== "") {
7345
- spread.port = Number(spread.port);
6374
+ maxBudget;
6375
+ totalCost = 0;
6376
+ record(model, inputTokens, outputTokens) {
6377
+ const prices = pricePer1k(model);
6378
+ const cost = inputTokens / 1e3 * prices.input + outputTokens / 1e3 * prices.output;
6379
+ this.totalCost += cost;
6380
+ return cost;
7346
6381
  }
7347
- spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
7348
- return spread;
7349
- }
7350
- function removeMatchingHeaders(regex, headers) {
7351
- var lastValue;
7352
- for (var header in headers) {
7353
- if (regex.test(header)) {
7354
- lastValue = headers[header];
7355
- delete headers[header];
7356
- }
6382
+ canContinue() {
6383
+ if (this.maxBudget === null) return true;
6384
+ return this.totalCost < this.maxBudget;
7357
6385
  }
7358
- return lastValue === null || typeof lastValue === "undefined" ? void 0 : String(lastValue).trim();
7359
- }
7360
- function createErrorType(code, message, baseClass) {
7361
- function CustomError(properties) {
7362
- if (isFunction(Error.captureStackTrace)) {
7363
- Error.captureStackTrace(this, this.constructor);
7364
- }
7365
- Object.assign(this, properties || {});
7366
- this.code = code;
7367
- this.message = this.cause ? message + ": " + this.cause.message : message;
6386
+ getTotal() {
6387
+ return this.totalCost;
7368
6388
  }
7369
- CustomError.prototype = new (baseClass || Error)();
7370
- Object.defineProperties(CustomError.prototype, {
7371
- constructor: {
7372
- value: CustomError,
7373
- enumerable: false
7374
- },
7375
- name: {
7376
- value: "Error [" + code + "]",
7377
- enumerable: false
7378
- }
7379
- });
7380
- return CustomError;
7381
- }
7382
- function destroyRequest(request, error) {
7383
- for (var event of events) {
7384
- request.removeListener(event, eventHandlers[event]);
6389
+ getRemaining() {
6390
+ if (this.maxBudget === null) return null;
6391
+ return Math.max(0, this.maxBudget - this.totalCost);
7385
6392
  }
7386
- request.on("error", noop);
7387
- request.destroy(error);
7388
- }
7389
- function isSubdomain(subdomain, domain) {
7390
- assert(isString(subdomain) && isString(domain));
7391
- var dot = subdomain.length - domain.length - 1;
7392
- return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
7393
- }
7394
- function isArray(value) {
7395
- return value instanceof Array;
7396
- }
7397
- function isString(value) {
7398
- return typeof value === "string" || value instanceof String;
7399
- }
7400
- function isFunction(value) {
7401
- return typeof value === "function";
7402
- }
7403
- function isBuffer(value) {
7404
- return typeof value === "object" && "length" in value;
7405
- }
7406
- function isURL(value) {
7407
- return URL2 && value instanceof URL2;
7408
- }
7409
- function escapeRegex(regex) {
7410
- return regex.replace(/[\]\\/()*+?.$]/g, "\\$&");
7411
- }
7412
- module.exports = wrap({ http, https });
7413
- module.exports.wrap = wrap;
6393
+ };
7414
6394
  }
7415
6395
  });
7416
6396
 
@@ -8539,7 +7519,7 @@ var require_streamx = __commonJS({
8539
7519
  }
8540
7520
  }
8541
7521
  };
8542
- var Readable = class _Readable extends Stream {
7522
+ var Readable2 = class _Readable extends Stream {
8543
7523
  constructor(opts) {
8544
7524
  super(opts);
8545
7525
  this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD;
@@ -8556,7 +7536,7 @@ var require_streamx = __commonJS({
8556
7536
  fn().then((src) => {
8557
7537
  if (src === null) return out.end();
8558
7538
  if (out.destroying) return;
8559
- pipeline(src, out, noop);
7539
+ pipeline2(src, out, noop);
8560
7540
  }).catch((err) => out.destroy(err));
8561
7541
  return out;
8562
7542
  }
@@ -8750,7 +7730,7 @@ var require_streamx = __commonJS({
8750
7730
  return this;
8751
7731
  }
8752
7732
  };
8753
- var Duplex = class extends Readable {
7733
+ var Duplex = class extends Readable2 {
8754
7734
  // and Writable
8755
7735
  constructor(opts) {
8756
7736
  super(opts);
@@ -8843,13 +7823,13 @@ var require_streamx = __commonJS({
8843
7823
  }
8844
7824
  function pipelinePromise(...streams) {
8845
7825
  return new Promise((resolve3, reject) => {
8846
- return pipeline(...streams, (err) => {
7826
+ return pipeline2(...streams, (err) => {
8847
7827
  if (err) return reject(err);
8848
7828
  resolve3();
8849
7829
  });
8850
7830
  });
8851
7831
  }
8852
- function pipeline(stream, ...streams) {
7832
+ function pipeline2(stream, ...streams) {
8853
7833
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams];
8854
7834
  const done = all.length && typeof all[all.length - 1] === "function" ? all.pop() : null;
8855
7835
  if (all.length < 2) throw StreamError.BAD_ARGUMENT("Pipeline requires at least 2 streams");
@@ -8947,7 +7927,7 @@ var require_streamx = __commonJS({
8947
7927
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev;
8948
7928
  }
8949
7929
  module.exports = {
8950
- pipeline,
7930
+ pipeline: pipeline2,
8951
7931
  pipelinePromise,
8952
7932
  isStream,
8953
7933
  isStreamx,
@@ -8959,7 +7939,7 @@ var require_streamx = __commonJS({
8959
7939
  getStreamError,
8960
7940
  Stream,
8961
7941
  Writable,
8962
- Readable,
7942
+ Readable: Readable2,
8963
7943
  Duplex,
8964
7944
  Transform,
8965
7945
  // Export PassThrough for compatibility with Node.js core's stream module
@@ -8968,9 +7948,9 @@ var require_streamx = __commonJS({
8968
7948
  }
8969
7949
  });
8970
7950
 
8971
- // ../../node_modules/tar-fs/node_modules/tar-stream/headers.js
7951
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/headers.js
8972
7952
  var require_headers = __commonJS({
8973
- "../../node_modules/tar-fs/node_modules/tar-stream/headers.js"(exports) {
7953
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/headers.js"(exports) {
8974
7954
  var b4a = require_b4a();
8975
7955
  var ZEROS = "0000000000000000000";
8976
7956
  var SEVENS = "7777777777777777777";
@@ -9228,10 +8208,10 @@ var require_headers = __commonJS({
9228
8208
  }
9229
8209
  });
9230
8210
 
9231
- // ../../node_modules/tar-fs/node_modules/tar-stream/extract.js
8211
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/extract.js
9232
8212
  var require_extract = __commonJS({
9233
- "../../node_modules/tar-fs/node_modules/tar-stream/extract.js"(exports, module) {
9234
- var { Writable, Readable, getStreamError } = require_streamx();
8213
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/extract.js"(exports, module) {
8214
+ var { Writable, Readable: Readable2, getStreamError } = require_streamx();
9235
8215
  var FIFO = require_fast_fifo();
9236
8216
  var b4a = require_b4a();
9237
8217
  var headers = require_headers();
@@ -9278,7 +8258,7 @@ var require_extract = __commonJS({
9278
8258
  return buf.subarray(this._offset, this._offset += size);
9279
8259
  }
9280
8260
  };
9281
- var Source = class extends Readable {
8261
+ var Source = class extends Readable2 {
9282
8262
  constructor(self, header, offset) {
9283
8263
  super();
9284
8264
  this.header = header;
@@ -9562,9 +8542,9 @@ var require_extract = __commonJS({
9562
8542
  }
9563
8543
  });
9564
8544
 
9565
- // ../../node_modules/tar-fs/node_modules/tar-stream/constants.js
8545
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/constants.js
9566
8546
  var require_constants = __commonJS({
9567
- "../../node_modules/tar-fs/node_modules/tar-stream/constants.js"(exports, module) {
8547
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/constants.js"(exports, module) {
9568
8548
  var constants = {
9569
8549
  // just for envs without fs
9570
8550
  S_IFMT: 61440,
@@ -9582,10 +8562,10 @@ var require_constants = __commonJS({
9582
8562
  }
9583
8563
  });
9584
8564
 
9585
- // ../../node_modules/tar-fs/node_modules/tar-stream/pack.js
8565
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/pack.js
9586
8566
  var require_pack = __commonJS({
9587
- "../../node_modules/tar-fs/node_modules/tar-stream/pack.js"(exports, module) {
9588
- var { Readable, Writable, getStreamError } = require_streamx();
8567
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/pack.js"(exports, module) {
8568
+ var { Readable: Readable2, Writable, getStreamError } = require_streamx();
9589
8569
  var b4a = require_b4a();
9590
8570
  var constants = require_constants();
9591
8571
  var headers = require_headers();
@@ -9678,7 +8658,7 @@ var require_pack = __commonJS({
9678
8658
  cb();
9679
8659
  }
9680
8660
  };
9681
- var Pack = class extends Readable {
8661
+ var Pack = class extends Readable2 {
9682
8662
  constructor(opts) {
9683
8663
  super(opts);
9684
8664
  this._drain = noop;
@@ -9816,9 +8796,9 @@ var require_pack = __commonJS({
9816
8796
  }
9817
8797
  });
9818
8798
 
9819
- // ../../node_modules/tar-fs/node_modules/tar-stream/index.js
8799
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-stream/index.js
9820
8800
  var require_tar_stream = __commonJS({
9821
- "../../node_modules/tar-fs/node_modules/tar-stream/index.js"(exports) {
8801
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-stream/index.js"(exports) {
9822
8802
  exports.extract = require_extract();
9823
8803
  exports.pack = require_pack();
9824
8804
  }
@@ -10056,9 +9036,9 @@ var require_pump = __commonJS({
10056
9036
  }
10057
9037
  });
10058
9038
 
10059
- // ../../node_modules/tar-fs/index.js
9039
+ // ../../node_modules/@sparticuz/chromium/node_modules/tar-fs/index.js
10060
9040
  var require_tar_fs = __commonJS({
10061
- "../../node_modules/tar-fs/index.js"(exports) {
9041
+ "../../node_modules/@sparticuz/chromium/node_modules/tar-fs/index.js"(exports) {
10062
9042
  var tar = require_tar_stream();
10063
9043
  var pump = require_pump();
10064
9044
  var fs2 = __require("fs");
@@ -10384,10 +9364,9 @@ var require_tar_fs = __commonJS({
10384
9364
  }
10385
9365
  }
10386
9366
  });
10387
- var import_follow_redirects, import_tar_fs, setupLambdaEnvironment, isValidUrl, isRunningInAmazonLinux2023, downloadAndExtract;
9367
+ var import_tar_fs, setupLambdaEnvironment, isValidUrl, isRunningInAmazonLinux2023, downloadAndExtract;
10388
9368
  var init_helper = __esm({
10389
- "../../node_modules/@sparticuz/chromium/build/esm/helper.js"() {
10390
- import_follow_redirects = __toESM(require_follow_redirects());
9369
+ "../../node_modules/@sparticuz/chromium/build/helper.js"() {
10391
9370
  import_tar_fs = __toESM(require_tar_fs());
10392
9371
  setupLambdaEnvironment = (baseLibPath) => {
10393
9372
  process.env["FONTCONFIG_PATH"] ??= join(tmpdir(), "fonts");
@@ -10403,7 +9382,13 @@ var init_helper = __esm({
10403
9382
  };
10404
9383
  isValidUrl = (input) => {
10405
9384
  try {
10406
- return Boolean(new URL(input));
9385
+ const url = new URL(input);
9386
+ if (url.protocol === "https:")
9387
+ return true;
9388
+ if (url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]")) {
9389
+ return true;
9390
+ }
9391
+ return false;
10407
9392
  } catch {
10408
9393
  return false;
10409
9394
  }
@@ -10421,43 +9406,41 @@ var init_helper = __esm({
10421
9406
  return false;
10422
9407
  };
10423
9408
  downloadAndExtract = async (url) => {
10424
- const getOptions = new URL(url);
10425
- getOptions.maxBodyLength = 60 * 1024 * 1024;
10426
9409
  const destDir = join(tmpdir(), "chromium-pack");
10427
- return new Promise((resolve3, reject) => {
10428
- const extractObj = (0, import_tar_fs.extract)(destDir);
10429
- const cleanupOnError = (err) => {
9410
+ const response = await fetch(url, {
9411
+ redirect: "follow",
9412
+ signal: AbortSignal.timeout(3e5)
9413
+ });
9414
+ if (!response.ok) {
9415
+ throw new Error(`Unexpected status code: ${String(response.status)}.`);
9416
+ }
9417
+ if (!response.body) {
9418
+ throw new Error("Response body is empty.");
9419
+ }
9420
+ try {
9421
+ await pipeline(Readable.fromWeb(response.body), (0, import_tar_fs.extract)(destDir));
9422
+ } catch (error) {
9423
+ await new Promise((resolve3) => {
10430
9424
  rm(destDir, { force: true, recursive: true }, () => {
10431
- reject(err);
9425
+ resolve3();
10432
9426
  });
10433
- };
10434
- extractObj.once("error", cleanupOnError);
10435
- extractObj.once("finish", () => {
10436
- resolve3(destDir);
10437
- });
10438
- const req = import_follow_redirects.default.https.get(url, (response) => {
10439
- if (response.statusCode !== 200) {
10440
- reject(new Error(`Unexpected status code: ${response.statusCode?.toFixed(0) ?? "UNK"}.`));
10441
- return;
10442
- }
10443
- response.pipe(extractObj);
10444
- response.once("error", cleanupOnError);
10445
9427
  });
10446
- req.once("error", cleanupOnError);
10447
- req.setTimeout(60 * 1e3, () => {
10448
- req.destroy();
10449
- cleanupOnError(new Error("Request timeout"));
10450
- });
10451
- });
9428
+ throw error;
9429
+ }
9430
+ return destDir;
10452
9431
  };
10453
9432
  }
10454
9433
  });
10455
- var import_tar_fs2, inflate;
9434
+ var import_tar_fs2, ARCHIVE_EXTENSION_REGEX, TAR_EXTENSION_REGEX, BROTLI_EXTENSION_REGEX, GZIP_EXTENSION_REGEX, inflate;
10456
9435
  var init_lambdafs = __esm({
10457
- "../../node_modules/@sparticuz/chromium/build/esm/lambdafs.js"() {
9436
+ "../../node_modules/@sparticuz/chromium/build/lambdafs.js"() {
10458
9437
  import_tar_fs2 = __toESM(require_tar_fs());
9438
+ ARCHIVE_EXTENSION_REGEX = /\.(?:t(?:ar(?:\.(?:br|gz))?|br|gz)|br|gz)$/i;
9439
+ TAR_EXTENSION_REGEX = /\.t(?:ar(?:\.(?:br|gz))?|br|gz)$/i;
9440
+ BROTLI_EXTENSION_REGEX = /br$/i;
9441
+ GZIP_EXTENSION_REGEX = /gz$/i;
10459
9442
  inflate = (filePath) => {
10460
- const output = filePath.includes("swiftshader") ? tmpdir() : join(tmpdir(), basename(filePath).replace(/\.(?:t(?:ar(?:\.(?:br|gz))?|br|gz)|br|gz)$/i, ""));
9443
+ const output = filePath.includes("swiftshader") ? tmpdir() : join(tmpdir(), basename(filePath).replace(ARCHIVE_EXTENSION_REGEX, ""));
10461
9444
  return new Promise((resolve3, reject) => {
10462
9445
  if (filePath.includes("swiftshader")) {
10463
9446
  if (existsSync(`${output}/libGLESv2.so`)) {
@@ -10468,9 +9451,9 @@ var init_lambdafs = __esm({
10468
9451
  resolve3(output);
10469
9452
  return;
10470
9453
  }
10471
- const isBrotli = /br$/i.test(filePath);
10472
- const isGzip = /gz$/i.test(filePath);
10473
- const isTar = /\.t(?:ar(?:\.(?:br|gz))?|br|gz)$/i.test(filePath);
9454
+ const isBrotli = BROTLI_EXTENSION_REGEX.test(filePath);
9455
+ const isGzip = GZIP_EXTENSION_REGEX.test(filePath);
9456
+ const isTar = TAR_EXTENSION_REGEX.test(filePath);
10474
9457
  const highWaterMark = 2 ** 22;
10475
9458
  const source = createReadStream(filePath, { highWaterMark });
10476
9459
  let target;
@@ -10502,24 +9485,28 @@ var init_lambdafs = __esm({
10502
9485
  }
10503
9486
  });
10504
9487
  function getBinPath() {
10505
- return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "bin");
9488
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "bin");
10506
9489
  }
10507
- var init_paths_esm = __esm({
10508
- "../../node_modules/@sparticuz/chromium/build/esm/paths.esm.js"() {
9490
+ var init_paths = __esm({
9491
+ "../../node_modules/@sparticuz/chromium/build/paths.js"() {
10509
9492
  }
10510
9493
  });
10511
9494
 
10512
- // ../../node_modules/@sparticuz/chromium/build/esm/index.js
10513
- var esm_exports = {};
10514
- __export(esm_exports, {
10515
- default: () => esm_default
9495
+ // ../../node_modules/@sparticuz/chromium/build/index.js
9496
+ var build_exports = {};
9497
+ __export(build_exports, {
9498
+ default: () => build_default,
9499
+ inflate: () => inflate,
9500
+ setupLambdaEnvironment: () => setupLambdaEnvironment
10516
9501
  });
10517
- var nodeMajorVersion, Chromium, esm_default;
10518
- var init_esm = __esm({
10519
- "../../node_modules/@sparticuz/chromium/build/esm/index.js"() {
9502
+ var nodeMajorVersion, Chromium, build_default;
9503
+ var init_build = __esm({
9504
+ "../../node_modules/@sparticuz/chromium/build/index.js"() {
9505
+ init_helper();
9506
+ init_lambdafs();
9507
+ init_paths();
10520
9508
  init_helper();
10521
9509
  init_lambdafs();
10522
- init_paths_esm();
10523
9510
  nodeMajorVersion = Number.parseInt(process.versions.node.split(".")[0] ?? "");
10524
9511
  if (isRunningInAmazonLinux2023(nodeMajorVersion)) {
10525
9512
  setupLambdaEnvironment(join(tmpdir(), "al2023", "lib"));
@@ -10638,7 +9625,7 @@ var init_esm = __esm({
10638
9625
  }
10639
9626
  input ??= getBinPath();
10640
9627
  if (!existsSync(input)) {
10641
- throw new Error(`The input directory "${input}" does not exist. Please provide the location of the brotli files.`);
9628
+ 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`);
10642
9629
  }
10643
9630
  const promises = [
10644
9631
  inflate(join(input, "chromium.br")),
@@ -10652,7 +9639,7 @@ var init_esm = __esm({
10652
9639
  return result.shift();
10653
9640
  }
10654
9641
  };
10655
- esm_default = Chromium;
9642
+ build_default = Chromium;
10656
9643
  }
10657
9644
  });
10658
9645
 
@@ -10661,6 +9648,7 @@ var visual_evaluator_exports = {};
10661
9648
  __export(visual_evaluator_exports, {
10662
9649
  buildRenderHTML: () => buildRenderHTML,
10663
9650
  bundleForRendering: () => bundleForRendering,
9651
+ callMultimodalLLM: () => callMultimodalLLM,
10664
9652
  runVisualEval: () => runVisualEval,
10665
9653
  runVisualEvaluation: () => runVisualEvaluation
10666
9654
  });
@@ -10759,7 +9747,7 @@ ${bundledCode}
10759
9747
  async function captureScreenshot(html, viewport = { width: 1280, height: 800 }) {
10760
9748
  try {
10761
9749
  const puppeteer = await import('puppeteer-core');
10762
- const chromium = await Promise.resolve().then(() => (init_esm(), esm_exports));
9750
+ const chromium = await Promise.resolve().then(() => (init_build(), build_exports));
10763
9751
  const launchOptions = {
10764
9752
  args: chromium.default.args,
10765
9753
  defaultViewport: viewport,
@@ -10769,7 +9757,9 @@ async function captureScreenshot(html, viewport = { width: 1280, height: 800 })
10769
9757
  const browser = await puppeteer.default.launch(launchOptions);
10770
9758
  try {
10771
9759
  const page = await browser.newPage();
10772
- await page.setContent(html, { waitUntil: "networkidle0", timeout: 15e3 });
9760
+ await page.setContent(html, { waitUntil: "load", timeout: 15e3 });
9761
+ await page.waitForNetworkIdle({ idleTime: 500, timeout: 15e3 }).catch(() => {
9762
+ });
10773
9763
  await page.waitForSelector("#root > *", { timeout: 1e4 }).catch(() => {
10774
9764
  });
10775
9765
  await new Promise((r) => setTimeout(r, 1e3));
@@ -10783,63 +9773,23 @@ async function captureScreenshot(html, viewport = { width: 1280, height: 800 })
10783
9773
  return null;
10784
9774
  }
10785
9775
  }
10786
- async function callMultimodalLLM(provider, model, prompt, screenshot, originalPrompt) {
9776
+ async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt) {
10787
9777
  const userPrompt = `## Original Request
10788
9778
  ${originalPrompt}
10789
9779
 
10790
9780
  Evaluate the screenshot of the generated component.`;
10791
- if (provider === "claude") {
10792
- const { default: Anthropic2 } = await import('@anthropic-ai/sdk');
10793
- const client2 = new Anthropic2({
10794
- apiKey: process.env.ANTHROPIC_API_KEY,
10795
- baseURL: "https://api.anthropic.com"
10796
- });
10797
- const response2 = await client2.messages.create({
10798
- model: model.startsWith("anthropic/") ? model.slice(10) : model,
10799
- max_tokens: 1500,
10800
- system: prompt,
10801
- messages: [{
10802
- role: "user",
10803
- content: [
10804
- {
10805
- type: "image",
10806
- source: {
10807
- type: "base64",
10808
- media_type: "image/png",
10809
- data: screenshot.toString("base64")
10810
- }
10811
- },
10812
- { type: "text", text: userPrompt }
10813
- ]
10814
- }]
10815
- });
10816
- return {
10817
- text: response2.content.filter((b) => b.type === "text").map((b) => b.text).join(""),
10818
- inputTokens: response2.usage.input_tokens,
10819
- outputTokens: response2.usage.output_tokens
10820
- };
10821
- }
10822
- const { GoogleGenAI } = await import('@google/genai');
10823
- const client = new GoogleGenAI({
10824
- apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
10825
- });
10826
- const response = await client.models.generateContent({
10827
- model: model.startsWith("google/") ? model.slice(7) : model,
10828
- contents: [{
10829
- role: "user",
10830
- parts: [
10831
- { inlineData: { mimeType: "image/png", data: screenshot.toString("base64") } },
10832
- { text: userPrompt }
10833
- ]
10834
- }],
10835
- config: { systemInstruction: prompt }
9781
+ const agent = createVisionAgent({
9782
+ provider: config.provider === "claude" ? "anthropic" : config.provider,
9783
+ routeOverride: config.routeOverride,
9784
+ onRetry: config.onRetry
10836
9785
  });
10837
- const usage = response.usageMetadata;
10838
- return {
10839
- text: response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "",
10840
- inputTokens: usage?.promptTokenCount ?? 0,
10841
- outputTokens: usage?.candidatesTokenCount ?? 0
10842
- };
9786
+ return agent.callVision(
9787
+ model,
9788
+ prompt,
9789
+ userPrompt,
9790
+ { mediaType: "image/png", base64: screenshot.toString("base64") },
9791
+ 1500
9792
+ );
10843
9793
  }
10844
9794
  async function runVisualEvaluation(context, config) {
10845
9795
  const startTime = Date.now();
@@ -10857,7 +9807,7 @@ async function runVisualEvaluation(context, config) {
10857
9807
  console.log(`[visual-eval] screenshot: ${screenshot.length}B (${Date.now() - startTime}ms)`);
10858
9808
  const model = config.model ?? getDefaultVisualModel(config.provider);
10859
9809
  const response = await callMultimodalLLM(
10860
- config.provider,
9810
+ config,
10861
9811
  model,
10862
9812
  VISUAL_EVAL_PROMPT,
10863
9813
  screenshot,
@@ -10931,6 +9881,7 @@ async function runVisualEval(context, config) {
10931
9881
  var VISUAL_EVAL_PROMPT;
10932
9882
  var init_visual_evaluator = __esm({
10933
9883
  "src/evaluation/visual-evaluator.ts"() {
9884
+ init_llm_router();
10934
9885
  VISUAL_EVAL_PROMPT = `You are a visual quality evaluator for ggui \u2014 a platform where AI agents generate React UI components on demand.
10935
9886
 
10936
9887
  ## Context
@@ -11393,10 +10344,12 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
11393
10344
  - Borders/dividers \u2192 \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
11394
10345
 
11395
10346
  ### State Colors
11396
- - var(--ggui-color-success) \u2014 success states
11397
- - var(--ggui-color-warning) \u2014 warning states
11398
- - var(--ggui-color-error) \u2014 error states, destructive actions
11399
- - var(--ggui-color-info) \u2014 informational
10347
+ Semantic families ship as SCALES; the \`500\` stop is the role color (per-mode brightness), \`50\` the tint, \`800\` the on-tint text:
10348
+ - var(--ggui-color-success-500) \u2014 success states (tint bg: success-50, text on tint: success-800)
10349
+ - var(--ggui-color-warning-500) \u2014 warning states (warning-50 / warning-800)
10350
+ - var(--ggui-color-error-500) \u2014 error states, destructive actions (error-50 / error-800)
10351
+ - var(--ggui-color-info-500) \u2014 informational (info-50 / info-800)
10352
+ Prefer the primitives' \`tone="success|warning|error|info"\` prop over hand-written vars \u2014 it resolves to the same stops.
11400
10353
 
11401
10354
  ### IMPORTANT: Color Rules
11402
10355
  - **NEVER** use hardcoded hex colors. ONLY use var(--ggui-color-*) tokens.
@@ -11696,16 +10649,19 @@ function generateSampleProps(spec) {
11696
10649
  return props;
11697
10650
  }
11698
10651
  function synthesizeFromSchema(schema) {
11699
- switch (schema.type) {
10652
+ const declared = Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : schema.type;
10653
+ switch (declared) {
11700
10654
  case "string":
11701
10655
  return "sample";
11702
10656
  case "number":
10657
+ case "integer":
11703
10658
  return 0;
11704
10659
  case "boolean":
11705
10660
  return false;
10661
+ case "null":
10662
+ return null;
11706
10663
  case "array": {
11707
- const itemSchema = schema.items;
11708
- if (itemSchema) return [synthesizeFromSchema(itemSchema) ?? null];
10664
+ if (schema.items) return [synthesizeFromSchema(schema.items) ?? null];
11709
10665
  return [];
11710
10666
  }
11711
10667
  case "object": {
@@ -13171,11 +12127,11 @@ var stateFragments = {
13171
12127
  axis: "state",
13172
12128
  value: "merge",
13173
12129
  cacheTier: "axisDelta",
13174
- 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.",
12130
+ 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.",
13175
12131
  boilerplateMarker: [
13176
12132
  "",
13177
12133
  " // \u2500\u2500 Live entity state (merge-by-id) \u2500\u2500",
13178
- " // useState(props.items ?? []); merge stream events by item.id; never append.",
12134
+ " // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.",
13179
12135
  ""
13180
12136
  ].join("\n")
13181
12137
  },
@@ -13293,13 +12249,13 @@ var realtimeFragments = {
13293
12249
  axis: "realtime",
13294
12250
  value: "merge",
13295
12251
  cacheTier: "axisDelta",
13296
- 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."
12252
+ 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."
13297
12253
  },
13298
12254
  append: {
13299
12255
  axis: "realtime",
13300
12256
  value: "append",
13301
12257
  cacheTier: "axisDelta",
13302
- 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."
12258
+ 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."
13303
12259
  },
13304
12260
  status: {
13305
12261
  axis: "realtime",
@@ -13317,7 +12273,7 @@ var realtimeFragments = {
13317
12273
  axis: "realtime",
13318
12274
  value: "mixed",
13319
12275
  cacheTier: "axisDelta",
13320
- 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.",
12276
+ 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.",
13321
12277
  boilerplateMarker: [
13322
12278
  "",
13323
12279
  " // \u2500\u2500 Mixed stream handlers \u2500\u2500",
@@ -13628,13 +12584,14 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
13628
12584
  const propsFields = [];
13629
12585
  const propsProperties = contract?.propsSpec?.properties ?? {};
13630
12586
  for (const [key, entry] of Object.entries(propsProperties)) {
13631
- const required = entry.required !== false;
12587
+ const required = entry.required === true;
13632
12588
  const nullable = entry.schema.nullable === true;
13633
12589
  const tsType = jsonSchemaTypeToTs(entry.schema);
13634
12590
  const fullType = nullable ? `${tsType} | null` : tsType;
13635
12591
  const parts = [];
13636
12592
  if (entry.description) parts.push(entry.description);
13637
12593
  if (entry.default !== void 0) parts.push(`(default: ${JSON.stringify(entry.default)})`);
12594
+ if (!required) parts.push("(optional \u2014 the agent may omit it; guard or fall back)");
13638
12595
  const desc = parts.length > 0 ? ` // ${parts.join(" ")}` : "";
13639
12596
  propsFields.push(` ${key}${required ? "" : "?"}: ${fullType};${desc}`);
13640
12597
  }
@@ -14191,13 +13148,15 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
14191
13148
 
14192
13149
  ## Defensive coding for absent / late-arriving data
14193
13150
 
14194
- 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.**
13151
+ 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.**
14195
13152
 
13153
+ - **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.
14196
13154
  - **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
14197
13155
  - **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
14198
13156
  - **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
14199
13157
  - **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\`.
14200
13158
  - **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.
13159
+ - **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.
14201
13160
  - **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.
14202
13161
  - **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.
14203
13162
 
@@ -14325,7 +13284,7 @@ Every value change is mirrored to the host LLM's context automatically (debounce
14325
13284
 
14326
13285
  **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.
14327
13286
 
14328
- **\`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.
13287
+ **\`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.
14329
13288
 
14330
13289
  **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.
14331
13290
 
@@ -15432,6 +14391,31 @@ function runNoPhantomUseState(input) {
15432
14391
  }
15433
14392
  return issues;
15434
14393
  }
14394
+ function runPropSeedNoResync(input) {
14395
+ if (input.compiledCode === null) return [];
14396
+ const src = input.sourceCode;
14397
+ const issues = [];
14398
+ const re = /const\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useState(?:<[^>]*>)?\s*\(\s*props\??\.(\w+)/g;
14399
+ for (const m of src.matchAll(re)) {
14400
+ const [full, stateVar, setter, propName] = m;
14401
+ const idx = m.index ?? 0;
14402
+ const after = src.slice(idx + full.length);
14403
+ if (!new RegExp(`\\b${setter}\\s*\\(`).test(after)) continue;
14404
+ const resync = new RegExp(
14405
+ `\\[[^\\[\\]]*props\\??\\.${propName}\\b[^\\[\\]]*\\]`
14406
+ );
14407
+ if (resync.test(src)) continue;
14408
+ issues.push(
14409
+ mkIssue(
14410
+ "universal.prop_seed_no_resync",
14411
+ `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).`,
14412
+ `Add \`useEffect(() => ${setter}(props.${propName}), [props.${propName}]);\` so agent repaints re-seed the state, or derive the display directly from props.${propName}.`,
14413
+ "warn"
14414
+ )
14415
+ );
14416
+ }
14417
+ return issues;
14418
+ }
15435
14419
  var UNIVERSAL_CHECKS = [
15436
14420
  {
15437
14421
  id: "universal.prop_coverage",
@@ -15450,6 +14434,12 @@ var UNIVERSAL_CHECKS = [
15450
14434
  axis: "render",
15451
14435
  values: ALL_RENDER_VALUES,
15452
14436
  run: runNoPhantomUseState
14437
+ },
14438
+ {
14439
+ id: "universal.prop_seed_no_resync",
14440
+ axis: "render",
14441
+ values: ALL_RENDER_VALUES,
14442
+ run: runPropSeedNoResync
15453
14443
  }
15454
14444
  ];
15455
14445
 
@@ -15469,7 +14459,7 @@ function runStateSeededFromProps(input) {
15469
14459
  mkIssue(
15470
14460
  "state.merge.seeded_from_props",
15471
14461
  `Entity collection "${e.name}" is not seeded from props \u2014 no useState initializer reads props.${e.name}.`,
15472
- `Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` so stream/action updates can merge into live state.`
14462
+ `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).`
15473
14463
  )
15474
14464
  );
15475
14465
  }
@@ -15573,6 +14563,106 @@ var STATE_MERGE_CHECKS = [
15573
14563
  }
15574
14564
  ];
15575
14565
 
14566
+ // src/evaluation/axis-checks/checks/state-ui-affordance.ts
14567
+ var STATE_ARIA_OR_ROLE = /aria-(checked|pressed|selected|expanded|current)\s*=|role\s*=/;
14568
+ var STATE_PRIMITIVE_IN_SUBTREE = /<(Checkbox|Toggle|Tabs|Accordion)[\s/>]/;
14569
+ var SEMANTIC_PRIMITIVES = /* @__PURE__ */ new Set(["Button", "Checkbox", "Toggle", "Link", "MenuItem"]);
14570
+ function collectSetters(src) {
14571
+ const setters = /* @__PURE__ */ new Set();
14572
+ for (const m of src.matchAll(/const\s*\[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState/g)) {
14573
+ setters.add(m[1]);
14574
+ }
14575
+ return setters;
14576
+ }
14577
+ function collectHandlers(src, setters) {
14578
+ const handlers = /* @__PURE__ */ new Set();
14579
+ for (const m of src.matchAll(/const\s+(\w+)\s*=\s*(?:\([^)]*\)|\w+)\s*=>\s*\{?/g)) {
14580
+ const body = src.slice(m.index + m[0].length, m.index + m[0].length + 400);
14581
+ for (const s of setters) {
14582
+ if (body.includes(s)) {
14583
+ handlers.add(m[1]);
14584
+ break;
14585
+ }
14586
+ }
14587
+ }
14588
+ return handlers;
14589
+ }
14590
+ function attrBlock(src, start) {
14591
+ let depth = 0;
14592
+ for (let i = start; i < src.length; i++) {
14593
+ const c = src[i];
14594
+ if (c === "{") depth++;
14595
+ else if (c === "}") depth--;
14596
+ else if (c === ">" && depth === 0) return { block: src.slice(start, i), end: i };
14597
+ }
14598
+ return { block: src.slice(start, start + 900), end: start + 900 };
14599
+ }
14600
+ function onClickValue(block) {
14601
+ const m = block.match(/onClick\s*=\s*\{/);
14602
+ if (!m || m.index === void 0) return null;
14603
+ const open = m.index + m[0].length - 1;
14604
+ let depth = 0;
14605
+ for (let i = open; i < block.length; i++) {
14606
+ if (block[i] === "{") depth++;
14607
+ else if (block[i] === "}") {
14608
+ depth--;
14609
+ if (depth === 0) return block.slice(open + 1, i);
14610
+ }
14611
+ }
14612
+ return block.slice(open + 1);
14613
+ }
14614
+ function subtree(src, pos, tag) {
14615
+ const close = src.indexOf(`</${tag}>`, pos);
14616
+ return src.slice(pos, close === -1 ? pos + 700 : close);
14617
+ }
14618
+ function runStateAriaPresent(input) {
14619
+ if (input.compiledCode === null) return [];
14620
+ const src = input.sourceCode;
14621
+ const setters = collectSetters(src);
14622
+ const handlers = collectHandlers(src, setters);
14623
+ const issues = [];
14624
+ const flaggedLines = /* @__PURE__ */ new Set();
14625
+ const localComponents = /* @__PURE__ */ new Set();
14626
+ for (const m of src.matchAll(/(?:function\s+([A-Z]\w*)\s*\(|const\s+([A-Z]\w*)\s*=)/g)) {
14627
+ localComponents.add(m[1] ?? m[2]);
14628
+ }
14629
+ for (const m of src.matchAll(/<([A-Za-z][A-Za-z0-9]*)\b/g)) {
14630
+ const tag = m[1];
14631
+ if (m.index === void 0) continue;
14632
+ if (localComponents.has(tag)) continue;
14633
+ const { block, end } = attrBlock(src, m.index + m[0].length);
14634
+ const clickValue = onClickValue(block);
14635
+ if (clickValue === null) continue;
14636
+ 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
14637
+ // `on[A-Z]…` callback prop and is invoked from onClick — inside a
14638
+ // ui-affordance component that IS the affordance mutation.
14639
+ /\bon[A-Z]\w*\b/.test(clickValue);
14640
+ if (!mutatesAffordance) continue;
14641
+ if (SEMANTIC_PRIMITIVES.has(tag)) continue;
14642
+ if (STATE_ARIA_OR_ROLE.test(block)) continue;
14643
+ if (STATE_PRIMITIVE_IN_SUBTREE.test(subtree(src, end, tag))) continue;
14644
+ const line = src.slice(0, m.index).split("\n").length;
14645
+ if (flaggedLines.has(line)) continue;
14646
+ flaggedLines.add(line);
14647
+ issues.push(
14648
+ mkIssue(
14649
+ "state.ui_affordance.state_aria_present",
14650
+ `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).`,
14651
+ `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.`
14652
+ )
14653
+ );
14654
+ }
14655
+ return issues;
14656
+ }
14657
+ var STATE_UI_AFFORDANCE_CHECKS = [
14658
+ {
14659
+ id: "state.ui_affordance.state_aria_present",
14660
+ axis: "state",
14661
+ values: ["ui-affordance"],
14662
+ run: runStateAriaPresent
14663
+ }
14664
+ ];
14665
+
15576
14666
  // src/evaluation/axis-checks/checks/realtime.ts
15577
14667
  var REALTIME_ACTIVE = ["merge", "append", "status", "presence", "mixed"];
15578
14668
  function runStreamHandlerPerEvent(input) {
@@ -16262,6 +15352,7 @@ var EXTRA_CHECKS = [
16262
15352
  var REGISTRY = [
16263
15353
  ...UNIVERSAL_CHECKS,
16264
15354
  ...STATE_MERGE_CHECKS,
15355
+ ...STATE_UI_AFFORDANCE_CHECKS,
16265
15356
  ...REALTIME_CHECKS,
16266
15357
  ...WRITES_CHECKS,
16267
15358
  ...STATE_PAYLOAD_CHECKS,
@@ -16432,8 +15523,8 @@ ${cjsResult.code}
16432
15523
  // src/harness/check/runtime-render/load-component.ts
16433
15524
  async function loadComponent(input) {
16434
15525
  const { sourceCode, moduleResolutions = {} } = input;
16435
- const { createRequire: createRequire3 } = await import('module');
16436
- const require_ = createRequire3(import.meta.url);
15526
+ const { createRequire: createRequire4 } = await import('module');
15527
+ const require_ = createRequire4(import.meta.url);
16437
15528
  const sandboxRequire = (id) => {
16438
15529
  if (id in moduleResolutions) return moduleResolutions[id];
16439
15530
  if (id === "react/jsx-runtime" || id === "react/jsx-dev-runtime") {
@@ -16689,6 +15780,47 @@ function wrapDetection(kind, jsx, native, custom) {
16689
15780
  function dedupe(arr) {
16690
15781
  return Array.from(new Set(arr));
16691
15782
  }
15783
+ var installed = false;
15784
+ function installProductionActShim() {
15785
+ if (installed) return;
15786
+ installed = true;
15787
+ const require_ = createRequire(import.meta.url);
15788
+ let reactCjs;
15789
+ try {
15790
+ reactCjs = require_("react");
15791
+ } catch {
15792
+ return;
15793
+ }
15794
+ if (typeof reactCjs.act === "function") {
15795
+ return;
15796
+ }
15797
+ try {
15798
+ const testUtils = require_("react-dom/test-utils");
15799
+ const reactDom = require_("react-dom");
15800
+ testUtils.act = createFlushSyncAct(reactDom.flushSync);
15801
+ } catch {
15802
+ }
15803
+ }
15804
+ function createFlushSyncAct(flushSync) {
15805
+ return (callback) => {
15806
+ let callbackResult;
15807
+ flushSync(() => {
15808
+ callbackResult = callback();
15809
+ });
15810
+ if (isThenable(callbackResult)) {
15811
+ const pending = callbackResult;
15812
+ return new Promise((resolve3, reject) => {
15813
+ pending.then(() => resolve3(), reject);
15814
+ });
15815
+ }
15816
+ return new Promise((resolve3) => {
15817
+ queueMicrotask(resolve3);
15818
+ });
15819
+ };
15820
+ }
15821
+ function isThenable(value) {
15822
+ return typeof value === "object" && value !== null && typeof value.then === "function";
15823
+ }
16692
15824
 
16693
15825
  // src/internal/open-record.ts
16694
15826
  function openRecord(o) {
@@ -16806,6 +15938,7 @@ async function runRenderCheck(input) {
16806
15938
  const probe = createProbe();
16807
15939
  const wireConfig = createProbeWireConfig(probe);
16808
15940
  uninstallSpy = probe.installPostMessageSpy();
15941
+ installProductionActShim();
16809
15942
  const { render, cleanup } = await import('@testing-library/react');
16810
15943
  const userEventModule = await import('@testing-library/user-event');
16811
15944
  const userEvent = userEventModule.default ?? userEventModule;
@@ -16961,6 +16094,50 @@ async function runRenderCheck(input) {
16961
16094
  if (issue) issues.push(issue);
16962
16095
  }
16963
16096
  }
16097
+ const declaredProps = input.contract?.propsSpec?.properties;
16098
+ if (declaredProps) {
16099
+ const optionalFilled = Object.entries(declaredProps).filter(
16100
+ ([name, entry]) => entry.required !== true && input.mockupProps[name] !== void 0
16101
+ ).map(([name]) => name);
16102
+ if (optionalFilled.length > 0) {
16103
+ const requiredOnlyProps = { ...input.mockupProps };
16104
+ for (const name of optionalFilled) delete requiredOnlyProps[name];
16105
+ const omitBoundaryRef = {};
16106
+ class OmitErrorBoundary extends React.Component {
16107
+ state = { caught: false };
16108
+ static getDerivedStateFromError() {
16109
+ return { caught: true };
16110
+ }
16111
+ componentDidCatch(error) {
16112
+ omitBoundaryRef.error ??= error;
16113
+ }
16114
+ render() {
16115
+ if (this.state.caught) return null;
16116
+ return this.props.children;
16117
+ }
16118
+ }
16119
+ try {
16120
+ render(
16121
+ React.createElement(GguiWireProvider, {
16122
+ config: wireConfig,
16123
+ children: React.createElement(OmitErrorBoundary, {
16124
+ children: React.createElement(Component, requiredOnlyProps)
16125
+ })
16126
+ })
16127
+ );
16128
+ } catch (e) {
16129
+ omitBoundaryRef.error ??= e instanceof Error ? e : new Error(String(e));
16130
+ }
16131
+ if (omitBoundaryRef.error) {
16132
+ issues.push({
16133
+ check: "optional-props-omitted",
16134
+ outcome: "failed",
16135
+ subject: optionalFilled.join(", "),
16136
+ 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.`
16137
+ });
16138
+ }
16139
+ }
16140
+ }
16964
16141
  } finally {
16965
16142
  cleanup();
16966
16143
  }
@@ -17128,8 +16305,8 @@ async function setupHappyDom() {
17128
16305
  };
17129
16306
  }
17130
16307
  const { Window } = await import('happy-dom');
17131
- const window2 = new Window({ url: "https://render-check.local" });
17132
- const windowAny = openRecord(window2);
16308
+ const window = new Window({ url: "https://render-check.local" });
16309
+ const windowAny = openRecord(window);
17133
16310
  const keys = [
17134
16311
  "window",
17135
16312
  "document",
@@ -17532,6 +16709,15 @@ function synthesizeFromSchema2(schema, hint, warnings, depth) {
17532
16709
  if (schema.anyOf && schema.anyOf.length > 0) {
17533
16710
  return synthesizeFromSchema2(schema.anyOf[0], hint, warnings, depth + 1);
17534
16711
  }
16712
+ if (Array.isArray(schema.type)) {
16713
+ const primary = schema.type.find((t) => t !== "null") ?? "null";
16714
+ return synthesizeFromSchema2(
16715
+ { ...schema, type: primary },
16716
+ hint,
16717
+ warnings,
16718
+ depth + 1
16719
+ );
16720
+ }
17535
16721
  switch (schema.type) {
17536
16722
  case "string": {
17537
16723
  switch (schema.format) {
@@ -17702,6 +16888,16 @@ function toEvalIssue(issue) {
17702
16888
  description: `Component crashed at runtime: ${issue.reason}`,
17703
16889
  fix: classifyRenderCrashFix(issue.reason)
17704
16890
  };
16891
+ case "optional-props-omitted":
16892
+ return {
16893
+ tier: 0,
16894
+ result,
16895
+ category: "crash",
16896
+ subcategory,
16897
+ severity: "critical",
16898
+ description: `Component crashed when optional props were omitted: ${issue.reason}`,
16899
+ 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.`
16900
+ };
17705
16901
  case "action-wiring": {
17706
16902
  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.`;
17707
16903
  return {
@@ -17748,6 +16944,7 @@ async function warmupRuntimeRenderProbe() {
17748
16944
  missing += 1;
17749
16945
  }
17750
16946
  };
16947
+ installProductionActShim();
17751
16948
  await Promise.all([
17752
16949
  tryLoad("happy-dom"),
17753
16950
  tryLoad("@testing-library/react"),
@@ -18824,6 +18021,14 @@ function mapProviderForEvaluator(provider) {
18824
18021
 
18825
18022
  // src/harness/coding/run-eval-round.ts
18826
18023
  var MAX_FEEDBACK_ISSUES = 3;
18024
+ function selectActionableFeedback(issues) {
18025
+ const pool = issues.filter((i) => i.result === "fail" || i.result === "warn");
18026
+ const reserved = pool.find((i) => i.tier === 0 && i.result === "fail");
18027
+ return [
18028
+ ...reserved ? [reserved] : [],
18029
+ ...pool.filter((i) => i !== reserved).sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1))
18030
+ ].slice(0, MAX_FEEDBACK_ISSUES);
18031
+ }
18827
18032
  function fingerprintFail(issue) {
18828
18033
  const cat = issue.category ?? "";
18829
18034
  const sub = issue.subcategory ?? "";
@@ -18992,7 +18197,20 @@ async function runEvalRound(ctx, input) {
18992
18197
  },
18993
18198
  {
18994
18199
  provider: mapProviderForEvaluator(evaluationAgent.provider),
18995
- model: evaluationAgent.model
18200
+ model: evaluationAgent.model,
18201
+ // #484 — thread the coding-agent's routing override
18202
+ // through to the evaluator so its LLM call never falls
18203
+ // back to `process.env` either (the eval calls run
18204
+ // inside the same concurrent generation, so the race is
18205
+ // real here too, not just on the coding turn).
18206
+ routeOverride: evaluationAgent.routeOverride,
18207
+ // #489 — thread the same provider-429-retry observer.
18208
+ // Without this, a rate-limited retry inside the
18209
+ // evaluation agent's apiCall() happens (the base-class
18210
+ // retry loop is unconditional) but never reaches the
18211
+ // caller, silently missing the host application's
18212
+ // structured log.
18213
+ onRetry: evaluationAgent.onRetry
18996
18214
  },
18997
18215
  preWarmedContext
18998
18216
  ) : null,
@@ -19003,7 +18221,15 @@ async function runEvalRound(ctx, input) {
19003
18221
  model: visualEvalAgent.model,
19004
18222
  passThreshold: visualThreshold,
19005
18223
  sampleProps: visualEvaluation?.sampleProps,
19006
- viewport: visualEvaluation?.viewport
18224
+ viewport: visualEvaluation?.viewport,
18225
+ // #484/#489 — same threading as the LLM-eval leg above:
18226
+ // the visual eval's multimodal call runs inside the same
18227
+ // concurrent generation, so it needs the same routing
18228
+ // override (never fall back to `process.env`) and the
18229
+ // same provider-429-retry observer (retries reach the
18230
+ // host application's structured log).
18231
+ routeOverride: visualEvalAgent.routeOverride,
18232
+ onRetry: visualEvalAgent.onRetry
19007
18233
  }
19008
18234
  ) : null
19009
18235
  ]);
@@ -19222,7 +18448,7 @@ async function runEvalRound(ctx, input) {
19222
18448
  lastDiffFailed: false
19223
18449
  };
19224
18450
  }
19225
- 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);
18451
+ const allActionable = selectActionableFeedback(evalResult.issues);
19226
18452
  const issueLines = allActionable.map((issue) => {
19227
18453
  const tag = issue.result === "fail" ? "FAIL" : "WARN";
19228
18454
  return `[${tag}] ${issue.category}${issue.subcategory ? "/" + issue.subcategory : ""}: ${issue.description}
@@ -19237,7 +18463,16 @@ async function runEvalRound(ctx, input) {
19237
18463
  evalResult,
19238
18464
  evalRoundsUsed,
19239
18465
  prevModeSubcats: updatedPrevModeSubcats,
19240
- prevFailFingerprints: currFailFingerprints,
18466
+ // DELIVERED fails only (Exp 54 P1 addendum, proposal 2): the
18467
+ // stuck detector's premise is "the LLM saw these and did not
18468
+ // resolve them" — recording fails that the MAX_FEEDBACK_ISSUES
18469
+ // cap cut from the feedback turns truncation into a premature
18470
+ // stuck-exit (the model never saw the issue it is blamed for
18471
+ // not fixing). An undelivered fail recurring next round now
18472
+ // reads as NEW, gets delivered, and earns its fix round.
18473
+ prevFailFingerprints: new Set(
18474
+ allActionable.filter((i) => i.result === "fail").map(fingerprintFail)
18475
+ ),
19241
18476
  preWarmedContext,
19242
18477
  evalTokens,
19243
18478
  evalLlmMs,
@@ -19640,7 +18875,7 @@ async function initSession(input) {
19640
18875
  await workspace.stage();
19641
18876
  await workspace.commit("scaffold: boilerplate");
19642
18877
  const systemPrompt = input.systemPromptOverride ?? harness.how.systemPrompt;
19643
- const codingAgent = createAgent(agents.coding.provider);
18878
+ const codingAgent = createAgent(agents.coding);
19644
18879
  await codingAgent.warmCache(
19645
18880
  agents.coding.model,
19646
18881
  systemPrompt,
@@ -19729,8 +18964,18 @@ async function dispatchGeneration(params) {
19729
18964
  }
19730
18965
  const routerProvider = mapProviderForLLMRouter(params.provider);
19731
18966
  const resolvedModel = params.models?.coding ?? params.models?.default ?? params.model;
19732
- const codingAgent = { provider: routerProvider, model: resolvedModel };
19733
- const evaluationAgent = params.models?.evaluation ? { provider: routerProvider, model: params.models.evaluation } : void 0;
18967
+ const codingAgent = {
18968
+ provider: routerProvider,
18969
+ model: resolvedModel,
18970
+ routeOverride: params.routeOverride,
18971
+ onRetry: params.onRetry
18972
+ };
18973
+ const evaluationAgent = params.models?.evaluation ? {
18974
+ provider: routerProvider,
18975
+ model: params.models.evaluation,
18976
+ routeOverride: params.routeOverride,
18977
+ onRetry: params.onRetry
18978
+ } : void 0;
19734
18979
  const effectiveContract = params.contract;
19735
18980
  const sessionParams = {
19736
18981
  userPrompt: params.originalPrompt ?? params.userPrompt,
@@ -20090,11 +19335,15 @@ ${JSON.stringify(clientCapabilities, null, 2)}
20090
19335
  const uiRequirements = [];
20091
19336
  if (contract.propsSpec?.properties) {
20092
19337
  for (const [name, entry] of Object.entries(contract.propsSpec.properties)) {
20093
- const required = entry.required;
20094
- if (required !== false) {
19338
+ const required = entry.required === true;
19339
+ if (required) {
20095
19340
  uiRequirements.push(
20096
19341
  `- **Required prop \`${name}\`** must appear somewhere in rendered DOM (display the value, or use it to drive a label/aria/key).`
20097
19342
  );
19343
+ } else {
19344
+ uiRequirements.push(
19345
+ `- **Optional prop \`${name}\`** may be ABSENT at runtime \u2014 the component must render without it (guard the access or apply its default) and must display it when it IS provided.`
19346
+ );
20098
19347
  }
20099
19348
  }
20100
19349
  }
@@ -20177,7 +19426,9 @@ function createUiGenerator(options = {}) {
20177
19426
  maxAttempts,
20178
19427
  maxEvalRounds,
20179
19428
  qualityConfig,
20180
- gadgetCatalog
19429
+ gadgetCatalog,
19430
+ disableEnvMutation = false,
19431
+ onRetry
20181
19432
  } = options;
20182
19433
  const identity = resolveIdentity(options);
20183
19434
  return {
@@ -20206,16 +19457,22 @@ function createUiGenerator(options = {}) {
20206
19457
  });
20207
19458
  }
20208
19459
  const envBackup = {};
20209
- for (const k of Object.keys(route.env)) envBackup[k] = process.env[k];
20210
- const baseEnvForApply = {};
20211
- for (const [k, v] of Object.entries(process.env)) {
20212
- if (v !== void 0) baseEnvForApply[k] = v;
20213
- }
20214
- const routedEnv = applyRouteToEnv(baseEnvForApply, route);
20215
- for (const [k, v] of Object.entries(routedEnv)) process.env[k] = v;
20216
- for (const [k, v] of Object.entries(route.env)) {
20217
- if (v === void 0) delete process.env[k];
19460
+ if (!disableEnvMutation) {
19461
+ for (const k of Object.keys(route.env)) envBackup[k] = process.env[k];
19462
+ const baseEnvForApply = {};
19463
+ for (const [k, v] of Object.entries(process.env)) {
19464
+ if (v !== void 0) baseEnvForApply[k] = v;
19465
+ }
19466
+ const routedEnv = applyRouteToEnv(baseEnvForApply, route);
19467
+ for (const [k, v] of Object.entries(routedEnv)) process.env[k] = v;
19468
+ for (const [k, v] of Object.entries(route.env)) {
19469
+ if (v === void 0) delete process.env[k];
19470
+ }
20218
19471
  }
19472
+ const routeOverride = disableEnvMutation ? {
19473
+ apiKey: route.env["ANTHROPIC_API_KEY"] ?? route.env["OPENAI_API_KEY"] ?? route.env["GEMINI_API_KEY"] ?? route.env["OPENROUTER_API_KEY"],
19474
+ useBedrock: route.env["CLAUDE_CODE_USE_BEDROCK"] === "1"
19475
+ } : void 0;
20219
19476
  try {
20220
19477
  const tools = createGeneratorTools({
20221
19478
  ...input.contract ? { contract: input.contract } : {}
@@ -20248,7 +19505,9 @@ function createUiGenerator(options = {}) {
20248
19505
  // `Type:` lines and the coding-agent typecheck overlay via
20249
19506
  // `dispatchGeneration`.
20250
19507
  ...input.gadgetTypes !== void 0 ? { gadgetTypes: input.gadgetTypes } : {},
20251
- enableRuntimeRender
19508
+ enableRuntimeRender,
19509
+ ...routeOverride !== void 0 ? { routeOverride } : {},
19510
+ ...onRetry !== void 0 ? { onRetry } : {}
20252
19511
  });
20253
19512
  const metadata = {
20254
19513
  provider: input.llm.provider,