@opensearch-project/agent-health 0.5.2 → 0.6.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 (126) hide show
  1. package/README.md +1 -0
  2. package/cli/dist/index.js +1592 -620
  3. package/dist/assets/index-BfxtxmKc.css +1 -0
  4. package/dist/assets/index-CrjAfDHu.js +243 -0
  5. package/dist/index.html +2 -2
  6. package/docs/CLI.md +45 -2
  7. package/docs/CONFIGURATION.md +1 -1
  8. package/docs/INSTRUMENT_WITH_OTEL.md +7 -0
  9. package/docs/SDK.md +52 -3
  10. package/docs/STORAGE_INDEX_FIELD_LIMITS.md +216 -0
  11. package/docs/skills/AGENT_HEALTH.md +55 -1
  12. package/examples/eval-files/demo.eval.js +1 -1
  13. package/examples/eval-files/ops-rca-classification.eval.js +71 -0
  14. package/examples/eval-files/ops-rca-evaluator.json +15 -0
  15. package/examples/eval-files/sdk-demo.eval.js +72 -0
  16. package/examples/eval-files/sdk-describe-demo.eval.js +50 -0
  17. package/examples/eval-files/sdk-hooks-demo.eval.js +1 -1
  18. package/lib/dist/lib/bedrockCompat.d.ts +27 -0
  19. package/lib/dist/lib/bedrockCompat.d.ts.map +1 -0
  20. package/lib/dist/lib/bedrockCompat.js +83 -0
  21. package/lib/dist/lib/bedrockCompat.js.map +1 -0
  22. package/lib/dist/lib/benchmarkImage.d.ts +52 -0
  23. package/lib/dist/lib/benchmarkImage.d.ts.map +1 -0
  24. package/lib/dist/lib/benchmarkImage.js +113 -0
  25. package/lib/dist/lib/benchmarkImage.js.map +1 -0
  26. package/lib/dist/lib/benchmarkVersionUtils.d.ts +13 -0
  27. package/lib/dist/lib/benchmarkVersionUtils.d.ts.map +1 -1
  28. package/lib/dist/lib/benchmarkVersionUtils.js +21 -0
  29. package/lib/dist/lib/benchmarkVersionUtils.js.map +1 -1
  30. package/lib/dist/lib/chunkedFetch.d.ts +18 -0
  31. package/lib/dist/lib/chunkedFetch.d.ts.map +1 -0
  32. package/lib/dist/lib/chunkedFetch.js +40 -0
  33. package/lib/dist/lib/chunkedFetch.js.map +1 -0
  34. package/lib/dist/lib/comparisonInsights.d.ts +104 -0
  35. package/lib/dist/lib/comparisonInsights.d.ts.map +1 -0
  36. package/lib/dist/lib/comparisonInsights.js +212 -0
  37. package/lib/dist/lib/comparisonInsights.js.map +1 -0
  38. package/lib/dist/lib/config/loader.d.ts.map +1 -1
  39. package/lib/dist/lib/config/loader.js +5 -0
  40. package/lib/dist/lib/config/loader.js.map +1 -1
  41. package/lib/dist/lib/config/types.d.ts +14 -0
  42. package/lib/dist/lib/config/types.d.ts.map +1 -1
  43. package/lib/dist/lib/constants.d.ts +11 -0
  44. package/lib/dist/lib/constants.d.ts.map +1 -1
  45. package/lib/dist/lib/constants.js +10 -1
  46. package/lib/dist/lib/constants.js.map +1 -1
  47. package/lib/dist/lib/contextFormat.d.ts +26 -0
  48. package/lib/dist/lib/contextFormat.d.ts.map +1 -0
  49. package/lib/dist/lib/contextFormat.js +28 -0
  50. package/lib/dist/lib/contextFormat.js.map +1 -0
  51. package/lib/dist/lib/envCompat.d.ts.map +1 -1
  52. package/lib/dist/lib/envCompat.js +14 -5
  53. package/lib/dist/lib/envCompat.js.map +1 -1
  54. package/lib/dist/lib/evaluationRerun.d.ts +63 -0
  55. package/lib/dist/lib/evaluationRerun.d.ts.map +1 -0
  56. package/lib/dist/lib/evaluationRerun.js +85 -0
  57. package/lib/dist/lib/evaluationRerun.js.map +1 -0
  58. package/lib/dist/lib/matchers/traces.d.ts +17 -2
  59. package/lib/dist/lib/matchers/traces.d.ts.map +1 -1
  60. package/lib/dist/lib/matchers/traces.js +136 -16
  61. package/lib/dist/lib/matchers/traces.js.map +1 -1
  62. package/lib/dist/lib/matchers/tracesPricing.d.ts +38 -0
  63. package/lib/dist/lib/matchers/tracesPricing.d.ts.map +1 -0
  64. package/lib/dist/lib/matchers/tracesPricing.js +64 -0
  65. package/lib/dist/lib/matchers/tracesPricing.js.map +1 -0
  66. package/lib/dist/lib/runStats.d.ts +24 -0
  67. package/lib/dist/lib/runStats.d.ts.map +1 -1
  68. package/lib/dist/lib/runStats.js +32 -0
  69. package/lib/dist/lib/runStats.js.map +1 -1
  70. package/lib/dist/lib/testCases/loader.d.ts +11 -1
  71. package/lib/dist/lib/testCases/loader.d.ts.map +1 -1
  72. package/lib/dist/lib/testCases/loader.js +35 -5
  73. package/lib/dist/lib/testCases/loader.js.map +1 -1
  74. package/lib/dist/lib/utils.d.ts +15 -0
  75. package/lib/dist/lib/utils.d.ts.map +1 -1
  76. package/lib/dist/lib/utils.js +22 -0
  77. package/lib/dist/lib/utils.js.map +1 -1
  78. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts +10 -0
  79. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts.map +1 -1
  80. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js +17 -3
  81. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js.map +1 -1
  82. package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts.map +1 -1
  83. package/lib/dist/services/connectors/subprocess/SubprocessConnector.js +8 -0
  84. package/lib/dist/services/connectors/subprocess/SubprocessConnector.js.map +1 -1
  85. package/lib/dist/services/evaluation/bedrockJudge.d.ts +7 -0
  86. package/lib/dist/services/evaluation/bedrockJudge.d.ts.map +1 -1
  87. package/lib/dist/services/evaluation/bedrockJudge.js +2 -0
  88. package/lib/dist/services/evaluation/bedrockJudge.js.map +1 -1
  89. package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts +1 -1
  90. package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts.map +1 -1
  91. package/lib/dist/services/storage/asyncBenchmarkStorage.js +14 -3
  92. package/lib/dist/services/storage/asyncBenchmarkStorage.js.map +1 -1
  93. package/lib/dist/services/storage/asyncRunStorage.d.ts +9 -1
  94. package/lib/dist/services/storage/asyncRunStorage.d.ts.map +1 -1
  95. package/lib/dist/services/storage/asyncRunStorage.js +77 -1
  96. package/lib/dist/services/storage/asyncRunStorage.js.map +1 -1
  97. package/lib/dist/services/storage/asyncTestCaseStorage.d.ts +18 -4
  98. package/lib/dist/services/storage/asyncTestCaseStorage.d.ts.map +1 -1
  99. package/lib/dist/services/storage/asyncTestCaseStorage.js +20 -4
  100. package/lib/dist/services/storage/asyncTestCaseStorage.js.map +1 -1
  101. package/lib/dist/services/storage/opensearchClient.d.ts +35 -12
  102. package/lib/dist/services/storage/opensearchClient.d.ts.map +1 -1
  103. package/lib/dist/services/storage/opensearchClient.js +12 -5
  104. package/lib/dist/services/storage/opensearchClient.js.map +1 -1
  105. package/lib/dist/services/traces/browserRecovery.d.ts +12 -1
  106. package/lib/dist/services/traces/browserRecovery.d.ts.map +1 -1
  107. package/lib/dist/services/traces/browserRecovery.js +32 -5
  108. package/lib/dist/services/traces/browserRecovery.js.map +1 -1
  109. package/lib/dist/services/traces/messageExtraction.d.ts.map +1 -1
  110. package/lib/dist/services/traces/messageExtraction.js +95 -33
  111. package/lib/dist/services/traces/messageExtraction.js.map +1 -1
  112. package/lib/dist/services/traces/spansToTrajectory.d.ts.map +1 -1
  113. package/lib/dist/services/traces/spansToTrajectory.js +47 -9
  114. package/lib/dist/services/traces/spansToTrajectory.js.map +1 -1
  115. package/lib/dist/services/traces/tracePoller.d.ts +27 -3
  116. package/lib/dist/services/traces/tracePoller.d.ts.map +1 -1
  117. package/lib/dist/services/traces/tracePoller.js +199 -33
  118. package/lib/dist/services/traces/tracePoller.js.map +1 -1
  119. package/lib/dist/types/index.d.ts +55 -1
  120. package/lib/dist/types/index.d.ts.map +1 -1
  121. package/lib/dist/types/index.js.map +1 -1
  122. package/package.json +3 -1
  123. package/server/dist/app.js +2238 -777
  124. package/server/dist/index.js +2238 -777
  125. package/dist/assets/index-CCQRDlO0.js +0 -243
  126. package/dist/assets/index-CNHQVbcj.css +0 -1
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright OpenSearch Contributors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * SDK demo with describe() grouping. Each describe() block declares a
8
+ * benchmark; CLI auto-creates one benchmark per group and runs them.
9
+ */
10
+
11
+ const { describe, test, expect } = require('@opensearch-project/agent-health');
12
+
13
+ describe('RCA Suite', () => {
14
+ test('payment service is the root cause', {
15
+ prompt: 'Diagnose why the payment service is failing.',
16
+ context: [{ description: 'Error log', value: 'ERROR [payment-service] Connection refused to db-primary:5432' }],
17
+ labels: ['category:RCA', 'difficulty:Medium'],
18
+ }, async function ({ agent, judge }) {
19
+ const result = await agent.run();
20
+ expect(result.trajectory).to.have.length.greaterThan(0);
21
+ expect(result).to.haveCompletedWithin(60_000);
22
+ await judge(result, 'Mentions the payment service or its database connection failure');
23
+ });
24
+
25
+ test('budget cap', {
26
+ prompt: 'Summarize the system in one paragraph.',
27
+ labels: ['category:Budget', 'difficulty:Easy'],
28
+ }, async function ({ agent, traces }) {
29
+ const result = await agent.run();
30
+ expect(result.agentOutput.length).to.be.greaterThan(0);
31
+ expect(traces.totalTokens).to.be.lessThan(50_000);
32
+ });
33
+ });
34
+
35
+ describe('Smoke', () => {
36
+ test('says hello', {
37
+ prompt: 'Say hello in one short sentence.',
38
+ labels: ['category:Smoke', 'difficulty:Easy'],
39
+ }, async function ({ agent }) {
40
+ const result = await agent.run();
41
+ expect(result.trajectory).to.have.length.greaterThan(0);
42
+ expect(result.agentOutput.trim()).to.have.length.greaterThan(0);
43
+ expect(result).to.haveCompletedWithin(30_000);
44
+ });
45
+ });
46
+
47
+ // Tests outside any describe() — go to the file-default benchmark
48
+ test('orphan-deterministic', function () {
49
+ expect(2 + 2).to.equal(4);
50
+ });
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Run with:
16
16
  * AH_PORT=4002 npx @opensearch-project/agent-health benchmark \
