@ggui-ai/ui-gen 0.13.0 → 0.14.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 (62) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +2 -2
  2. package/dist/adapters/generation-dispatch.js +190 -57
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +5 -8
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +3 -3
  7. package/dist/advanced/index.js +190 -57
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +2 -7
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.js +4 -4
  12. package/dist/boilerplate.js.map +1 -1
  13. package/dist/check/index.d.ts +1 -1
  14. package/dist/check/index.js +55 -15
  15. package/dist/check/index.js.map +1 -1
  16. package/dist/{create-ui-generator-CF0EL0Li.d.ts → create-ui-generator-D6TKuv9V.d.ts} +2 -2
  17. package/dist/{dispatch-D75M-OfZ.d.ts → dispatch-BuZfTh9r.d.ts} +1 -1
  18. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  19. package/dist/evaluation/axis-checks/index.js.map +1 -1
  20. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  21. package/dist/evaluation/index.d.ts +3 -3
  22. package/dist/evaluation/index.js +79 -16
  23. package/dist/evaluation/index.js.map +1 -1
  24. package/dist/harness/check/runtime-render/index.d.ts +1 -1
  25. package/dist/harness/check/runtime-render/index.js +2 -7
  26. package/dist/harness/check/runtime-render/index.js.map +1 -1
  27. package/dist/harness/index.d.ts +2 -2
  28. package/dist/harness/index.js +64 -19
  29. package/dist/harness/index.js.map +1 -1
  30. package/dist/harness/prompts.js +2 -0
  31. package/dist/harness/prompts.js.map +1 -1
  32. package/dist/harness/result-types.d.ts +1 -1
  33. package/dist/harness/runtime.d.ts +2 -2
  34. package/dist/harness/runtime.js +5 -5
  35. package/dist/harness/runtime.js.map +1 -1
  36. package/dist/harness/types-public.d.ts +1 -1
  37. package/dist/harness/types-public.js +4 -4
  38. package/dist/harness/types-public.js.map +1 -1
  39. package/dist/harness/validator-trace-sink.d.ts +1 -1
  40. package/dist/index.d.ts +3 -3
  41. package/dist/index.js +190 -57
  42. package/dist/index.js.map +1 -1
  43. package/dist/{llm-router-BEVtbAb9.d.ts → llm-router-D7fjBhtr.d.ts} +39 -0
  44. package/dist/patch.d.ts +1 -1
  45. package/dist/provider-adapter-contract.js +1017 -731
  46. package/dist/provider-adapter-contract.js.map +1 -1
  47. package/dist/{types-public-DIpjC-OA.d.ts → types-public-CkNWEov9.d.ts} +64 -1
  48. package/dist/validation/index.d.ts +1 -1
  49. package/dist/validation/index.js +1 -1
  50. package/dist/validation/index.js.map +1 -1
  51. package/dist/workflows.d.ts +1 -1
  52. package/package.json +21 -21
  53. package/src/adapters/provider-router.ts +9 -0
  54. package/src/boilerplate/system-prompt.ts +4 -4
  55. package/src/check/run-tier0.ts +109 -22
  56. package/src/evaluation/index.ts +4 -0
  57. package/src/evaluation/llm-evaluator.ts +52 -8
  58. package/src/evaluation/types-public.ts +79 -0
  59. package/src/evaluation/visual-evaluator.ts +27 -5
  60. package/src/harness/coding/run-eval-round.ts +32 -6
  61. package/src/harness/llm-router.ts +128 -2
  62. package/src/validation/primitives.ts +1 -1
@@ -23,6 +23,7 @@ import type { Classification } from "../../classifier/index.js";
23
23
  import type { AgentWorkspace } from "../../coding-agent/workspace.js";
24
24
  import type { CostTracker } from "../../evaluation/cost-tracker.js";
25
25
  import type { EvalIssue, EvalResult, RuntimeProbeMeta } from "../../evaluation/types-public.js";
26
+ import { notApplicableCoverage } from "../../evaluation/types-public.js";
26
27
  import { mapProviderForEvaluator } from "../enforced-coding.js";
27
28
  import { runCheck } from "../index.js";
28
29
  import { isRecoverableRenderCrash } from "../check/runtime-render/index.js";
