@arclabs561/ai-visual-test 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to ai-visual-test will be documented in this file.
4
4
 
5
+ ## [0.7.5] - 2026-03-16
6
+
7
+ ### Fixed
8
+ - **Test image generator**: replaced 1x1 pixel PNG stub with programmatic 100x100 gradient PNG using raw PNG chunk construction (zlib deflate). Fixes Groq API rejections ("Image must have at least 2 pixels") in integration tests.
9
+ - **BatchOptimizer queue-full test**: matched error type to actual `ValidationError` thrown by `_queueRequest` (was expecting `TimeoutError`).
10
+ - **Deleted stale test**: removed `validation-framework.test.mjs` referencing deleted `src/validation-framework.mjs` module.
11
+
12
+ ## [0.7.4] - 2026-03-03
13
+
14
+ ### Added
15
+ - **Structured result fields at top level** - `result.richIssues`, `result.recommendations`, `result.strengths` promoted from `result.semantic` to the top-level result object, eliminating the need to reach into `result.semantic` for structured output.
16
+ - `richIssues`: array of `{ description, importance, annoyance, impact, evidence, suggestion }` objects
17
+ - `recommendations`: array of `{ priority, suggestion, expectedImpact }` objects
18
+ - `strengths`: array of strings describing what works well
19
+ - **TypeScript types** for `RichIssue`, `Recommendation`; updated `SemanticInfo` and `ValidationResult` interfaces.
20
+
21
+ ### Fixed
22
+ - `result.issues` (flat strings) is preserved for backward compatibility; `result.richIssues` adds the structured version alongside it.
23
+
24
+ ## [0.7.3] - 2026-03-02
25
+
26
+ ### Added
27
+ - **Visual anchors** - domain-level grounding cues (text + image) injected into VLM prompts. Supports `AnchorEntry` union type: plain strings, dimension-scoped text, image references, or combinations. Config-level anchors merge with per-call `context.anchors`.
28
+ - **Dimension-scoped anchors** - tag anchors with rubric dimension names for targeted evaluation.
29
+ - **Image anchor resolution** - file paths, data URIs, and raw base64 supported for reference screenshots.
30
+
31
+ ### Fixed
32
+ - Prompt composer: proper `\n\n` separation between anchor section and base prompt.
33
+ - Judge: always warn on missing anchor images (not just verbose mode).
34
+ - Build script: strip `scripts` and `devDependencies` from dist `package.json`.
35
+ - Publish workflow: run only unit tests in CI; audit prod deps only.
36
+
5
37
  ## [0.6.0] - 2025-01-17
6
38
 