17
- * -f evals/sdk-hooks-demo.eval.js -a demo
17
+ * -f examples/eval-files/sdk-hooks-demo.eval.js -a demo
18
18
  */
19
19
 
20
20
  const fs = require('fs');
@@ -0,0 +1,27 @@
1
+ /** Whether the given Bedrock model still accepts an explicit `temperature`. */
2
+ export declare function modelSupportsTemperature(modelId: string): boolean;
3
+ /**
4
+ * Build a Converse `inferenceConfig`, including `temperature` only when the
5
+ * target model accepts it. Callers pass their preferred temperature; for
6
+ * models that have deprecated the parameter it is silently omitted (the
7
+ * model's own default applies).
8
+ */
9
+ export declare function buildInferenceConfig(modelId: string, opts: {
10
+ maxTokens: number;
11
+ temperature?: number;
12
+ }): {
13
+ maxTokens: number;
14
+ temperature?: number;
15
+ };
16
+ /** Inference-profile prefix appropriate for the given (or current) AWS region. */
17
+ export declare function regionInferencePrefix(region?: string): string;
18
+ /**
19
+ * Re-home a cross-region inference-profile id to the active AWS region.
20
+ *
21
+ * `us.anthropic.claude-...` → `eu.anthropic.claude-...` when running in an
22
+ * EU region, etc. Ids that carry no regional prefix (bare model ids,
23
+ * `global.` profiles, non-Bedrock ids like `mock://` or `gpt-4o`) are
24
+ * returned unchanged.
25
+ */
26
+ export declare function resolveRegionAwareModelId(modelId: string, region?: string): string;
27
+ //# sourceMappingURL=bedrockCompat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bedrockCompat.d.ts","sourceRoot":"","sources":["../../bedrockCompat.ts"],"names":[],"mappings":"AAmCA,+EAA+E;AAC/E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAM7C;AAED,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAU7D;AAID;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CASlF"}
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Copyright OpenSearch Contributors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Bedrock model compatibility helpers shared by every Bedrock Converse call
7
+ * site (judge, skill eval generator, skill improver, failure clustering,
8
+ * assistant fallback).
9
+ *
10
+ * Two concerns live here so the policy is defined exactly once:
11
+ *
12
+ * 1. **Temperature deprecation** — newer Anthropic models (Claude Opus 4.5+,
13
+ * Sonnet 4.6+, and the Claude 5 family incl. Fable/Mythos) reject an
14
+ * explicit `temperature` with `ValidationException: 'temperature' is
15
+ * deprecated for this model`. `buildInferenceConfig()` omits the field for
16
+ * those models and lets the model use its default. (GitHub issue #299)
17
+ *
18
+ * 2. **Region-aware inference profiles** — the default model registry pins
19
+ * `us.`-prefixed cross-region inference profile ids, which only resolve
20
+ * from US regions. `resolveRegionAwareModelId()` swaps the prefix to the
21
+ * one matching the active AWS region (`eu.` / `apac.`) at call time, so
22
+ * the registry itself stays region-neutral. (GitHub issue #298)
23
+ */
24
+ /**
25
+ * Model-id patterns that have deprecated the `temperature` parameter.
26
+ * Matched case-insensitively against the full model/inference-profile id.
27
+ */
28
+ const TEMPERATURE_DEPRECATED_PATTERNS = [
29
+ /claude-opus-4-[5-9]/, // Opus 4.5, 4.6, 4.7, 4.8, ...
30
+ /claude-sonnet-4-[6-9]/, // Sonnet 4.6+ (4.5 still accepts temperature)
31
+ /claude-[a-z]+-5(?![0-9])/, // Claude 5 family: fable-5, mythos-5, opus-5, sonnet-5, ...
32
+ ];
33
+ /** Whether the given Bedrock model still accepts an explicit `temperature`. */
34
+ export function modelSupportsTemperature(modelId) {
35
+ const id = (modelId || '').toLowerCase();
36
+ return !TEMPERATURE_DEPRECATED_PATTERNS.some((p) => p.test(id));
37
+ }
38
+ /**
39
+ * Build a Converse `inferenceConfig`, including `temperature` only when the
40
+ * target model accepts it. Callers pass their preferred temperature; for
41
+ * models that have deprecated the parameter it is silently omitted (the
42
+ * model's own default applies).
43
+ */
44
+ export function buildInferenceConfig(modelId, opts) {
45
+ const cfg = { maxTokens: opts.maxTokens };
46
+ if (opts.temperature !== undefined && modelSupportsTemperature(modelId)) {
47
+ cfg.temperature = opts.temperature;
48
+ }
49
+ return cfg;
50
+ }
51
+ /** Inference-profile prefix appropriate for the given (or current) AWS region. */
52
+ export function regionInferencePrefix(region) {
53
+ const r = (region ||
54
+ process.env.AWS_REGION ||
55
+ process.env.AWS_DEFAULT_REGION ||
56
+ 'us-east-1').toLowerCase();
57
+ if (r.startsWith('eu-'))
58
+ return 'eu.';
59
+ if (r.startsWith('ap-'))
60
+ return 'apac.';
61
+ return 'us.';
62
+ }
63
+ const CROSS_REGION_PREFIXES = ['us.', 'eu.', 'apac.'];
64
+ /**
65
+ * Re-home a cross-region inference-profile id to the active AWS region.
66
+ *
67
+ * `us.anthropic.claude-...` → `eu.anthropic.claude-...` when running in an
68
+ * EU region, etc. Ids that carry no regional prefix (bare model ids,
69
+ * `global.` profiles, non-Bedrock ids like `mock://` or `gpt-4o`) are
70
+ * returned unchanged.
71
+ */
72
+ export function resolveRegionAwareModelId(modelId, region) {
73
+ if (!modelId)
74
+ return modelId;
75
+ const target = regionInferencePrefix(region);
76
+ for (const prefix of CROSS_REGION_PREFIXES) {
77
+ if (modelId.startsWith(prefix)) {
78
+ return prefix === target ? modelId : target + modelId.slice(prefix.length);
79
+ }
80
+ }
81
+ return modelId;
82
+ }
83
+ //# sourceMappingURL=bedrockCompat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bedrockCompat.js","sourceRoot":"","sources":["../../bedrockCompat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,MAAM,+BAA+B,GAAa;IAChD,qBAAqB,EAAE,+BAA+B;IACtD,uBAAuB,EAAE,8CAA8C;IACvE,0BAA0B,EAAE,4DAA4D;CACzF,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,OAAO,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,IAAiD;IAEjD,MAAM,GAAG,GAAgD,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IACvF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;QACxE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,qBAAqB,CAAC,MAAe;IACnD,MAAM,CAAC,GAAG,CACR,MAAM;QACN,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC9B,WAAW,CACZ,CAAC,WAAW,EAAE,CAAC;IAChB,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;AAE/D;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAe,EAAE,MAAe;IACxE,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC;IAC7B,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,MAAM,MAAM,IAAI,qBAAqB,EAAE,CAAC;QAC3C,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { BenchmarkImage, TestCase } from '../types/index.js';
2
+ /** Eval conditions frozen into an image digest. */
3
+ export interface ImageEvalConditions {
4
+ evaluatorId?: string;
5
+ judgeModelId?: string;
6
+ }
7
+ /** Bump when the digest input shape changes (invalidates old digests). */
8
+ export declare const IMAGE_DIGEST_VERSION = 1;
9
+ /**
10
+ * Deterministic JSON: objects serialized with sorted keys, recursively.
11
+ * Arrays keep their order (order-sensitivity is decided by the caller).
12
+ */
13
+ export declare function canonicalStringify(value: unknown): string;
14
+ /**
15
+ * Content fingerprint of a single test case. Uses CONTENT only — never the
16
+ * storage id — so re-imported test cases (fresh ids, same content) fingerprint
17
+ * identically. Fields chosen to match what actually changes agent behavior
18
+ * and judging: prompt, context, expected outcomes/trajectory, tools.
19
+ */
20
+ export declare function fingerprintTestCase(tc: Partial<TestCase> & {
21
+ name: string;
22
+ }): {
23
+ name: string;
24
+ contentHash: string;
25
+ };
26
+ /**
27
+ * Compute the image digest for a set of test cases + eval conditions.
28
+ * Order-insensitive over test cases (fingerprints are sorted), content- and
29
+ * condition-sensitive.
30
+ */
31
+ export declare function computeImageDigest(input: {
32
+ testCases: Array<Partial<TestCase> & {
33
+ name: string;
34
+ }>;
35
+ evalConditions?: ImageEvalConditions;
36
+ }): string;
37
+ /** Storage id for an image doc (content-addressed — one doc per digest). */
38
+ export declare function imageIdForDigest(digest: string): string;
39
+ /**
40
+ * Build a full image doc for storage. `create` on the storage side is
41
+ * find-or-create keyed on the digest, so calling this repeatedly for the
42
+ * same inputs is harmless.
43
+ */
44
+ export declare function buildImageDoc(input: {
45
+ testCases: Array<Partial<TestCase> & {
46
+ id?: string;
47
+ name: string;
48
+ }>;
49
+ evalConditions?: ImageEvalConditions;
50
+ tags?: string[];
51
+ }): BenchmarkImage;
52
+ //# sourceMappingURL=benchmarkImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmarkImage.d.ts","sourceRoot":"","sources":["../../benchmarkImage.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExD,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEzD;AAmBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAYvC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,MAAM,CAaT;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,cAAc,CAkBjB"}
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright OpenSearch Contributors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Benchmark images — content-addressed identity for evaluation conditions.
7
+ *
8
+ * An image freezes the "controls" of an evaluation run: the test-case
9
+ * contents and the eval conditions (evaluator, judge model). The digest is a
10
+ * SHA-256 over a canonical serialization, so:
11
+ *
12
+ * - two runs with the same digest are comparable **by construction**
13
+ * (same tests, same judge conditions — only the agent varies);
14
+ * - re-running the same command yields the same digest → the image is
15
+ * found, not re-created (inherent dedup: identity is content, not name).
16
+ *
17
+ * Names are docker-style *tags*: mutable labels pointing at a digest, never
18
+ * identity. See the benchmark-dedup design discussion for rationale.
19
+ */
20
+ import { createHash } from 'crypto';
21
+ /** Bump when the digest input shape changes (invalidates old digests). */
22
+ export const IMAGE_DIGEST_VERSION = 1;
23
+ /**
24
+ * Deterministic JSON: objects serialized with sorted keys, recursively.
25
+ * Arrays keep their order (order-sensitivity is decided by the caller).
26
+ */
27
+ export function canonicalStringify(value) {
28
+ return JSON.stringify(sortValue(value));
29
+ }
30
+ function sortValue(value) {
31
+ if (Array.isArray(value))
32
+ return value.map(sortValue);
33
+ if (value && typeof value === 'object') {
34
+ const out = {};
35
+ for (const key of Object.keys(value).sort()) {
36
+ const v = value[key];
37
+ if (v !== undefined)
38
+ out[key] = sortValue(v);
39
+ }
40
+ return out;
41
+ }
42
+ return value;
43
+ }
44
+ function sha256Hex(input) {
45
+ return createHash('sha256').update(input, 'utf8').digest('hex');
46
+ }
47
+ /**
48
+ * Content fingerprint of a single test case. Uses CONTENT only — never the
49
+ * storage id — so re-imported test cases (fresh ids, same content) fingerprint
50
+ * identically. Fields chosen to match what actually changes agent behavior
51
+ * and judging: prompt, context, expected outcomes/trajectory, tools.
52
+ */
53
+ export function fingerprintTestCase(tc) {
54
+ const content = {
55
+ name: tc.name,
56
+ initialPrompt: tc.initialPrompt ?? null,
57
+ context: tc.context ?? null,
58
+ tools: tc.tools ?? null,
59
+ expectedOutcomes: tc.expectedOutcomes ?? null,
60
+ expectedTrajectory: tc.expectedTrajectory ?? null,
61
+ // Code-imported cases: sourceHash already fingerprints the executable body.
62
+ sourceHash: tc.sourceHash ?? null,
63
+ };
64
+ return { name: tc.name, contentHash: sha256Hex(canonicalStringify(content)) };
65
+ }
66
+ /**
67
+ * Compute the image digest for a set of test cases + eval conditions.
68
+ * Order-insensitive over test cases (fingerprints are sorted), content- and
69
+ * condition-sensitive.
70
+ */
71
+ export function computeImageDigest(input) {
72
+ const fingerprints = input.testCases
73
+ .map(fingerprintTestCase)
74
+ .sort((a, b) => a.contentHash.localeCompare(b.contentHash));
75
+ const payload = {
76
+ v: IMAGE_DIGEST_VERSION,
77
+ testCases: fingerprints.map((f) => f.contentHash),
78
+ evalConditions: {
79
+ evaluatorId: input.evalConditions?.evaluatorId ?? null,
80
+ judgeModelId: input.evalConditions?.judgeModelId ?? null,
81
+ },
82
+ };
83
+ return sha256Hex(canonicalStringify(payload));
84
+ }
85
+ /** Storage id for an image doc (content-addressed — one doc per digest). */
86
+ export function imageIdForDigest(digest) {
87
+ return `img-${digest}`;
88
+ }
89
+ /**
90
+ * Build a full image doc for storage. `create` on the storage side is
91
+ * find-or-create keyed on the digest, so calling this repeatedly for the
92
+ * same inputs is harmless.
93
+ */
94
+ export function buildImageDoc(input) {
95
+ const digest = computeImageDigest(input);
96
+ const fingerprints = input.testCases
97
+ .map((tc) => ({ ...fingerprintTestCase(tc), id: tc.id }))
98
+ .sort((a, b) => a.contentHash.localeCompare(b.contentHash));
99
+ return {
100
+ id: imageIdForDigest(digest),
101
+ docType: 'benchmark-image',
102
+ digest,
103
+ tags: input.tags ?? [],
104
+ testCaseFingerprints: fingerprints,
105
+ testCaseCount: fingerprints.length,
106
+ evalConditions: {
107
+ ...(input.evalConditions?.evaluatorId ? { evaluatorId: input.evalConditions.evaluatorId } : {}),
108
+ ...(input.evalConditions?.judgeModelId ? { judgeModelId: input.evalConditions.judgeModelId } : {}),
109
+ },
110
+ createdAt: new Date().toISOString(),
111
+ };
112
+ }
113
+ //# sourceMappingURL=benchmarkImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmarkImage.js","sourceRoot":"","sources":["../../benchmarkImage.ts"],"names":[],"mappings":"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"}
@@ -34,4 +34,17 @@ export declare function getVersionTestCases(testCases: TestCase[], selectedVersi
34
34
  * @param versionFilter - Version number to filter by, or 'all' for all runs
35
35
  */
36
36
  export declare function filterRunsByVersion(runs: BenchmarkRun[] | undefined, versionFilter: number | 'all'): BenchmarkRun[];
37
+ /**
38
+ * Resolve the effective run-version filter from a persisted (possibly stale)
39
+ * value. A persisted version the benchmark doesn't actually have — e.g. a
40
+ * filter set while viewing a different benchmark under the old global
41
+ * localStorage key, a deleted version, or corrupted storage — must behave as
42
+ * 'all' rather than filtering every run out and rendering a bogus
43
+ * "No runs for vN" empty state that looks like data loss.
44
+ *
45
+ * @param raw - persisted filter value
46
+ * @param availableVersions - the benchmark's actual version numbers
47
+ * (undefined while the benchmark is still loading — value passes through)
48
+ */
49
+ export declare function effectiveRunVersionFilter(raw: number | 'all', availableVersions: number[] | undefined): number | 'all';
37
50
  //# sourceMappingURL=benchmarkVersionUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"benchmarkVersionUtils.d.ts","sourceRoot":"","sources":["../../benchmarkVersionUtils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAoB,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,WAAW,EAAE,CA0B7E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EAAE,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,WAAW,GAAG,IAAI,CAIpB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,QAAQ,EAAE,EACrB,mBAAmB,EAAE,WAAW,GAAG,IAAI,GACtC,QAAQ,EAAE,CAKZ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAAE,GAAG,SAAS,EAChC,aAAa,EAAE,MAAM,GAAG,KAAK,GAC5B,YAAY,EAAE,CAShB"}
1
+ {"version":3,"file":"benchmarkVersionUtils.d.ts","sourceRoot":"","sources":["../../benchmarkVersionUtils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAoB,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,WAAW,EAAE,CA0B7E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EAAE,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,WAAW,GAAG,IAAI,CAIpB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,QAAQ,EAAE,EACrB,mBAAmB,EAAE,WAAW,GAAG,IAAI,GACtC,QAAQ,EAAE,CAKZ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAAE,GAAG,SAAS,EAChC,aAAa,EAAE,MAAM,GAAG,KAAK,GAC5B,YAAY,EAAE,CAShB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,GAAG,KAAK,EACnB,iBAAiB,EAAE,MAAM,EAAE,GAAG,SAAS,GACtC,MAAM,GAAG,KAAK,CAKhB"}
@@ -65,4 +65,25 @@ export function filterRunsByVersion(runs, versionFilter) {
65
65
  return sorted;
66
66
  return sorted.filter(run => (run.benchmarkVersion || 1) === versionFilter);
67
67
  }
68
+ /**
69
+ * Resolve the effective run-version filter from a persisted (possibly stale)
70
+ * value. A persisted version the benchmark doesn't actually have — e.g. a
71
+ * filter set while viewing a different benchmark under the old global
72
+ * localStorage key, a deleted version, or corrupted storage — must behave as
73
+ * 'all' rather than filtering every run out and rendering a bogus
74
+ * "No runs for vN" empty state that looks like data loss.
75
+ *
76
+ * @param raw - persisted filter value
77
+ * @param availableVersions - the benchmark's actual version numbers
78
+ * (undefined while the benchmark is still loading — value passes through)
79
+ */
80
+ export function effectiveRunVersionFilter(raw, availableVersions) {
81
+ if (raw === 'all')
82
+ return 'all';
83
+ if (!Number.isInteger(raw) || raw < 1)
84
+ return 'all';
85
+ if (availableVersions !== undefined && !availableVersions.includes(raw))
86
+ return 'all';
87
+ return raw;
88
+ }
68
89
  //# sourceMappingURL=benchmarkVersionUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"benchmarkVersionUtils.js","sourceRoot":"","sources":["../../benchmarkVersionUtils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"benchmarkVersionUtils.js","sourceRoot":"","sources":["../../benchmarkVersionUtils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shared "fetch a big id list without blowing up the request" policy.
3
+ *
4
+ * A comparison over a large multi-run pool can need results for hundreds of
5
+ * ids (report ids, test-case ids, …) in one go. Two failure modes to avoid:
6
+ * - one unbounded `?ids=<all>` GET can blow past practical URL/header size
7
+ * limits (HTTP 431 — see asyncRunStorage.getReportsByIds's history);
8
+ * - firing one request per chunk with no cap can stampede the backend.
9
+ *
10
+ * `fetchChunked` splits `ids` into bounded chunks and fans them out with a
11
+ * capped worker pool — never one unbounded request, never an unbounded burst
12
+ * of parallel ones either. Used by asyncRunStorage (report ids) and
13
+ * asyncTestCaseStorage (test-case ids).
14
+ */
15
+ export declare const DEFAULT_CHUNK_SIZE = 100;
16
+ export declare const DEFAULT_MAX_CONCURRENT_CHUNKS = 8;
17
+ export declare function fetchChunked<T>(ids: string[], chunkSize: number, fetchChunk: (chunk: string[]) => Promise<T[]>, maxConcurrentChunks?: number): Promise<T[]>;
18
+ //# sourceMappingURL=chunkedFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkedFetch.d.ts","sourceRoot":"","sources":["../../chunkedFetch.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,wBAAsB,YAAY,CAAC,CAAC,EAClC,GAAG,EAAE,MAAM,EAAE,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,EAC7C,mBAAmB,GAAE,MAAsC,GAC1D,OAAO,CAAC,CAAC,EAAE,CAAC,CAiBd"}
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright OpenSearch Contributors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Shared "fetch a big id list without blowing up the request" policy.
7
+ *
8
+ * A comparison over a large multi-run pool can need results for hundreds of
9
+ * ids (report ids, test-case ids, …) in one go. Two failure modes to avoid:
10
+ * - one unbounded `?ids=<all>` GET can blow past practical URL/header size
11
+ * limits (HTTP 431 — see asyncRunStorage.getReportsByIds's history);
12
+ * - firing one request per chunk with no cap can stampede the backend.
13
+ *
14
+ * `fetchChunked` splits `ids` into bounded chunks and fans them out with a
15
+ * capped worker pool — never one unbounded request, never an unbounded burst
16
+ * of parallel ones either. Used by asyncRunStorage (report ids) and
17
+ * asyncTestCaseStorage (test-case ids).
18
+ */
19
+ export const DEFAULT_CHUNK_SIZE = 100;
20
+ export const DEFAULT_MAX_CONCURRENT_CHUNKS = 8;
21
+ export async function fetchChunked(ids, chunkSize, fetchChunk, maxConcurrentChunks = DEFAULT_MAX_CONCURRENT_CHUNKS) {
22
+ const chunks = [];
23
+ for (let i = 0; i < ids.length; i += chunkSize) {
24
+ chunks.push(ids.slice(i, i + chunkSize));
25
+ }
26
+ const results = new Array(chunks.length);
27
+ let nextChunk = 0;
28
+ async function worker() {
29
+ while (true) {
30
+ const i = nextChunk++;
31
+ if (i >= chunks.length)
32
+ return;
33
+ results[i] = await fetchChunk(chunks[i]);
34
+ }
35
+ }
36
+ const workerCount = Math.min(maxConcurrentChunks, chunks.length);
37
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
38
+ return results.flat();
39
+ }
40
+ //# sourceMappingURL=chunkedFetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkedFetch.js","sourceRoot":"","sources":["../../chunkedFetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAa,EACb,SAAiB,EACjB,UAA6C,EAC7C,sBAA8B,6BAA6B;IAE3D,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,OAAO,GAAU,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,UAAU,MAAM;QACnB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM;gBAAE,OAAO;YAC/B,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Comparison insights — deterministic agreement + category math for the
3
+ * comparison page's insights band (and, later, as grounding context for the
4
+ * "What's actually different" LLM deep-dive).
5
+ *
6
+ * Everything here is pure arithmetic over the already-loaded comparison rows:
7
+ * no API calls, no LLM. The band renders the *what* (which runs agree where,
8
+ * which categories are weak); the deep-dive explains the *why*.
9
+ *
10
+ * Generalized to N runs from the start:
11
+ * - 2 runs → "Both pass / Both fail / Split"
12
+ * - 3+ runs → "All pass / All fail / Split"
13
+ */
14
+ import type { TestCaseComparisonRow } from '../types/index.js';
15
+ /** Agreement bucket for a row across the selected runs. */
16
+ export type AgreementBucket = 'allPass' | 'allFail' | 'split';
17
+ export interface AgreementPartition {
18
+ allPass: TestCaseComparisonRow[];
19
+ allFail: TestCaseComparisonRow[];
20
+ split: TestCaseComparisonRow[];
21
+ /** Rows where at least one run has no verdict (missing report / not run). */
22
+ uncovered: TestCaseComparisonRow[];
23
+ }
24
+ /**
25
+ * Partition rows into agreement buckets across the given runs.
26
+ * A row only participates when EVERY selected run has a verdict for it —
27
+ * partially-covered rows go to `uncovered` (they can't agree or disagree).
28
+ */
29
+ export declare function partitionByAgreement(rows: TestCaseComparisonRow[], runIds: string[]): AgreementPartition;
30
+ /** Bucket a single row (same semantics as {@link partitionByAgreement}); null = uncovered. */
31
+ export declare function bucketRow(row: TestCaseComparisonRow, runIds: string[]): AgreementBucket | null;
32
+ /**
33
+ * Synthetic bucket names. Parentheses are deliberately outside the
34
+ * name-tag charset (`[\w-]`), so a real benchmark category can never
35
+ * collide with these rollup buckets.
36
+ */
37
+ export declare const UNCATEGORIZED = "(uncategorized)";
38
+ /** Rollup bucket for categories with too few cases to be meaningful. */
39
+ export declare const OTHER_CATEGORY = "(other)";
40
+ /**
41
+ * Extract a row's category.
42
+ * Priority:
43
+ * 1. The `subcategory:<x>` label — the proper, purpose-built tag for this
44
+ * (set via the SDK's `test(name, { labels: ['subcategory:basic'] })`,
45
+ * the JSON/CLI import's `subcategory` field, or the Test Case editor).
46
+ * Prefer this over anything scraped from free text: it's validated,
47
+ * exported/round-tripped, and shown elsewhere in the UI (EvalsPage,
48
+ * BenchmarkEditor) — a real column, not a regex guess.
49
+ * 2. A bracketed tag embedded in the test-case name — a convention older
50
+ * imported benchmarks used before `subcategory` existed (e.g.
51
+ * "qst_0011 [basic] How long is …"). Kept only for benchmarks that
52
+ * predate the `subcategory` field; new imports should set it instead.
53
+ * 3. A `topic:<x>` label (the generic labels system, pre-dates
54
+ * `subcategory` as a dedicated concept).
55
+ * 4. {@link UNCATEGORIZED}.
56
+ * (`category:<x>` labels are intentionally NOT used here: imported benchmarks
57
+ * stamp a single `category:RAG` on every case, which would collapse the
58
+ * breakdown into one column — that's the coarse domain, not this facet.)
59
+ */
60
+ export declare function extractRowCategory(row: Pick<TestCaseComparisonRow, 'testCaseName' | 'labels'>): string;
61
+ export interface CategoryCell {
62
+ passed: number;
63
+ total: number;
64
+ }
65
+ export interface CategoryBreakdown {
66
+ /** Display order: by case count desc; `other` (if present) always last. */
67
+ categories: string[];
68
+ /** runId → category → {passed,total}. Totals may differ per run (missing results are skipped). */
69
+ perRun: Record<string, Record<string, CategoryCell>>;
70
+ /** Overall case count per category (across rows, not per run). */
71
+ totals: Record<string, number>;
72
+ /**
73
+ * Raw categories each displayed column represents. Identity for real
74
+ * categories; the union of rolled-up raw categories for `(other)`.
75
+ * Filtering MUST use this mapping so clicking `(other)` matches exactly
76
+ * the rows its cell counted.
77
+ */
78
+ members: Record<string, string[]>;
79
+ }
80
+ /**
81
+ * Default minimum case count for a category to get its own column; smaller
82
+ * ones roll up into `other`. Callers with small datasets may lower this —
83
+ * a cell needs enough cases that a single verdict flip doesn't swing it
84
+ * by tens of percentage points.
85
+ */
86
+ export declare const MIN_CATEGORY_CASES = 5;
87
+ /** Per-category pass rates per run, with small categories rolled into `other`. */
88
+ export declare function buildCategoryBreakdown(rows: TestCaseComparisonRow[], runIds: string[], minCases?: number): CategoryBreakdown;
89
+ export interface SharedWeakness {
90
+ category: string;
91
+ /** runId → pass-rate percent (0-100) in this category. */
92
+ rates: Record<string, number>;
93
+ /** Fraction (0-1) of all-fail cases that belong to this category. */
94
+ allFailShare: number;
95
+ }
96
+ /**
97
+ * Detect a category that is the weakest (within {@link WEAKEST_TOLERANCE_PP})
98
+ * for EVERY selected run — the "shared floor". When present, it usually
99
+ * indicates a benchmark/corpus-level problem rather than an agent choice.
100
+ * Returns null when runs disagree about their weakest category or nothing is
101
+ * genuinely weak. `other`/`uncategorized` rollups are never flagged.
102
+ */
103
+ export declare function detectSharedWeakness(breakdown: CategoryBreakdown, partition: AgreementPartition, runIds: string[]): SharedWeakness | null;
104
+ //# sourceMappingURL=comparisonInsights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comparisonInsights.d.ts","sourceRoot":"","sources":["../../comparisonInsights.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGrD,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC/B,6EAA6E;IAC7E,SAAS,EAAE,qBAAqB,EAAE,CAAC;CACpC;AAyBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,qBAAqB,EAAE,EAC7B,MAAM,EAAE,MAAM,EAAE,GACf,kBAAkB,CAgBpB;AAED,8FAA8F;AAC9F,wBAAgB,SAAS,CAAC,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,GAAG,IAAI,CAO9F;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAC/C,wEAAwE;AACxE,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,QAAQ,CAAC,GAAG,MAAM,CAQtG;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACrD,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,kFAAkF;AAClF,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,qBAAqB,EAAE,EAC7B,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,GAAE,MAA2B,GACpC,iBAAiB,CAuCnB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;CACtB;AAYD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,kBAAkB,EAC7B,MAAM,EAAE,MAAM,EAAE,GACf,cAAc,GAAG,IAAI,CAuCvB"}