@@ -121,6 +122,15 @@ export interface EvalRoundResult {
121
122
  /** Cap feedback to N issues per eval-fix turn so the LLM stays surgical. */
122
123
  const MAX_FEEDBACK_ISSUES = 3;
123
124
 
125
+ /**
126
+ * `criteriaCoverage` reason stamped on the same-image low-risk bypass
127
+ * exits: tier-1/2 evaluation is never invoked there BY DESIGN, so every
128
+ * criterion is `not-applicable` (excluded from coverage denominators),
129
+ * not `skipped` (which would count as silent absence).
130
+ */
131
+ const LOW_RISK_BYPASS_REASON =
132
+ "same-image low-risk bypass: axis checks clean, tier-1/2 evaluation not invoked";
133
+
124
134
  /**
125
135
  * Pick the ≤{@link MAX_FEEDBACK_ISSUES} issues an eval-fix turn feeds
126
136
  * back. Provenance-aware (Exp 54 P1 addendum): ONE slot is reserved
@@ -375,7 +385,12 @@ export async function runEvalRound(
375
385
  );
376
386
  const lines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
377
387
  const allIssues = [...modeIssues, ...exitProbe.probeIssues];
378
- evalResult = { issues: allIssues, pass: ["axis.low-risk"], runtimeProbe: exitProbe.meta };
388
+ evalResult = {
389
+ issues: allIssues,
390
+ pass: ["axis.low-risk"],
391
+ criteriaCoverage: notApplicableCoverage(LOW_RISK_BYPASS_REASON),
392
+ runtimeProbe: exitProbe.meta,
393
+ };
379
394
  console.log(
380
395
  `[simple] eval round ${evalRoundsUsed}: low-risk bypass clean BUT ` +
381
396
  `runtime probe fail (recoverable) — granting +1 turn`,
@@ -398,6 +413,7 @@ export async function runEvalRound(
398
413
  evalResult = {
399
414
  issues: [...modeIssues, ...exitProbe.probeIssues],
400
415
  pass: ["axis.low-risk"],
416
+ criteriaCoverage: notApplicableCoverage(LOW_RISK_BYPASS_REASON),
401
417
  runtimeProbe: exitProbe.meta,
402
418
  };
403
419
  console.log(
@@ -499,7 +515,16 @@ export async function runEvalRound(
499
515
  ...modeIssues,
500
516
  ];
501
517
  const allPass: string[] = [...(llmResult?.pass ?? [])];
502
- evalResult = { issues: allIssues, pass: allPass };
518
+ // Carry the per-criterion coverage stamp from the evaluator; every
519
+ // later re-stamp of evalResult below spreads this base so the field
520
+ // survives to the assembled result (benchmark reads it per run).
521
+ evalResult = {
522
+ issues: allIssues,
523
+ pass: allPass,
524
+ ...(llmResult?.criteriaCoverage !== undefined
525
+ ? { criteriaCoverage: llmResult.criteriaCoverage }
526
+ : {}),
527
+ };
503
528
 
504
529
  // ── Log merged results, with mode-check subcategory breakdown ──
505
530
  const fails = allIssues.filter((i) => i.result === "fail");
@@ -570,7 +595,7 @@ export async function runEvalRound(
570
595
  );
571
596
  const lines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
572
597
  const enrichedIssues = [...allIssues, ...exitProbe.probeIssues];
573
- evalResult = { issues: enrichedIssues, pass: allPass, runtimeProbe: exitProbe.meta };
598
+ evalResult = { ...evalResult, issues: enrichedIssues, pass: allPass, runtimeProbe: exitProbe.meta };
574
599
  const enrichedFingerprints = new Set([
575
600
  ...currFailFingerprints,
576
601
  ...runtimeFails.map(fingerprintFail),
@@ -598,6 +623,7 @@ export async function runEvalRound(
598
623
  // into the merged result and ALWAYS stamp its execution meta, so a
599
624
  // did-not-run probe is visible downstream even on this clean exit.
600
625
  evalResult = {
626
+ ...evalResult,
601
627
  issues: [...allIssues, ...exitProbe.probeIssues],
602
628
  pass: allPass,
603
629
  runtimeProbe: exitProbe.meta,
@@ -659,8 +685,8 @@ export async function runEvalRound(
659
685
  fixtureProps,
660
686
  });
661
687
  evalResult = {
688
+ ...evalResult,
662
689
  issues: [...evalResult.issues, ...stuckExitProbe.probeIssues],
663
- pass: evalResult.pass,
664
690
  runtimeProbe: stuckExitProbe.meta,
665
691
  };
666
692
  return {
@@ -718,8 +744,8 @@ export async function runEvalRound(
718
744
  // ALWAYS stamp the probe's execution meta, even though we're not
719
745
  // granting an extension — a did-not-run probe must stay visible.
720
746
  evalResult = {
747
+ ...evalResult,
721
748
  issues: [...evalResult.issues, ...exitProbe.probeIssues],
722
- pass: evalResult.pass,
723
749
  runtimeProbe: exitProbe.meta,
724
750
  };
725
751
  console.log(`[simple] eval round ${evalRoundsUsed}: max eval rounds reached — stopping`);
@@ -748,7 +774,7 @@ export async function runEvalRound(
748
774
  .slice(0, MAX_FEEDBACK_ISSUES)
749
775
  .map(formatRuntimeProbeFeedback);
750
776
  const enrichedIssues = [...evalResult.issues, ...exitProbe.probeIssues];
751
- evalResult = { issues: enrichedIssues, pass: evalResult.pass, runtimeProbe: exitProbe.meta };
777
+ evalResult = { ...evalResult, issues: enrichedIssues, runtimeProbe: exitProbe.meta };
752
778
  console.log(
753
779
  `[simple] eval round ${evalRoundsUsed}: runtime-probe fail active at cap — granting +1 retry (${maxEvalRounds + RUNTIME_EXTENSION_BONUS - evalRoundsUsed} of ${RUNTIME_EXTENSION_BONUS} bonus rounds remain)`,
754
780
  );
@@ -52,6 +52,14 @@ export interface AgentConfig {
52
52
  * Sampling temperature. When defined it is threaded into the provider
53
53
  * request; when undefined the provider default is used (unchanged
54
54
  * behavior for all existing callers).
55
+ *
56
+ * Capability is the ROUTER's: Anthropic Opus 4.7+ and the 5-family
57
+ * (`claude-opus-4-7*`, `claude-fable-5*`, `claude-opus-5*`,
58
+ * `claude-sonnet-5*`, incl. Bedrock ids) return HTTP 400 on a
59
+ * non-default `temperature`/`top_p`/`top_k`, so the Anthropic adapter
60
+ * STRIPS the value for those ids (one log line naming model + param)
61
+ * and reports the effective sampling on `LLMResponse.sampling`. Callers
62
+ * keep expressing intent; disclosure reads the applied value.
55
63
  */
56
64
  temperature?: number;
57
65
  /**
@@ -101,10 +109,32 @@ export interface ProviderRetryInfo {
101
109
  readonly message: string;
102
110
  }
103
111
 
112
+ /**
113
+ * What sampling the provider ACTUALLY received for a text call — so a
114
+ * caller that asked for `temperature: 0` (a disclosed reproducibility
115
+ * property, e.g. the benchmark judge panel) can record the effective
116
+ * value instead of the requested one. `'provider-default'` means no
117
+ * temperature reached the provider: either the caller passed none, or
118
+ * the adapter stripped it because the model rejects sampling params
119
+ * (Anthropic Opus 4.7+ / 5-family → HTTP 400 on non-default
120
+ * `temperature`/`top_p`/`top_k`); `strippedReason` names the second case.
121
+ */
122
+ export interface AppliedSampling {
123
+ readonly temperature: number | 'provider-default';
124
+ readonly strippedReason?: string;
125
+ }
126
+
104
127
  export interface LLMResponse {
105
128
  text: string;
106
129
  inputTokens: number;
107
130
  outputTokens: number;
131
+ /**
132
+ * Effective sampling for this call (see {@link AppliedSampling}).
133
+ * Every provider's `callText` sets it; absent only on paths that
134
+ * predate the field — treat absence as unknown, never as "as
135
+ * requested".
136
+ */
137
+ sampling?: AppliedSampling;
108
138
  }
109
139
 
110
140
  /**
@@ -163,6 +193,16 @@ export interface LLMToolCallResponse {
163
193
  toolCalls: LLMToolCall[];
164
194
  inputTokens: number;
165
195
  outputTokens: number;
196
+ /**
197
+ * The tool-choice mode the provider ACTUALLY received. Callers ask
198
+ * for `'required'` (Anthropic `tool_choice: any`); Claude Fable 5.1
199
+ * rejects forced tool choice with HTTP 400, so the Anthropic adapter
200
+ * downgrades to `'auto'` for that family and reports it here. A
201
+ * downgraded call may legitimately return zero tool calls — the
202
+ * evaluator's `criteriaCoverage` records that as `skipped`, never as
203
+ * a pass. Absent = predates the field (treat as unknown).
204
+ */
205
+ appliedToolChoice?: 'required' | 'auto';
166
206
  /**
167
207
  * Tokens read from / written to the prompt cache, when the provider
168
208
  * reports them. Optional because not every provider exposes
@@ -506,6 +546,65 @@ export abstract class LLMAgent {
506
546
  // AnthropicAgent
507
547
  // =============================================================================
508
548
 
549
+ /**
550
+ * Strip every routing spelling down to the bare Claude API id so the
551
+ * family predicates below see one form: `anthropic/claude-x`,
552
+ * `anthropic.claude-x`, `us.anthropic.claude-x[-vN:M]` and the bare
553
+ * `claude-x` all normalize to `claude-x…`. ARNs are left as-is (they
554
+ * name a profile, not a family) and match no predicate.
555
+ */
556
+ export function normalizeAnthropicModelId(model: string): string {
557
+ return model
558
+ .replace(/^anthropic\//, '')
559
+ .replace(/^(?:[a-z]{2}\.)?anthropic\./, '');
560
+ }
561
+
562
+ /**
563
+ * Models that reject non-default sampling parameters with HTTP 400:
564
+ * Opus 4.7 and everything after it (Fable 5, Fable 5.1, Opus 5,
565
+ * Sonnet 5). Haiku 4.5 (`claude-haiku-4-5*`) still accepts them.
566
+ * Strings per ggui#706 (platform.claude.com, verified 2026-09-02).
567
+ */
568
+ export function anthropicRejectsSamplingParams(model: string): boolean {
569
+ return /^claude-(?:opus-4-7|opus-5|sonnet-5|fable-5)(?:-|$)/.test(
570
+ normalizeAnthropicModelId(model),
571
+ );
572
+ }
573
+
574
+ /**
575
+ * Models that reject a FORCED tool choice (`tool_choice: any` / a named
576
+ * tool) with HTTP 400: Claude Fable 5.1 (ggui#706). `auto` is accepted.
577
+ */
578
+ export function anthropicRejectsForcedToolChoice(model: string): boolean {
579
+ return /^claude-fable-5-1(?:-|$)/.test(normalizeAnthropicModelId(model));
580
+ }
581
+
582
+ /**
583
+ * Decide what sampling reaches Anthropic for `model` given the caller's
584
+ * `temperature`: pass it through on models that accept it, strip it on
585
+ * the 4.7+/5-family and say so. Pure — the caller logs and spreads.
586
+ */
587
+ export function resolveAnthropicSampling(
588
+ model: string,
589
+ temperature: number | undefined,
590
+ ): { readonly request: { temperature?: number }; readonly applied: AppliedSampling } {
591
+ if (temperature === undefined) {
592
+ return { request: {}, applied: { temperature: 'provider-default' } };
593
+ }
594
+ if (anthropicRejectsSamplingParams(model)) {
595
+ return {
596
+ request: {},
597
+ applied: {
598
+ temperature: 'provider-default',
599
+ strippedReason:
600
+ `${normalizeAnthropicModelId(model)} rejects non-default sampling params (HTTP 400 on Opus 4.7+/5-family); ` +
601
+ `caller's temperature=${temperature} was not sent`,
602
+ },
603
+ };
604
+ }
605
+ return { request: { temperature }, applied: { temperature } };
606
+ }
607
+
509
608
  export class AnthropicAgent extends LLMAgent {
510
609
  readonly provider = 'anthropic' as const;
511
610
 
@@ -568,6 +667,14 @@ export class AnthropicAgent extends LLMAgent {
568
667
  },
569
668
  ];
570
669
 
670
+ // Capability is the router's: the 4.7+/5-family rejects sampling
671
+ // params with 400, so strip (and disclose) rather than fail the call
672
+ // or make callers stop expressing intent (ggui#710 / #706).
673
+ const sampling = resolveAnthropicSampling(resolvedModel, temperature);
674
+ if (sampling.applied.strippedReason !== undefined) {
675
+ console.warn(`[anthropic] callText: ${sampling.applied.strippedReason}`);
676
+ }
677
+
571
678
  try {
572
679
  const response = await this.apiCall(() =>
573
680
  client.messages.create({
@@ -575,7 +682,7 @@ export class AnthropicAgent extends LLMAgent {
575
682
  max_tokens: maxTokens ?? 4096,
576
683
  system,
577
684
  messages: [{ role: 'user', content: userPrompt }],
578
- ...(temperature !== undefined && { temperature }),
685
+ ...sampling.request,
579
686
  }),
580
687
  );
581
688
 
@@ -618,6 +725,7 @@ export class AnthropicAgent extends LLMAgent {
618
725
  text,
619
726
  inputTokens: response.usage.input_tokens,
620
727
  outputTokens: response.usage.output_tokens,
728
+ sampling: sampling.applied,
621
729
  };
622
730
  } catch (e) {
623
731
  const endedAt = Date.now();
@@ -761,6 +869,20 @@ export class AnthropicAgent extends LLMAgent {
761
869
  // want the final-message shape downstream, so use the stream
762
870
  // helper's `finalMessage()` which folds the events back to the
763
871
  // same `Message` Type the non-streaming path returned.
872
+ // Fable 5.1 rejects a forced tool choice (400); downgrade to
873
+ // `auto` for that family and disclose it on the response so a
874
+ // resulting zero-tool-call reply is never mistaken for a verdict
875
+ // (ggui#710 / #706).
876
+ const appliedToolChoice: 'required' | 'auto' =
877
+ toolChoice === 'required' && anthropicRejectsForcedToolChoice(resolvedModel)
878
+ ? 'auto'
879
+ : toolChoice;
880
+ if (appliedToolChoice !== toolChoice) {
881
+ console.warn(
882
+ `[anthropic] callTools: ${normalizeAnthropicModelId(resolvedModel)} rejects forced tool_choice (HTTP 400); ` +
883
+ `caller's 'required' downgraded to 'auto'`,
884
+ );
885
+ }
764
886
  const response = await this.apiCall(() =>
765
887
  client.messages
766
888
  .stream({
@@ -769,7 +891,7 @@ export class AnthropicAgent extends LLMAgent {
769
891
  system,
770
892
  messages: [{ role: 'user', content: userPrompt }],
771
893
  tools: cachedTools,
772
- tool_choice: { type: toolChoice === 'required' ? 'any' : 'auto' },
894
+ tool_choice: { type: appliedToolChoice === 'required' ? 'any' : 'auto' },
773
895
  })
774
896
  .finalMessage(),
775
897
  );
@@ -821,6 +943,7 @@ export class AnthropicAgent extends LLMAgent {
821
943
  outputTokens: response.usage.output_tokens,
822
944
  cacheReadTokens: cacheRead,
823
945
  cacheCreationTokens: cacheCreated,
946
+ appliedToolChoice,
824
947
  };
825
948
  } catch (e) {
826
949
  const endedAt = Date.now();
@@ -1012,6 +1135,7 @@ export class OpenAIAgent extends LLMAgent {
1012
1135
  text,
1013
1136
  inputTokens: response.usage?.input_tokens ?? 0,
1014
1137
  outputTokens: response.usage?.output_tokens ?? 0,
1138
+ sampling: { temperature: temperature ?? 'provider-default' },
1015
1139
  };
1016
1140
  }
1017
1141
 
@@ -1270,6 +1394,7 @@ export class GoogleAgent extends LLMAgent {
1270
1394
  text: interactionText(interaction),
1271
1395
  inputTokens: interaction.usage?.total_input_tokens ?? 0,
1272
1396
  outputTokens: interaction.usage?.total_output_tokens ?? 0,
1397
+ sampling: { temperature: temperature ?? 'provider-default' },
1273
1398
  };
1274
1399
  }
1275
1400
 
@@ -1669,6 +1794,7 @@ export class OpenRouterAgent extends LLMAgent {
1669
1794
  text,
1670
1795
  inputTokens: response.usage.prompt_tokens,
1671
1796
  outputTokens: response.usage.completion_tokens,
1797
+ sampling: { temperature: temperature ?? 'provider-default' },
1672
1798
  };
1673
1799
  }
1674
1800