7
39
  ### Changed
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # ai-visual-test
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@arclabs561/ai-visual-test)](https://www.npmjs.com/package/@arclabs561/ai-visual-test)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
3
6
  Visual testing framework using Vision Language Models. Validates screenshots, checks accessibility, and can play games.
4
7
 
5
8
  ## Why This Package
package/index.d.ts CHANGED
@@ -341,11 +341,41 @@ export interface EstimatedCost {
341
341
  currency: string;
342
342
  }
343
343
 
344
+ /** A structured issue with metadata (importance, evidence, suggestion). */
345
+ export interface RichIssue {
346
+ /** Human-readable issue description */
347
+ description: string;
348
+ /** Importance level */
349
+ importance?: 'low' | 'medium' | 'high' | 'critical';
350
+ /** Annoyance level */
351
+ annoyance?: 'low' | 'medium' | 'high';
352
+ /** Impact category */
353
+ impact?: string;
354
+ /** Evidence observed in the screenshot */
355
+ evidence?: string;
356
+ /** Suggested fix */
357
+ suggestion?: string;
358
+ }
359
+
360
+ /** A structured recommendation with priority and expected impact. */
361
+ export interface Recommendation {
362
+ /** Priority level */
363
+ priority?: 'low' | 'medium' | 'high';
364
+ /** What to change */
365
+ suggestion: string;
366
+ /** Expected improvement from the change */
367
+ expectedImpact?: string;
368
+ }
369
+
344
370
  export interface SemanticInfo {
345
371
  score: number | null;
346
- issues: string[];
372
+ issues: RichIssue[];
347
373
  assessment: string | null;
348
- reasoning: string;
374
+ reasoning: string | null;
375
+ strengths?: string[];
376
+ recommendations?: Recommendation[];
377
+ evidence?: string | Record<string, unknown> | null;
378
+ dimensionScores?: Record<string, number> | null;
349
379
  brutalistViolations?: string[];
350
380
  zeroToleranceViolations?: string[];
351
381
  }
@@ -378,12 +408,20 @@ export interface ValidationResult {
378
408
  provider: string;
379
409
  /** Quality score (0-10, null if validation failed) */
380
410
  score: number | null;
381
- /** List of issues found */
411
+ /** List of issues found (flat strings for backward compat) */
382
412
  issues: string[];
413
+ /** Structured issues with importance, evidence, and suggestions */
414
+ richIssues?: RichIssue[];
383
415
  /** Overall assessment (e.g., 'Good', 'Needs Improvement') */
384
416
  assessment: string | null;
385
417
  /** Detailed reasoning for the score */
386
418
  reasoning: string;
419
+ /** Actionable recommendations with priority and expected impact */
420
+ recommendations?: Recommendation[];
421
+ /** What the UI does well */
422
+ strengths?: string[];
423
+ /** Per-dimension scores (e.g., game_authenticity: 9, typography: 7) */
424
+ dimensionScores?: Record<string, number> | null;
387
425
  /** Estimated API cost breakdown */
388
426
  estimatedCost?: EstimatedCost | null;
389
427
  /** Response time in milliseconds */
@@ -2078,3 +2116,143 @@ export function validateWithProgrammaticContext(
2078
2116
  options?: ValidationContext
2079
2117
  ): Promise<HybridValidationResult>;
2080
2118
 
2119
+ // --- Score Calibration (arXiv:2601.05114) ---
2120
+
2121
+ export interface CalibrationProfile {
2122
+ offset: number;
2123
+ scale: number;
2124
+ }
2125
+
2126
+ export interface DerivedCalibrationProfile extends CalibrationProfile {
2127
+ r2: number;
2128
+ }
2129
+
2130
+ export interface ScoreDistribution {
2131
+ mean: number;
2132
+ stddev: number;
2133
+ skew: number;
2134
+ histogram: Record<number, number>;
2135
+ }
2136
+
2137
+ export function calibrateScore(score: number | null, provider: string): number | null;
2138
+ export function setCalibrationProfile(provider: string, profile: CalibrationProfile): void;
2139
+ export function getCalibrationProfile(provider: string): CalibrationProfile;
2140
+ export function resetCalibrationProfiles(): void;
2141
+ export function deriveCalibrationProfile(pairs: Array<{ raw: number; expected: number }>): DerivedCalibrationProfile;
2142
+ export function analyzeScoreDistribution(scores: number[]): ScoreDistribution;
2143
+
2144
+ // --- Calibration Suite / Meta-evaluation (arXiv:2507.10062) ---
2145
+
2146
+ export interface CalibrationSample {
2147
+ screenshot: string;
2148
+ expectedScore: number;
2149
+ label?: string;
2150
+ prompt?: string;
2151
+ }
2152
+
2153
+ export interface CalibrationReport {
2154
+ sampleCount: number;
2155
+ scoredCount: number;
2156
+ pearsonR: number | null;
2157
+ spearmanR: number | null;
2158
+ meanAbsoluteError: number | null;
2159
+ maxError: number | null;
2160
+ falsePositiveRate: number | null;
2161
+ falseNegativeRate: number | null;
2162
+ suggestedCalibration: DerivedCalibrationProfile | null;
2163
+ scoreDistribution: ScoreDistribution;
2164
+ details: Array<{
2165
+ screenshot: string;
2166
+ label: string;
2167
+ expected: number;
2168
+ actual: number | null;
2169
+ error: number | null;
2170
+ skipped?: string;
2171
+ }>;
2172
+ error?: string;
2173
+ }
2174
+
2175
+ export interface CalibrationSuiteInstance {
2176
+ samples: CalibrationSample[];
2177
+ run(judgeOptions?: ConfigOptions): Promise<CalibrationReport>;
2178
+ }
2179
+
2180
+ export function createCalibrationSuite(
2181
+ samples: CalibrationSample[],
2182
+ options?: { passThreshold?: number }
2183
+ ): CalibrationSuiteInstance;
2184
+
2185
+ // --- Known VLM Limitations (arXiv:2501.09236, arXiv:2511.03471) ---
2186
+
2187
+ export interface VLMLimitation {
2188
+ description: string;
2189
+ severity: 'high' | 'medium' | 'low';
2190
+ recommendation: string;
2191
+ vlmAccuracy: 'none' | 'low' | 'medium' | 'high';
2192
+ }
2193
+
2194
+ export type VLMLimitationKey =
2195
+ | 'subtleSpatialShifts'
2196
+ | 'elementOverlap'
2197
+ | 'keyboardNavigation'
2198
+ | 'screenReaderOrder'
2199
+ | 'colorContrastPrecision'
2200
+ | 'dynamicContent'
2201
+ | 'textContent'
2202
+ | 'interactiveState';
2203
+
2204
+ export type TestType = 'accessibility' | 'layout' | 'visual' | 'interaction' | 'general';
2205
+
2206
+ export const VLM_LIMITATIONS: Record<VLMLimitationKey, VLMLimitation>;
2207
+
2208
+ export function getLimitationsForTestType(
2209
+ testType: TestType
2210
+ ): Array<{ key: string } & VLMLimitation>;
2211
+
2212
+ export function shouldUseHybridValidation(testType: TestType): boolean;
2213
+
2214
+ // --- Human Validation Manager ---
2215
+
2216
+ export interface HumanValidationManagerOptions {
2217
+ enabled?: boolean;
2218
+ autoCollect?: boolean;
2219
+ smartSampling?: boolean;
2220
+ calibrationThreshold?: number;
2221
+ humanValidatorFn?: ((vllmResult: ValidationResult) => Promise<unknown>) | null;
2222
+ }
2223
+
2224
+ export class HumanValidationManager {
2225
+ constructor(options?: HumanValidationManagerOptions);
2226
+ enabled: boolean;
2227
+ collectJudgment(result: ValidationResult): void;
2228
+ requestValidation(result: ValidationResult): Promise<unknown>;
2229
+ getCalibrationData(): Promise<unknown>;
2230
+ isCalibrated(): boolean;
2231
+ }
2232
+
2233
+ export function getHumanValidationManager(): HumanValidationManager;
2234
+ export function initHumanValidation(options?: HumanValidationManagerOptions): HumanValidationManager;
2235
+
2236
+ // --- Explanation Manager ---
2237
+
2238
+ export class ExplanationManager {
2239
+ constructor(options?: ConfigOptions);
2240
+ explainJudgment(vllmJudgment: object, question?: string | null, options?: object): Promise<object>;
2241
+ }
2242
+
2243
+ export function getExplanationManager(options?: ConfigOptions): ExplanationManager;
2244
+
2245
+ // --- Temporal Batch Optimizer ---
2246
+
2247
+ export class TemporalBatchOptimizer extends BatchOptimizer {
2248
+ constructor(options?: object);
2249
+ addWithDependencies(request: object, dependencies?: string[]): Promise<ValidationResult>;
2250
+ }
2251
+
2252
+ // --- Latency-Aware Batch Optimizer ---
2253
+
2254
+ export class LatencyAwareBatchOptimizer extends BatchOptimizer {
2255
+ constructor(options?: { maxConcurrency?: number; batchSize?: number; cacheEnabled?: boolean; defaultMaxLatency?: number; adaptiveBatchSize?: boolean });
2256
+ addWithLatencyTarget(request: object, maxLatencyMs?: number): Promise<ValidationResult>;
2257
+ }
2258
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arclabs561/ai-visual-test",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Visual testing framework for web applications using Vision Language Models",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -39,11 +39,7 @@
39
39
  "author": "arclabs561 <henry@henrywallace.io>",
40
40
  "license": "MIT",
41
41
  "dependencies": {
42
- "@anthropic-ai/sdk": "0.70.0",
43
- "@google/generative-ai": "0.24.1",
44
- "async-mutex": "0.5.0",
45
- "dotenv": "^16.4.5",
46
- "openai": "6.9.1"
42
+ "async-mutex": "0.5.0"
47
43
  },
48
44
  "peerDependencies": {
49
45
  "@arclabs561/llm-utils": "*",
@@ -29,7 +29,7 @@ import { createHash } from 'crypto';
29
29
  * @class BatchOptimizer
30
30
  */
31
31
  import { API_CONSTANTS, BATCH_OPTIMIZER_CONSTANTS } from './constants.mjs';
32
- import { TimeoutError } from './errors.mjs';
32
+ import { TimeoutError, ValidationError } from './errors.mjs';
33
33
  import { warn } from './logger.mjs';
34
34
 
35
35
  export class BatchOptimizer {
@@ -103,7 +103,7 @@ export class BatchOptimizer {
103
103
  const keyData = {
104
104
  imagePath,
105
105
  prompt: prompt || '',
106
- context: context ? JSON.stringify(context) : ''
106
+ context: context ? JSON.stringify(context, Object.keys(context).sort()) : ''
107
107
  };
108
108
  const keyString = JSON.stringify(keyData);
109
109
  return createHash('sha256').update(keyString).digest('hex');
@@ -238,7 +238,7 @@ export class BatchOptimizer {
238
238
  });
239
239
 
240
240
  warn(`[BatchOptimizer] Queue is full (${this.queue.length}/${this.maxQueueSize}). Rejecting request to prevent memory leak. Total rejections: ${this.metrics.queueRejections}`);
241
- throw new TimeoutError(
241
+ throw new ValidationError(
242
242
  `Queue is full (${this.queue.length}/${this.maxQueueSize}). Too many concurrent requests.`,
243
243
  { queueSize: this.queue.length, maxQueueSize: this.maxQueueSize }
244
244
  );
package/src/cache.mjs CHANGED
@@ -55,12 +55,11 @@ let cacheMetrics = { atomicWrites: 0, atomicWriteFailures: 0, tempFileCleanups:
55
55
  export function initCache(cacheDir) {
56
56
  // SECURITY: Validate and normalize cache directory to prevent path traversal
57
57
  if (cacheDir) {
58
- const normalized = normalize(resolve(cacheDir));
59
- // Prevent path traversal
60
- if (normalized.includes('..')) {
58
+ // Prevent path traversal: reject raw input containing '..' segments
59
+ if (cacheDir.includes('..')) {
61
60
  throw new CacheError('Invalid cache directory: path traversal detected', { cacheDir });
62
61
  }
63
- CACHE_DIR = normalized;
62
+ CACHE_DIR = normalize(resolve(cacheDir));
64
63
  } else {
65
64
  CACHE_DIR = join(__dirname, '..', '..', '..', 'test-results', 'vllm-cache');
66
65
  }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Calibration Suite (Meta-evaluation)
3
+ *
4
+ * Measures how well a VLM judge performs against human-labeled ground truth.
5
+ * When using ML as a test oracle, you need tests of the tests
6
+ * (LLMShot, arXiv:2507.10062; Stanford CS 329T).
7
+ *
8
+ * Usage:
9
+ * const suite = createCalibrationSuite([
10
+ * { screenshot: 'good.png', expectedScore: 9, label: 'clean homepage' },
11
+ * { screenshot: 'broken.png', expectedScore: 2, label: 'broken layout' },
12
+ * ]);
13
+ * const report = await suite.run({ provider: 'gemini' });
14
+ * // report.correlation, report.meanAbsoluteError, report.falsePositiveRate, ...
15
+ */
16
+
17
+ import { VLLMJudge } from './judge.mjs';
18
+ import { ValidationError, ConfigError } from './errors.mjs';
19
+ import { warn } from './logger.mjs';
20
+ import { pearsonCorrelation, spearmanCorrelation } from './metrics.mjs';
21
+ import { deriveCalibrationProfile, analyzeScoreDistribution } from './score-calibration.mjs';
22
+
23
+ /**
24
+ * @typedef {Object} CalibrationSample
25
+ * @property {string} screenshot - Path to screenshot file
26
+ * @property {number} expectedScore - Human-labeled expected score (0-10)
27
+ * @property {string} [label] - Description of the sample
28
+ * @property {string} [prompt] - Custom prompt (defaults to generic quality evaluation)
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} CalibrationReport
33
+ * @property {number} sampleCount - Number of samples evaluated
34
+ * @property {number} pearsonR - Pearson correlation between VLM and expected scores
35
+ * @property {number} spearmanR - Spearman rank correlation
36
+ * @property {number} meanAbsoluteError - Average |VLM score - expected score|
37
+ * @property {number} maxError - Worst single-sample error
38
+ * @property {number} falsePositiveRate - Rate of VLM pass (>=7) when expected fail (<7)
39
+ * @property {number} falseNegativeRate - Rate of VLM fail (<7) when expected pass (>=7)
40
+ * @property {{ offset: number, scale: number, r2: number }} suggestedCalibration - Derived calibration profile
41
+ * @property {{ mean: number, stddev: number, skew: number }} scoreDistribution - VLM score distribution
42
+ * @property {Array<{ screenshot: string, label: string, expected: number, actual: number, error: number }>} details
43
+ */
44
+
45
+ const DEFAULT_PROMPT = 'Evaluate the overall quality of this screenshot. Consider layout, visual design, readability, accessibility, and functional correctness.';
46
+ const DEFAULT_PASS_THRESHOLD = 7;
47
+
48
+ /**
49
+ * Create a calibration suite
50
+ *
51
+ * @param {CalibrationSample[]} samples - Labeled screenshot samples
52
+ * @param {{ passThreshold?: number }} [options={}] - Suite options
53
+ * @returns {{ run: (judgeOptions: object) => Promise<CalibrationReport>, samples: CalibrationSample[] }}
54
+ */
55
+ export function createCalibrationSuite(samples, options = {}) {
56
+ if (!Array.isArray(samples) || samples.length < 2) {
57
+ throw new ValidationError('Calibration suite requires at least 2 labeled samples', { count: samples?.length ?? 0 });
58
+ }
59
+
60
+ for (const s of samples) {
61
+ if (typeof s.screenshot !== 'string' || typeof s.expectedScore !== 'number') {
62
+ throw new ValidationError('Each sample must have a screenshot path (string) and expectedScore (number)', { screenshot: typeof s.screenshot, expectedScore: typeof s.expectedScore });
63
+ }
64
+ if (s.expectedScore < 0 || s.expectedScore > 10) {
65
+ throw new ValidationError(`expectedScore must be 0-10, got ${s.expectedScore}`, { expectedScore: s.expectedScore });
66
+ }
67
+ }
68
+
69
+ const passThreshold = options.passThreshold ?? DEFAULT_PASS_THRESHOLD;
70
+
71
+ return {
72
+ samples: [...samples],
73
+
74
+ /**
75
+ * Run the calibration suite against a judge
76
+ *
77
+ * @param {object} judgeOptions - Options passed to VLLMJudge constructor
78
+ * @returns {Promise<CalibrationReport>}
79
+ */
80
+ async run(judgeOptions = {}) {
81
+ const judge = new VLLMJudge(judgeOptions);
82
+
83
+ if (!judge.enabled) {
84
+ throw new ConfigError('VLLMJudge is disabled -- cannot run calibration suite');
85
+ }
86
+
87
+ const details = [];
88
+ const rawScores = [];
89
+ const expectedScores = [];
90
+
91
+ for (const sample of samples) {
92
+ const prompt = sample.prompt || DEFAULT_PROMPT;
93
+ try {
94
+ const result = await judge.judgeScreenshot(sample.screenshot, prompt);
95
+ const parsed = judge.extractSemanticInfo(result.judgment || '');
96
+ const score = parsed.score ?? result.score ?? null;
97
+
98
+ if (score !== null) {
99
+ const error = Math.abs(score - sample.expectedScore);
100
+ rawScores.push(score);
101
+ expectedScores.push(sample.expectedScore);
102
+ details.push({
103
+ screenshot: sample.screenshot,
104
+ label: sample.label || sample.screenshot,
105
+ expected: sample.expectedScore,
106
+ actual: score,
107
+ error
108
+ });
109
+ } else {
110
+ details.push({
111
+ screenshot: sample.screenshot,
112
+ label: sample.label || sample.screenshot,
113
+ expected: sample.expectedScore,
114
+ actual: null,
115
+ error: null,
116
+ skipped: 'No score returned'
117
+ });
118
+ }
119
+ } catch (err) {
120
+ warn(`[CalibrationSuite] Sample "${sample.label || sample.screenshot}" failed: ${err.message}`);
121
+ details.push({
122
+ screenshot: sample.screenshot,
123
+ label: sample.label || sample.screenshot,
124
+ expected: sample.expectedScore,
125
+ actual: null,
126
+ error: null,
127
+ errorMessage: err.message,
128
+ skipped: true
129
+ });
130
+ }
131
+ }
132
+
133
+ // Compute metrics from scored samples only
134
+ const scored = details.filter(d => d.actual !== null);
135
+ const n = scored.length;
136
+
137
+ if (n < 2) {
138
+ return {
139
+ sampleCount: samples.length,
140
+ scoredCount: n,
141
+ pearsonR: null,
142
+ spearmanR: null,
143
+ meanAbsoluteError: null,
144
+ maxError: null,
145
+ falsePositiveRate: null,
146
+ falseNegativeRate: null,
147
+ suggestedCalibration: null,
148
+ scoreDistribution: analyzeScoreDistribution(rawScores),
149
+ details,
150
+ error: 'Fewer than 2 samples scored -- cannot compute metrics'
151
+ };
152
+ }
153
+
154
+ const errors = scored.map(d => d.error);
155
+ const meanAbsoluteError = errors.reduce((a, b) => a + b, 0) / n;
156
+ const maxError = Math.max(...errors);
157
+
158
+ // Correlation
159
+ const actualScores = scored.map(d => d.actual);
160
+ const expScores = scored.map(d => d.expected);
161
+ const pearsonR = pearsonCorrelation(actualScores, expScores);
162
+ const spearmanR = spearmanCorrelation(actualScores, expScores);
163
+
164
+ // False positive/negative rates (relative to passThreshold)
165
+ let fp = 0, fn = 0, expectedFail = 0, expectedPass = 0;
166
+ for (const d of scored) {
167
+ if (d.expected >= passThreshold) {
168
+ expectedPass++;
169
+ if (d.actual < passThreshold) fn++;
170
+ } else {
171
+ expectedFail++;
172
+ if (d.actual >= passThreshold) fp++;
173
+ }
174
+ }
175
+ const falsePositiveRate = expectedFail > 0 ? fp / expectedFail : 0;
176
+ const falseNegativeRate = expectedPass > 0 ? fn / expectedPass : 0;
177
+
178
+ // Derive calibration profile
179
+ const pairs = scored.map(d => ({ raw: d.actual, expected: d.expected }));
180
+ const suggestedCalibration = deriveCalibrationProfile(pairs);
181
+
182
+ return {
183
+ sampleCount: samples.length,
184
+ scoredCount: n,
185
+ pearsonR,
186
+ spearmanR,
187
+ meanAbsoluteError,
188
+ maxError,
189
+ falsePositiveRate,
190
+ falseNegativeRate,
191
+ suggestedCalibration,
192
+ scoreDistribution: analyzeScoreDistribution(rawScores),
193
+ details
194
+ };
195
+ }
196
+ };
197
+ }
package/src/constants.mjs CHANGED
@@ -98,6 +98,17 @@ export const API_ENDPOINT_CONSTANTS = {
98
98
  RATE_LIMIT_MAX_REQUESTS: 10
99
99
  };
100
100
 
101
+ /**
102
+ * Rate Limit Validation Bounds
103
+ */
104
+ export const RATE_LIMIT_BOUNDS = {
105
+ /** Minimum allowed value for RATE_LIMIT_MAX_REQUESTS */
106
+ MIN: 1,
107
+
108
+ /** Maximum allowed value for RATE_LIMIT_MAX_REQUESTS */
109
+ MAX: 1000
110
+ };
111
+
101
112
  /**
102
113
  * Retry Configuration
103
114
  */
@@ -1 +1 @@
1
- (function(_0x363aa6,_0xe27cc5){const _0xe0b89c=_0x30d0,_0x500e4=_0x363aa6();while(!![]){try{const _0x18ac1d=parseInt(_0xe0b89c(0x21c))/0x1+-parseInt(_0xe0b89c(0x1ce))/0x2*(-parseInt(_0xe0b89c(0x1ea))/0x3)+parseInt(_0xe0b89c(0x1f0))/0x4+-parseInt(_0xe0b89c(0x216))/0x5*(parseInt(_0xe0b89c(0x1f3))/0x6)+parseInt(_0xe0b89c(0x20e))/0x7*(parseInt(_0xe0b89c(0x1dc))/0x8)+parseInt(_0xe0b89c(0x1c9))/0x9+-parseInt(_0xe0b89c(0x1d6))/0xa*(parseInt(_0xe0b89c(0x1f9))/0xb);if(_0x18ac1d===_0xe27cc5)break;else _0x500e4['push'](_0x500e4['shift']());}catch(_0x3fe9ed){_0x500e4['push'](_0x500e4['shift']());}}}(_0x540c,0x38dea));const _0x20e6c3=(function(){let _0x2e6c58=!![];return function(_0x3b3213,_0xc83401){const _0x32402f=_0x2e6c58?function(){if(_0xc83401){const _0x42e1e8=_0xc83401['apply'](_0x3b3213,arguments);return _0xc83401=null,_0x42e1e8;}}:function(){};return _0x2e6c58=![],_0x32402f;};}()),_0x33edcd=_0x20e6c3(this,function(){const _0x16ec1d=_0x30d0;return _0x33edcd[_0x16ec1d(0x1e6)+_0x16ec1d(0x1da)]()['searc'+'h']('(((.+'+_0x16ec1d(0x1e4)+'+$')[_0x16ec1d(0x1e6)+'ing']()['const'+_0x16ec1d(0x1e3)+'r'](_0x33edcd)[_0x16ec1d(0x1d3)+'h']('(((.+'+')+)+)'+'+$');});_0x33edcd();import{selectModelTier,selectProvider,selectModelTierAndProvider}from'./model-tier-selector.mjs';function _0x540c(){const _0x40d5dd=['oxHKqML6qW','Aw5WDxq','zxn0ihq','y2vUDa','ihvZzsa','BNnPzgu','mtu4mZy3mNbvvxP1BW','BMzPz3u','ihjLCxu','oda0zujOB01v','CMf0Aw8','zdOGja','zw5ZAxq','DMfSDwe','z3nqzxi','mteZnZe0ntLmCKjMwvO','C2f2Aw4','CMvZigi','y29ZDca','zgvY','ywWGy28','CMvXDwK','q29ZDa','DgLVBIa','CIb1C2K','zsbLEha','ihbLCIa','q29ZDc0','jsbTB3i','sgLNAc0','B3v0Chu','yMvZDa','yMfSyw4','y2vK','CM92Awq','icHLC3q','mte2mdzUzMDTDMm','zw52','B3iGCxu','qMfSyw4','Dg9gAxG','Esb0CMe','DgLLCI4','zNjLCxu','nZm3mfjksKrRAG','y29ZDfm','BJOG','y3jPDgK','y2fS','ChjVDMK','mtKXmJm5uhrbsg9O','AwvYigy','DgLVBIW','mZKZmJKXovrrExryBG','yxrLzca','Aw1HDgu','zw5ZAxy','AwvYiha','mtCYotaYz3zTrgLs','zgvVzMy','zw5JEq','zMfZDca','y2vKihq','C2vHCMm','zMfZDa','Dg90ywW','mtbiChD5thq','B3bLCMe','zwvKCYa','y29ZDa','Aw5N','ChjPy2K','odCYqvjwy2D3','vgLLCG','y2fSigu','zxn0Aw0','ywjZ','DMfSAwq','zxmGC3a','CNvJDg8','ksSPkYK','AgLNAa','Dg9tDhi','Bw9KzwW','CMvTzw4','DgLLCG'];_0x540c=function(){return _0x40d5dd;};return _0x540c();}import{createConfig,getProvider}from'./config.mjs';export function calculateCostComparison(_0x1bde85={},_0xee3629={}){const _0x4baaa9=_0x30d0,_0x24e0da=parseFloat(_0xee3629[_0x4baaa9(0x1df)+'atedC'+'ost']?.[_0x4baaa9(0x1d5)+_0x4baaa9(0x200)]||'0'),_0x13909c=_0x1bde85[_0x4baaa9(0x1e7)+'Tier']||'balan'+_0x4baaa9(0x20b),_0x5dc553=_0xee3629[_0x4baaa9(0x21b)+_0x4baaa9(0x1fd)]||'gemin'+'i',_0x5b8269=getProvider(_0x5dc553),_0x1bc750={};_0x1bc750['input']=0x0,_0x1bc750[_0x4baaa9(0x208)+'t']=0x0;const _0x43a316=_0x5b8269?.[_0x4baaa9(0x1db)+'ng']||_0x1bc750,_0x455760=0x3e8,_0x42cf84=0x1f4,_0x28c9ce={};for(const _0x5a12fa of[_0x4baaa9(0x1d4),_0x4baaa9(0x20a)+'ced',_0x4baaa9(0x209)]){const _0x34de26=_0x455760/0xf4240*_0x43a316['input'],_0x124b8e=_0x42cf84/0xf4240*_0x43a316[_0x4baaa9(0x208)+'t'];_0x28c9ce[_0x5a12fa]=_0x34de26+_0x124b8e;}const _0x33beea={};for(const _0x3b1a6b of['fast',_0x4baaa9(0x20a)+_0x4baaa9(0x20b),_0x4baaa9(0x209)]){if(_0x28c9ce[_0x3b1a6b]&&_0x24e0da>0x0){const _0x2dafeb=_0x24e0da-_0x28c9ce[_0x3b1a6b],_0x5cc7e6=_0x2dafeb/_0x24e0da*0x64;_0x33beea[_0x3b1a6b]={'absolute':_0x2dafeb,'percent':_0x5cc7e6,'cost':_0x28c9ce[_0x3b1a6b]};}}const _0x301c2e={};return _0x301c2e[_0x4baaa9(0x1e9)]=_0x13909c,_0x301c2e['provi'+'der']=_0x5dc553,_0x301c2e[_0x4baaa9(0x1d9)]=_0x24e0da,{'current':_0x301c2e,'tiers':_0x28c9ce,'savings':_0x33beea,'recommendation':getCostOptimizationRecommendation(_0x1bde85,_0x24e0da,_0x28c9ce)};}function _0x30d0(_0x250cc4,_0x7fabfc){const _0x42f66e=_0x540c();return _0x30d0=function(_0x33edcd,_0x20e6c3){_0x33edcd=_0x33edcd-0x1c9;let _0x540c14=_0x42f66e[_0x33edcd];if(_0x30d0['LOmDBw']===undefined){var _0x30d03e=function(_0x44bb83){const _0x539959='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x379ba8='',_0x566896='',_0x969928=_0x379ba8+_0x30d03e;for(let _0x1bc844=0x0,_0x5b6316,_0x2374c5,_0x10de93=0x0;_0x2374c5=_0x44bb83['charAt'](_0x10de93++);~_0x2374c5&&(_0x5b6316=_0x1bc844%0x4?_0x5b6316*0x40+_0x2374c5:_0x2374c5,_0x1bc844++%0x4)?_0x379ba8+=_0x969928['charCodeAt'](_0x10de93+0xa)-0xa!==0x0?String['fromCharCode'](0xff&_0x5b6316>>(-0x2*_0x1bc844&0x6)):_0x1bc844:0x0){_0x2374c5=_0x539959['indexOf'](_0x2374c5);}for(let _0x2e6c58=0x0,_0x3b3213=_0x379ba8['length'];_0x2e6c58<_0x3b3213;_0x2e6c58++){_0x566896+='%'+('00'+_0x379ba8['charCodeAt'](_0x2e6c58)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x566896);};_0x30d0['ZJpJuI']=_0x30d03e,_0x250cc4=arguments,_0x30d0['LOmDBw']=!![];}const _0x1451a2=_0x42f66e[0x0],_0x32b53d=_0x33edcd+_0x1451a2,_0x2aae6c=_0x250cc4[_0x32b53d];if(!_0x2aae6c){const _0xc83401=function(_0x32402f){this['oInCwh']=_0x32402f,this['ATCMuh']=[0x1,0x0,0x0],this['CTcwpT']=function(){return'newState';},this['ECRSOR']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['bhAIrq']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0xc83401['prototype']['MnFJHe']=function(){const _0x42e1e8=new RegExp(this['ECRSOR']+this['bhAIrq']),_0x1bde85=_0x42e1e8['test'](this['CTcwpT']['toString']())?--this['ATCMuh'][0x1]:--this['ATCMuh'][0x0];return this['LBHlgi'](_0x1bde85);},_0xc83401['prototype']['LBHlgi']=function(_0xee3629){if(!Boolean(~_0xee3629))return _0xee3629;return this['DnlTCR'](this['oInCwh']);},_0xc83401['prototype']['DnlTCR']=function(_0x24e0da){for(let _0x13909c=0x0,_0x5dc553=this['ATCMuh']['length'];_0x13909c<_0x5dc553;_0x13909c++){this['ATCMuh']['push'](Math['round'](Math['random']())),_0x5dc553=this['ATCMuh']['length'];}return _0x24e0da(this['ATCMuh'][0x0]);},new _0xc83401(_0x30d0)['MnFJHe'](),_0x540c14=_0x30d0['ZJpJuI'](_0x540c14),_0x250cc4[_0x32b53d]=_0x540c14;}else _0x540c14=_0x2aae6c;return _0x540c14;},_0x30d0(_0x250cc4,_0x7fabfc);}function getCostOptimizationRecommendation(_0x29ade2,_0x153a55,_0x4e1efd){const _0x2986b8=_0x30d0,{frequency:_0x5da791,criticality:_0x184fa1,costSensitive:_0xd1a9aa}=_0x29ade2;let _0x59a389='balan'+_0x2986b8(0x20b);if(_0x5da791==='high'||_0x5da791>=0xa||_0xd1a9aa)_0x59a389=_0x2986b8(0x1d4);else _0x184fa1===_0x2986b8(0x219)+_0x2986b8(0x21a)&&(_0x59a389='best');const _0x12718e=_0x4e1efd[_0x59a389]||_0x153a55,_0xd634ff=_0x153a55-_0x12718e,_0x5aa49b=_0x153a55>0x0?_0xd634ff/_0x153a55*0x64:0x0;return{'tier':_0x59a389,'cost':_0x12718e,'savings':_0xd634ff,'savingsPercent':_0x5aa49b,'reason':getRecommendationReason(_0x29ade2,_0x59a389)};}function getRecommendationReason(_0x4dbf44,_0x140804){const _0x5c735b=_0x30d0;if(_0x140804===_0x5c735b(0x1d4)){if(_0x4dbf44['frequ'+'ency']===_0x5c735b(0x1e5)||_0x4dbf44[_0x5c735b(0x215)+_0x5c735b(0x1d0)]>=0xa)return _0x5c735b(0x207)+_0x5c735b(0x215)+'ency\x20'+_0x5c735b(0x1e1)+'ation'+_0x5c735b(0x1f2)+'ires\x20'+_0x5c735b(0x1d1)+'tier';if(_0x4dbf44[_0x5c735b(0x217)+_0x5c735b(0x1f6)+'ive'])return _0x5c735b(0x205)+'sensi'+'tive\x20'+_0x5c735b(0x1d7)+_0x5c735b(0x21e)+_0x5c735b(0x1ee)+'fast\x20'+_0x5c735b(0x1e9);}if(_0x140804==='best')return'Criti'+_0x5c735b(0x1de)+_0x5c735b(0x1f7)+_0x5c735b(0x201)+_0x5c735b(0x1ff)+_0x5c735b(0x1fb)+_0x5c735b(0x1ec)+_0x5c735b(0x21d)+_0x5c735b(0x210)+'ality';return _0x5c735b(0x211)+_0x5c735b(0x1d2)+_0x5c735b(0x1cd)+_0x5c735b(0x20c)+_0x5c735b(0x1e2)+'eed/q'+'ualit'+_0x5c735b(0x213)+_0x5c735b(0x1cf);}export function optimizeCost(_0x16a526={}){const _0x3e979c=_0x30d0,{frequency:_0x3b003c,criticality:_0x19b007,costSensitive:_0x23fea6,budget:_0x825e3c,requirements:requirements={}}=_0x16a526,_0x1faa13={};_0x1faa13[_0x3e979c(0x215)+_0x3e979c(0x1d0)]=_0x3b003c,_0x1faa13['criti'+'calit'+'y']=_0x19b007,_0x1faa13['costS'+_0x3e979c(0x1f6)+'ive']=_0x23fea6,_0x1faa13[_0x3e979c(0x1ff)+_0x3e979c(0x1e8)+'ts']={...requirements},_0x1faa13[_0x3e979c(0x1ff)+_0x3e979c(0x1e8)+'ts'][_0x3e979c(0x217)+'ensit'+'ive']=_0x23fea6,_0x1faa13[_0x3e979c(0x1ff)+_0x3e979c(0x1e8)+'ts']['env']=process[_0x3e979c(0x20f)];const {tier:_0x561c64,provider:_0x2a9b30,reason:_0x4387e6}=selectModelTierAndProvider(_0x1faa13),_0x2d9d05={};_0x2d9d05[_0x3e979c(0x1e7)+_0x3e979c(0x1dd)]=_0x561c64,_0x2d9d05['provi'+'der']=_0x2a9b30;const _0xa186e=createConfig(_0x2d9d05),_0x5e44ed=getProvider(_0x2a9b30),_0x401693={};_0x401693[_0x3e979c(0x1eb)]=0x0,_0x401693[_0x3e979c(0x208)+'t']=0x0;const _0x3f9b52=_0x5e44ed?.['prici'+'ng']||_0x401693,_0x33b65a=0x3e8,_0x200ddd=0x1f4,_0x16a110=_0x33b65a/0xf4240*_0x3f9b52['input']+_0x200ddd/0xf4240*_0x3f9b52['outpu'+'t'],_0x3813d4={};for(const _0x1b7907 of[_0x3e979c(0x1d4),'balan'+_0x3e979c(0x20b),'best']){if(_0x1b7907!==_0x561c64){const _0x2842f2=_0x16a110,_0x1df359={};_0x1df359[_0x3e979c(0x1d9)]=_0x2842f2,_0x1df359['savin'+'gs']=_0x16a110-_0x2842f2,_0x1df359[_0x3e979c(0x1fa)+'gsPer'+_0x3e979c(0x1ed)]=_0x16a110>0x0?(_0x16a110-_0x2842f2)/_0x16a110*0x64:0x0,_0x3813d4[_0x1b7907]=_0x1df359;}}const _0xb2cda2=_0x825e3c?_0x16a110<=_0x825e3c:null;return{'recommendedTier':_0x561c64,'recommendedProvider':_0x2a9b30,'estimatedCost':_0x16a110,'savings':getSavingsEstimate(_0x561c64,_0x2a9b30,_0x3813d4),'config':_0xa186e,'reason':_0x4387e6,'withinBudget':_0xb2cda2,'comparisons':_0x3813d4,'recommendation':_0xb2cda2===![]?'Estim'+_0x3e979c(0x1ca)+_0x3e979c(0x1fc)+'($'+_0x16a110[_0x3e979c(0x212)+'ed'](0x6)+(')\x20exc'+_0x3e979c(0x1d8)+'budge'+'t\x20($')+_0x825e3c['toFix'+'ed'](0x6)+(').\x20Co'+_0x3e979c(0x1ef)+_0x3e979c(0x202)+'ng\x20\x27f'+'ast\x27\x20'+_0x3e979c(0x214)):'Optim'+_0x3e979c(0x1fe)+_0x3e979c(0x1f1)+_0x3e979c(0x1f4)+_0x3e979c(0x218)+_0x2a9b30+'\x20'+_0x561c64+('\x20tier'+_0x3e979c(0x20d)+_0x3e979c(0x1cb)+_0x3e979c(0x1f5))+_0x16a110[_0x3e979c(0x212)+'ed'](0x6)+(_0x3e979c(0x204)+'valid'+'ation'+')')};}function getSavingsEstimate(_0x1217b9,_0x1d34f6,_0x5139f4){const _0x502d05=_0x30d0;if(_0x1217b9===_0x502d05(0x1d4)){const _0x1b548a=_0x5139f4[_0x502d05(0x20a)+_0x502d05(0x20b)]?.['savin'+'gs']||0x0,_0x2ccd2c=_0x5139f4['best']?.[_0x502d05(0x1fa)+'gs']||0x0;return{'vsBalanced':_0x1b548a>0x0?(_0x5139f4[_0x502d05(0x20a)+_0x502d05(0x20b)][_0x502d05(0x1fa)+_0x502d05(0x1f8)+_0x502d05(0x1ed)]||0x0)[_0x502d05(0x212)+'ed'](0x0)+'%':'0%','vsBest':_0x2ccd2c>0x0?(_0x5139f4[_0x502d05(0x209)][_0x502d05(0x1fa)+_0x502d05(0x1f8)+'cent']||0x0)['toFix'+'ed'](0x0)+'%':'0%'};}if(_0x1217b9===_0x502d05(0x20a)+_0x502d05(0x20b)){const _0x2da01f=_0x5139f4['fast']?.[_0x502d05(0x1fa)+'gs']||0x0,_0x729809=_0x5139f4[_0x502d05(0x209)]?.['savin'+'gs']||0x0;return{'vsFast':_0x2da01f<0x0?Math[_0x502d05(0x1e0)](_0x5139f4['fast']?.['savin'+_0x502d05(0x1f8)+'cent']||0x0)[_0x502d05(0x212)+'ed'](0x0)+(_0x502d05(0x206)+_0x502d05(0x203)+'ensiv'+'e'):'0%','vsBest':_0x729809>0x0?(_0x5139f4['best']['savin'+'gsPer'+'cent']||0x0)[_0x502d05(0x212)+'ed'](0x0)+'%':'0%'};}return{'vsFast':_0x5139f4[_0x502d05(0x1d4)]?Math[_0x502d05(0x1e0)](_0x5139f4['fast']['savin'+_0x502d05(0x1f8)+'cent']||0x0)['toFix'+'ed'](0x0)+('%\x20mor'+'e\x20exp'+'ensiv'+'e'):'0%','vsBalanced':_0x5139f4['balan'+'ced']?Math['abs'](_0x5139f4[_0x502d05(0x20a)+'ced'][_0x502d05(0x1fa)+'gsPer'+_0x502d05(0x1ed)]||0x0)['toFix'+'ed'](0x0)+(_0x502d05(0x206)+_0x502d05(0x203)+_0x502d05(0x1cc)+'e'):'0%'};}
1
+ function _0x44fa(_0x1b7e6c,_0x4f2e91){const _0x39b127=_0x41cc();return _0x44fa=function(_0x42fad3,_0x7d21ab){_0x42fad3=_0x42fad3-0xa0;let _0x41cca5=_0x39b127[_0x42fad3];if(_0x44fa['mZqzuW']===undefined){var _0x44fa51=function(_0x137d0a){const _0x9cebc5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x10951a='',_0xb545cc='',_0x3eaa48=_0x10951a+_0x44fa51;for(let _0x5ce45a=0x0,_0x4393b2,_0x28fb3f,_0x197a35=0x0;_0x28fb3f=_0x137d0a['charAt'](_0x197a35++);~_0x28fb3f&&(_0x4393b2=_0x5ce45a%0x4?_0x4393b2*0x40+_0x28fb3f:_0x28fb3f,_0x5ce45a++%0x4)?_0x10951a+=_0x3eaa48['charCodeAt'](_0x197a35+0xa)-0xa!==0x0?String['fromCharCode'](0xff&_0x4393b2>>(-0x2*_0x5ce45a&0x6)):_0x5ce45a:0x0){_0x28fb3f=_0x9cebc5['indexOf'](_0x28fb3f);}for(let _0x3eae9f=0x0,_0x50f621=_0x10951a['length'];_0x3eae9f<_0x50f621;_0x3eae9f++){_0xb545cc+='%'+('00'+_0x10951a['charCodeAt'](_0x3eae9f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xb545cc);};_0x44fa['ctDYOL']=_0x44fa51,_0x1b7e6c=arguments,_0x44fa['mZqzuW']=!![];}const _0x91b6e8=_0x39b127[0x0],_0x5a6c85=_0x42fad3+_0x91b6e8,_0x1549a6=_0x1b7e6c[_0x5a6c85];if(!_0x1549a6){const _0x4de8b5=function(_0x193daf){this['bmzfVw']=_0x193daf,this['pBTXLc']=[0x1,0x0,0x0],this['loDRVx']=function(){return'newState';},this['dKehkO']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['vrQtQR']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x4de8b5['prototype']['ZpGEKi']=function(){const _0x504a68=new RegExp(this['dKehkO']+this['vrQtQR']),_0xa11c38=_0x504a68['test'](this['loDRVx']['toString']())?--this['pBTXLc'][0x1]:--this['pBTXLc'][0x0];return this['WIOdvz'](_0xa11c38);},_0x4de8b5['prototype']['WIOdvz']=function(_0x37beac){if(!Boolean(~_0x37beac))return _0x37beac;return this['OPbltl'](this['bmzfVw']);},_0x4de8b5['prototype']['OPbltl']=function(_0x4cd724){for(let _0x352e1c=0x0,_0x31d3e2=this['pBTXLc']['length'];_0x352e1c<_0x31d3e2;_0x352e1c++){this['pBTXLc']['push'](Math['round'](Math['random']())),_0x31d3e2=this['pBTXLc']['length'];}return _0x4cd724(this['pBTXLc'][0x0]);},new _0x4de8b5(_0x44fa)['ZpGEKi'](),_0x41cca5=_0x44fa['ctDYOL'](_0x41cca5),_0x1b7e6c[_0x5a6c85]=_0x41cca5;}else _0x41cca5=_0x1549a6;return _0x41cca5;},_0x44fa(_0x1b7e6c,_0x4f2e91);}(function(_0x440952,_0x534470){const _0x5c6aa1=_0x44fa,_0x19edde=_0x440952();while(!![]){try{const _0xbf939d=-parseInt(_0x5c6aa1(0xb5))/0x1*(parseInt(_0x5c6aa1(0xc5))/0x2)+parseInt(_0x5c6aa1(0xd9))/0x3*(parseInt(_0x5c6aa1(0xb6))/0x4)+-parseInt(_0x5c6aa1(0xb1))/0x5*(parseInt(_0x5c6aa1(0xa2))/0x6)+-parseInt(_0x5c6aa1(0xa3))/0x7+parseInt(_0x5c6aa1(0xc6))/0x8+-parseInt(_0x5c6aa1(0xe0))/0x9+parseInt(_0x5c6aa1(0xbc))/0xa;if(_0xbf939d===_0x534470)break;else _0x19edde['push'](_0x19edde['shift']());}catch(_0x2eef0f){_0x19edde['push'](_0x19edde['shift']());}}}(_0x41cc,0xa7341));const _0x7d21ab=(function(){let _0x3eae9f=!![];return function(_0x50f621,_0x4de8b5){const _0x193daf=_0x3eae9f?function(){const _0x1dcae6=_0x44fa;if(_0x4de8b5){const _0x504a68=_0x4de8b5[_0x1dcae6(0xd2)](_0x50f621,arguments);return _0x4de8b5=null,_0x504a68;}}:function(){};return _0x3eae9f=![],_0x193daf;};}()),_0x42fad3=_0x7d21ab(this,function(){const _0x2103db=_0x44fa;return _0x42fad3['toStr'+'ing']()['searc'+'h']('(((.+'+')+)+)'+'+$')[_0x2103db(0xb4)+'ing']()[_0x2103db(0xa1)+'ructo'+'r'](_0x42fad3)['searc'+'h'](_0x2103db(0xdf)+_0x2103db(0xbd)+'+$');});_0x42fad3();import{selectModelTier,selectProvider,selectModelTierAndProvider}from'./model-tier-selector.mjs';function _0x41cc(){const _0x224cfa=['y2vUDa','z3nqzxi','Aw5WDxq','y2fS','zMfZDa','vgLLCG','yxbWBhK','yNvKz2u','DgLLCG','ywWGy28','yMfSyw4','y29ZDfm','zxn0Aw0','mtm4mZC1AvnPvMHL','ks4Gq28','y2vKihq','BNnPzgu','AxzL','sgLNAc0','kcGOlIS','nZq5mdC5mfPVD0TZzG','q3jPDgK','zdOGja','ChjPy2K','ihrPzxi','y29ZDa','AwvYiha','y29ZDca','BMzPz3u','y29UC3q','ndq0s210wNP1','nte1otaXnfLpDxfuBa','zw5JEsa','CMvXDwK','CMf0Aw8','ywjZ','CM92Awq','y2vK','CIb1C2K','AwvYigy','B3v0Chu','zw5ZAxy','DgLLCI4','zwvKl3e','zNjLCxu','mZiXnwXLDKv3CW','zsbLEha','jsbTB3i','Dg9tDhi','mti5mZC5zeLZA25Y','otjAsKrkD20','C2f2Aw4','y3jPDgK','yMvZDa','ywXPDhK','zgvY','ntK3mZiWmfbOBLbRvq','ksSPkYK','ksbLEgm','yxn0jYa','zw5ZAxq','ChjVDMK','zMfZDca','yxrPB24','Bw9KzwW','mtbkExjuDLe','mtaZmJm2nZjmvfLUDuG','zxn0ihq','CMvTzw4','zw5JEq','DMfSAwq','Dg9gAxG'];_0x41cc=function(){return _0x224cfa;};return _0x41cc();}import{createConfig,getProvider}from'./config.mjs';export function calculateCostComparison(_0xa11c38={},_0x37beac={}){const _0x3d1f9b=_0x44fa,_0x4cd724=parseFloat(_0x37beac[_0x3d1f9b(0xd8)+'atedC'+'ost']?.['total'+'Cost']||'0'),_0x352e1c=_0xa11c38[_0x3d1f9b(0xc4)+'Tier']||_0x3d1f9b(0xd6)+'ced',_0x31d3e2=_0x37beac[_0x3d1f9b(0xc1)+_0x3d1f9b(0xbb)]||'gemin'+'i',_0x12b42d=getProvider(_0x31d3e2),_0x15de61={};_0x15de61[_0x3d1f9b(0xce)]=0x0,_0x15de61['outpu'+'t']=0x0;const _0x44c811=_0x12b42d?.[_0x3d1f9b(0xe3)+'ng']||_0x15de61,_0x414f2a=0x3e8,_0x199e08=0x1f4,_0x539bc1={};for(const _0x30cd7f of[_0x3d1f9b(0xd0),_0x3d1f9b(0xd6)+_0x3d1f9b(0xa9),'best']){const _0x4f9cf2=_0x414f2a/0xf4240*_0x44c811['input'],_0x422810=_0x199e08/0xf4240*_0x44c811[_0x3d1f9b(0xac)+'t'];_0x539bc1[_0x30cd7f]=_0x4f9cf2+_0x422810;}const _0x2b3f67={};for(const _0x49b904 of['fast','balan'+_0x3d1f9b(0xa9),_0x3d1f9b(0xb9)]){if(_0x539bc1[_0x49b904]&&_0x4cd724>0x0){const _0x1d8c17=_0x4cd724-_0x539bc1[_0x49b904],_0x3f747e=_0x1d8c17/_0x4cd724*0x64;_0x2b3f67[_0x49b904]={'absolute':_0x1d8c17,'percent':_0x3f747e,'cost':_0x539bc1[_0x49b904]};}}const _0x2ffaf4={};return _0x2ffaf4[_0x3d1f9b(0xd4)]=_0x352e1c,_0x2ffaf4[_0x3d1f9b(0xc1)+'der']=_0x31d3e2,_0x2ffaf4[_0x3d1f9b(0xe5)]=_0x4cd724,{'current':_0x2ffaf4,'tiers':_0x539bc1,'savings':_0x2b3f67,'recommendation':getCostOptimizationRecommendation(_0xa11c38,_0x4cd724,_0x539bc1)};}function getCostOptimizationRecommendation(_0x41e20b,_0x37af69,_0x48dd79){const _0x5b74bb=_0x44fa,{frequency:_0x20189a,criticality:_0x3f22ad,costSensitive:_0x36a0ee}=_0x41e20b;let _0x1e79d8=_0x5b74bb(0xd6)+'ced';if(_0x20189a==='high'||_0x20189a>=0xa||_0x36a0ee)_0x1e79d8=_0x5b74bb(0xd0);else _0x3f22ad==='criti'+_0x5b74bb(0xcf)&&(_0x1e79d8='best');const _0x1c7f5e=_0x48dd79[_0x1e79d8]||_0x37af69,_0x3d5a4=_0x37af69-_0x1c7f5e,_0x4a3102=_0x37af69>0x0?_0x3d5a4/_0x37af69*0x64:0x0;return{'tier':_0x1e79d8,'cost':_0x1c7f5e,'savings':_0x3d5a4,'savingsPercent':_0x4a3102,'reason':getRecommendationReason(_0x41e20b,_0x1e79d8)};}function getRecommendationReason(_0x52de8f,_0x4f001e){const _0x2d9ac1=_0x44fa;if(_0x4f001e===_0x2d9ac1(0xd0)){if(_0x52de8f[_0x2d9ac1(0xb0)+_0x2d9ac1(0xc9)]==='high'||_0x52de8f['frequ'+'ency']>=0xa)return _0x2d9ac1(0xde)+_0x2d9ac1(0xb0)+_0x2d9ac1(0xa4)+_0x2d9ac1(0xca)+_0x2d9ac1(0xc3)+'\x20requ'+'ires\x20'+_0x2d9ac1(0xc2)+_0x2d9ac1(0xd4);if(_0x52de8f[_0x2d9ac1(0xd7)+_0x2d9ac1(0xc0)+'ive'])return'Cost-'+'sensi'+'tive\x20'+'opera'+'tion,'+'\x20use\x20'+_0x2d9ac1(0xc2)+_0x2d9ac1(0xd4);}if(_0x4f001e==='best')return _0x2d9ac1(0xe1)+'cal\x20e'+'valua'+'tion\x20'+_0x2d9ac1(0xa5)+'res\x20b'+_0x2d9ac1(0xc7)+_0x2d9ac1(0xab)+'or\x20qu'+_0x2d9ac1(0xba);return'Balan'+_0x2d9ac1(0xdb)+_0x2d9ac1(0xe6)+_0x2d9ac1(0xa8)+'es\x20sp'+_0x2d9ac1(0xaf)+'ualit'+'y\x20tra'+'deoff';}export function optimizeCost(_0x1e9fe4={}){const _0x11e7ca=_0x44fa,{frequency:_0x5777cc,criticality:_0x22d469,costSensitive:_0x514cfe,budget:_0x432c0f,requirements:requirements={}}=_0x1e9fe4,_0x3eea82={};_0x3eea82['frequ'+_0x11e7ca(0xc9)]=_0x5777cc,_0x3eea82[_0x11e7ca(0xb8)+'calit'+'y']=_0x22d469,_0x3eea82['costS'+_0x11e7ca(0xc0)+_0x11e7ca(0xdd)]=_0x514cfe,_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']={...requirements},_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']['costS'+_0x11e7ca(0xc0)+_0x11e7ca(0xdd)]=_0x514cfe,_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']['env']=process['env'];const {tier:_0xe6a001,provider:_0x5e9077,reason:_0x5a780c}=selectModelTierAndProvider(_0x3eea82),_0x24f230={};_0x24f230['model'+_0x11e7ca(0xd1)]=_0xe6a001,_0x24f230['provi'+_0x11e7ca(0xbb)]=_0x5e9077;const _0x4ed90b=createConfig(_0x24f230),_0x1223b5=getProvider(_0x5e9077),_0x5f4181={};_0x5f4181[_0x11e7ca(0xce)]=0x0,_0x5f4181[_0x11e7ca(0xac)+'t']=0x0;const _0x303982=_0x1223b5?.[_0x11e7ca(0xe3)+'ng']||_0x5f4181,_0x5a3edf=0x3e8,_0x2dd28c=0x1f4,_0x4c3c82=_0x5a3edf/0xf4240*_0x303982[_0x11e7ca(0xce)]+_0x2dd28c/0xf4240*_0x303982['outpu'+'t'],_0x4090a9={};for(const _0x28e336 of[_0x11e7ca(0xd0),'balan'+_0x11e7ca(0xa9),_0x11e7ca(0xb9)]){if(_0x28e336!==_0xe6a001){const _0x368ddf=_0x4c3c82,_0x24617f={};_0x24617f['cost']=_0x368ddf,_0x24617f[_0x11e7ca(0xb7)+'gs']=_0x4c3c82-_0x368ddf,_0x24617f['savin'+'gsPer'+_0x11e7ca(0xcc)]=_0x4c3c82>0x0?(_0x4c3c82-_0x368ddf)/_0x4c3c82*0x64:0x0,_0x4090a9[_0x28e336]=_0x24617f;}}const _0x1fa802=_0x432c0f?_0x4c3c82<=_0x432c0f:null;return{'recommendedTier':_0xe6a001,'recommendedProvider':_0x5e9077,'estimatedCost':_0x4c3c82,'savings':getSavingsEstimate(_0xe6a001,_0x5e9077,_0x4090a9),'config':_0x4ed90b,'reason':_0x5a780c,'withinBudget':_0x1fa802,'comparisons':_0x4090a9,'recommendation':_0x1fa802===![]?'Estim'+'ated\x20'+_0x11e7ca(0xe7)+'($'+_0x4c3c82['toFix'+'ed'](0x6)+(_0x11e7ca(0xbe)+'eeds\x20'+_0x11e7ca(0xd3)+'t\x20($')+_0x432c0f['toFix'+'ed'](0x6)+(_0x11e7ca(0xda)+_0x11e7ca(0xdc)+_0x11e7ca(0xaa)+'ng\x20\x27f'+_0x11e7ca(0xbf)+_0x11e7ca(0xae)):'Optim'+_0x11e7ca(0xd5)+_0x11e7ca(0xa0)+_0x11e7ca(0xa6)+'n:\x20'+_0x5e9077+'\x20'+_0xe6a001+(_0x11e7ca(0xe4)+'\x20(est'+'imate'+_0x11e7ca(0xe2))+_0x4c3c82['toFix'+'ed'](0x6)+('\x20per\x20'+'valid'+_0x11e7ca(0xc3)+')')};}function getSavingsEstimate(_0x50d756,_0x3054e2,_0x503311){const _0x30faa3=_0x44fa;if(_0x50d756===_0x30faa3(0xd0)){const _0x364818=_0x503311[_0x30faa3(0xd6)+_0x30faa3(0xa9)]?.['savin'+'gs']||0x0,_0x1e017a=_0x503311[_0x30faa3(0xb9)]?.[_0x30faa3(0xb7)+'gs']||0x0;return{'vsBalanced':_0x364818>0x0?(_0x503311[_0x30faa3(0xd6)+'ced']['savin'+'gsPer'+'cent']||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+'%':'0%','vsBest':_0x1e017a>0x0?(_0x503311['best'][_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+'%':'0%'};}if(_0x50d756===_0x30faa3(0xd6)+_0x30faa3(0xa9)){const _0x8d4f53=_0x503311[_0x30faa3(0xd0)]?.[_0x30faa3(0xb7)+'gs']||0x0,_0x186328=_0x503311['best']?.[_0x30faa3(0xb7)+'gs']||0x0;return{'vsFast':_0x8d4f53<0x0?Math['abs'](_0x503311['fast']?.[_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)['toFix'+'ed'](0x0)+('%\x20mor'+'e\x20exp'+_0x30faa3(0xad)+'e'):'0%','vsBest':_0x186328>0x0?(_0x503311['best'][_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)['toFix'+'ed'](0x0)+'%':'0%'};}return{'vsFast':_0x503311[_0x30faa3(0xd0)]?Math['abs'](_0x503311[_0x30faa3(0xd0)]['savin'+'gsPer'+'cent']||0x0)['toFix'+'ed'](0x0)+(_0x30faa3(0xb3)+_0x30faa3(0xb2)+'ensiv'+'e'):'0%','vsBalanced':_0x503311[_0x30faa3(0xd6)+'ced']?Math[_0x30faa3(0xa7)](_0x503311['balan'+_0x30faa3(0xa9)]['savin'+'gsPer'+_0x30faa3(0xcc)]||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+(_0x30faa3(0xb3)+'e\x20exp'+_0x30faa3(0xad)+'e'):'0%'};}
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Explanation Manager
3
- *
4
- * Provides late interaction capabilities for explaining VLLM judgments.
5
- * Allows humans to ask questions about judgments after they've been made.
3
+ *
4
+ * Post-hoc explanation of VLM judgments. After a VLM scores a screenshot,
5
+ * this module lets callers ask follow-up questions ("why was the score low?",
6
+ * "what specific element caused the issue?") by sending the original judgment
7
+ * plus the question back to the VLM for a targeted explanation.
8
+ * Explanations are cached to avoid redundant API calls.
6
9
  */
7
10
 
8
11
  import { VLLMJudge } from './judge.mjs';
@@ -11,9 +14,10 @@ import { log, warn } from './logger.mjs';
11
14
  import { formatNotesForPrompt } from './temporal.mjs';
12
15
 
13
16
  /**
14
- * Explanation Manager
15
- *
16
- * Manages interactive explanations of VLLM judgments
17
+ * Post-hoc explanation engine for VLM judgments.
18
+ *
19
+ * Sends the original judgment + a follow-up question to the VLM,
20
+ * returning a targeted explanation. Results are cached per judgment+question pair.
17
21
  */
18
22
  export class ExplanationManager {
19
23
  constructor(options = {}) {