visual-ai-assertions 0.4.0 → 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.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /** Supported provider identifiers for configuring `visualAI()`. */
3
+ /** Supported provider identifiers used internally for pricing and provider selection. */
4
4
  declare const Provider: {
5
5
  readonly ANTHROPIC: "anthropic";
6
6
  readonly OPENAI: "openai";
@@ -16,6 +16,8 @@ declare const Model: {
16
16
  readonly OpenAI: {
17
17
  readonly GPT_5_4: "gpt-5.4";
18
18
  readonly GPT_5_4_PRO: "gpt-5.4-pro";
19
+ readonly GPT_5_4_MINI: "gpt-5.4-mini";
20
+ readonly GPT_5_4_NANO: "gpt-5.4-nano";
19
21
  readonly GPT_5_2: "gpt-5.2";
20
22
  readonly GPT_5_MINI: "gpt-5-mini";
21
23
  };
@@ -29,11 +31,9 @@ type KnownModelName = (typeof Model.Anthropic)[keyof typeof Model.Anthropic] | (
29
31
  /** Default model selection used when a caller omits `config.model`. */
30
32
  declare const DEFAULT_MODELS: {
31
33
  readonly anthropic: "claude-sonnet-4-6";
32
- readonly openai: "gpt-5-mini";
34
+ readonly openai: "gpt-5.4-mini";
33
35
  readonly google: "gemini-3-flash-preview";
34
36
  };
35
- /** List of accepted provider names for validation and public consumption. */
36
- declare const VALID_PROVIDERS: readonly ProviderName[];
37
37
  /** Built-in content checks available through `client.content()`. */
38
38
  declare const Content: {
39
39
  /** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */
@@ -392,17 +392,20 @@ type ReasoningEffort = "low" | "medium" | "high" | "xhigh";
392
392
  * @example
393
393
  * ```ts
394
394
  * const client = visualAI({
395
- * provider: "openai",
396
395
  * model: "gpt-5-mini",
397
396
  * apiKey: process.env.OPENAI_API_KEY,
398
397
  * });
399
398
  * ```
400
399
  */
401
400
  interface VisualAIConfig {
402
- provider?: ProviderName;
403
401
  apiKey?: string;
404
402
  model?: string;
403
+ /** Enable error diagnostic logging to stderr. Does not enable prompt/response logging — use `debugPrompt` and `debugResponse` for that. */
405
404
  debug?: boolean;
405
+ /** Log prompts to stderr. */
406
+ debugPrompt?: boolean;
407
+ /** Log responses to stderr. */
408
+ debugResponse?: boolean;
406
409
  maxTokens?: number;
407
410
  reasoningEffort?: ReasoningEffort;
408
411
  trackUsage?: boolean;
@@ -458,7 +461,7 @@ interface ContentOptions {
458
461
  *
459
462
  * @example
460
463
  * ```ts
461
- * const client = visualAI({ provider: "openai" });
464
+ * const client = visualAI({ model: "gpt-5-mini" });
462
465
  * const result = await client.check("./tests/fixtures/small.png", "The button is visible");
463
466
  * ```
464
467
  */
@@ -608,7 +611,7 @@ interface VisualAIClient {
608
611
  /**
609
612
  * Creates a configured visual AI client.
610
613
  *
611
- * @param config Provider selection and runtime options for subsequent requests.
614
+ * @param config Model selection and runtime options for subsequent requests.
612
615
  * @returns A `VisualAIClient` instance with check, compare, ask, and template helpers.
613
616
  * @throws {VisualAIConfigError} When the provider or model configuration is invalid.
614
617
  * @throws {VisualAIAuthError} When required API credentials are missing.
@@ -619,7 +622,7 @@ interface VisualAIClient {
619
622
  *
620
623
  * test("hero loads correctly", async ({ page }) => {
621
624
  * const client = visualAI({
622
- * provider: "openai",
625
+ * model: "gpt-5-mini",
623
626
  * apiKey: process.env.OPENAI_API_KEY,
624
627
  * });
625
628
  *
@@ -836,4 +839,4 @@ declare function assertVisualResult(result: CheckResult, label?: string): void;
836
839
  */
837
840
  declare function assertVisualCompareResult(result: CompareResult, label?: string): void;
838
841
 
839
- export { Accessibility, type AccessibilityCheckName, type AccessibilityOptions, type AskOptions, type AskResult, AskResultSchema, type ChangeEntry, ChangeEntrySchema, type CheckOptions, type CheckResult, CheckResultSchema, type CompareOptions, type CompareResult, CompareResultSchema, type Confidence, ConfidenceSchema, Content, type ContentCheckName, type ContentOptions, DEFAULT_MODELS, type DiffImageResult, type ElementsVisibilityOptions, type ImageInput, type Issue, type IssueCategory, IssueCategorySchema, type IssuePriority, IssuePrioritySchema, IssueSchema, type KnownModelName, Layout, type LayoutCheckName, type LayoutOptions, Model, type PageLoadOptions, Provider, type ProviderName, type ReasoningEffort, type StatementResult, StatementResultSchema, type SupportedMimeType, type UsageInfo, UsageInfoSchema, VALID_PROVIDERS, VisualAIAssertionError, VisualAIAuthError, type VisualAIClient, type VisualAIConfig, VisualAIConfigError, VisualAIError, type VisualAIErrorCode, VisualAIImageError, type VisualAIKnownError, VisualAIProviderError, VisualAIRateLimitError, VisualAIResponseParseError, assertVisualCompareResult, assertVisualResult, formatCheckResult, formatCompareResult, isVisualAIKnownError, visualAI };
842
+ export { Accessibility, type AccessibilityCheckName, type AccessibilityOptions, type AskOptions, type AskResult, AskResultSchema, type ChangeEntry, ChangeEntrySchema, type CheckOptions, type CheckResult, CheckResultSchema, type CompareOptions, type CompareResult, CompareResultSchema, type Confidence, ConfidenceSchema, Content, type ContentCheckName, type ContentOptions, DEFAULT_MODELS, type DiffImageResult, type ElementsVisibilityOptions, type ImageInput, type Issue, type IssueCategory, IssueCategorySchema, type IssuePriority, IssuePrioritySchema, IssueSchema, type KnownModelName, Layout, type LayoutCheckName, type LayoutOptions, Model, type PageLoadOptions, Provider, type ProviderName, type ReasoningEffort, type StatementResult, StatementResultSchema, type SupportedMimeType, type UsageInfo, UsageInfoSchema, VisualAIAssertionError, VisualAIAuthError, type VisualAIClient, type VisualAIConfig, VisualAIConfigError, VisualAIError, type VisualAIErrorCode, VisualAIImageError, type VisualAIKnownError, VisualAIProviderError, VisualAIRateLimitError, VisualAIResponseParseError, assertVisualCompareResult, assertVisualResult, formatCheckResult, formatCompareResult, isVisualAIKnownError, visualAI };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /** Supported provider identifiers for configuring `visualAI()`. */
3
+ /** Supported provider identifiers used internally for pricing and provider selection. */
4
4
  declare const Provider: {
5
5
  readonly ANTHROPIC: "anthropic";
6
6
  readonly OPENAI: "openai";
@@ -16,6 +16,8 @@ declare const Model: {
16
16
  readonly OpenAI: {
17
17
  readonly GPT_5_4: "gpt-5.4";
18
18
  readonly GPT_5_4_PRO: "gpt-5.4-pro";
19
+ readonly GPT_5_4_MINI: "gpt-5.4-mini";
20
+ readonly GPT_5_4_NANO: "gpt-5.4-nano";
19
21
  readonly GPT_5_2: "gpt-5.2";
20
22
  readonly GPT_5_MINI: "gpt-5-mini";
21
23
  };
@@ -29,11 +31,9 @@ type KnownModelName = (typeof Model.Anthropic)[keyof typeof Model.Anthropic] | (
29
31
  /** Default model selection used when a caller omits `config.model`. */
30
32
  declare const DEFAULT_MODELS: {
31
33
  readonly anthropic: "claude-sonnet-4-6";
32
- readonly openai: "gpt-5-mini";
34
+ readonly openai: "gpt-5.4-mini";
33
35
  readonly google: "gemini-3-flash-preview";
34
36
  };
35
- /** List of accepted provider names for validation and public consumption. */
36
- declare const VALID_PROVIDERS: readonly ProviderName[];
37
37
  /** Built-in content checks available through `client.content()`. */
38
38
  declare const Content: {
39
39
  /** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */
@@ -392,17 +392,20 @@ type ReasoningEffort = "low" | "medium" | "high" | "xhigh";
392
392
  * @example
393
393
  * ```ts
394
394
  * const client = visualAI({
395
- * provider: "openai",
396
395
  * model: "gpt-5-mini",
397
396
  * apiKey: process.env.OPENAI_API_KEY,
398
397
  * });
399
398
  * ```
400
399
  */
401
400
  interface VisualAIConfig {
402
- provider?: ProviderName;
403
401
  apiKey?: string;
404
402
  model?: string;
403
+ /** Enable error diagnostic logging to stderr. Does not enable prompt/response logging — use `debugPrompt` and `debugResponse` for that. */
405
404
  debug?: boolean;
405
+ /** Log prompts to stderr. */
406
+ debugPrompt?: boolean;
407
+ /** Log responses to stderr. */
408
+ debugResponse?: boolean;
406
409
  maxTokens?: number;
407
410
  reasoningEffort?: ReasoningEffort;
408
411
  trackUsage?: boolean;
@@ -458,7 +461,7 @@ interface ContentOptions {
458
461
  *
459
462
  * @example
460
463
  * ```ts
461
- * const client = visualAI({ provider: "openai" });
464
+ * const client = visualAI({ model: "gpt-5-mini" });
462
465
  * const result = await client.check("./tests/fixtures/small.png", "The button is visible");
463
466
  * ```
464
467
  */
@@ -608,7 +611,7 @@ interface VisualAIClient {
608
611
  /**
609
612
  * Creates a configured visual AI client.
610
613
  *
611
- * @param config Provider selection and runtime options for subsequent requests.
614
+ * @param config Model selection and runtime options for subsequent requests.
612
615
  * @returns A `VisualAIClient` instance with check, compare, ask, and template helpers.
613
616
  * @throws {VisualAIConfigError} When the provider or model configuration is invalid.
614
617
  * @throws {VisualAIAuthError} When required API credentials are missing.
@@ -619,7 +622,7 @@ interface VisualAIClient {
619
622
  *
620
623
  * test("hero loads correctly", async ({ page }) => {
621
624
  * const client = visualAI({
622
- * provider: "openai",
625
+ * model: "gpt-5-mini",
623
626
  * apiKey: process.env.OPENAI_API_KEY,
624
627
  * });
625
628
  *
@@ -836,4 +839,4 @@ declare function assertVisualResult(result: CheckResult, label?: string): void;
836
839
  */
837
840
  declare function assertVisualCompareResult(result: CompareResult, label?: string): void;
838
841
 
839
- export { Accessibility, type AccessibilityCheckName, type AccessibilityOptions, type AskOptions, type AskResult, AskResultSchema, type ChangeEntry, ChangeEntrySchema, type CheckOptions, type CheckResult, CheckResultSchema, type CompareOptions, type CompareResult, CompareResultSchema, type Confidence, ConfidenceSchema, Content, type ContentCheckName, type ContentOptions, DEFAULT_MODELS, type DiffImageResult, type ElementsVisibilityOptions, type ImageInput, type Issue, type IssueCategory, IssueCategorySchema, type IssuePriority, IssuePrioritySchema, IssueSchema, type KnownModelName, Layout, type LayoutCheckName, type LayoutOptions, Model, type PageLoadOptions, Provider, type ProviderName, type ReasoningEffort, type StatementResult, StatementResultSchema, type SupportedMimeType, type UsageInfo, UsageInfoSchema, VALID_PROVIDERS, VisualAIAssertionError, VisualAIAuthError, type VisualAIClient, type VisualAIConfig, VisualAIConfigError, VisualAIError, type VisualAIErrorCode, VisualAIImageError, type VisualAIKnownError, VisualAIProviderError, VisualAIRateLimitError, VisualAIResponseParseError, assertVisualCompareResult, assertVisualResult, formatCheckResult, formatCompareResult, isVisualAIKnownError, visualAI };
842
+ export { Accessibility, type AccessibilityCheckName, type AccessibilityOptions, type AskOptions, type AskResult, AskResultSchema, type ChangeEntry, ChangeEntrySchema, type CheckOptions, type CheckResult, CheckResultSchema, type CompareOptions, type CompareResult, CompareResultSchema, type Confidence, ConfidenceSchema, Content, type ContentCheckName, type ContentOptions, DEFAULT_MODELS, type DiffImageResult, type ElementsVisibilityOptions, type ImageInput, type Issue, type IssueCategory, IssueCategorySchema, type IssuePriority, IssuePrioritySchema, IssueSchema, type KnownModelName, Layout, type LayoutCheckName, type LayoutOptions, Model, type PageLoadOptions, Provider, type ProviderName, type ReasoningEffort, type StatementResult, StatementResultSchema, type SupportedMimeType, type UsageInfo, UsageInfoSchema, VisualAIAssertionError, VisualAIAuthError, type VisualAIClient, type VisualAIConfig, VisualAIConfigError, VisualAIError, type VisualAIErrorCode, VisualAIImageError, type VisualAIKnownError, VisualAIProviderError, VisualAIRateLimitError, VisualAIResponseParseError, assertVisualCompareResult, assertVisualResult, formatCheckResult, formatCompareResult, isVisualAIKnownError, visualAI };
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@ var Model = {
13
13
  OpenAI: {
14
14
  GPT_5_4: "gpt-5.4",
15
15
  GPT_5_4_PRO: "gpt-5.4-pro",
16
+ GPT_5_4_MINI: "gpt-5.4-mini",
17
+ GPT_5_4_NANO: "gpt-5.4-nano",
16
18
  GPT_5_2: "gpt-5.2",
17
19
  GPT_5_MINI: "gpt-5-mini"
18
20
  },
@@ -23,7 +25,7 @@ var Model = {
23
25
  };
24
26
  var DEFAULT_MODELS = {
25
27
  [Provider.ANTHROPIC]: Model.Anthropic.SONNET_4_6,
26
- [Provider.OPENAI]: Model.OpenAI.GPT_5_MINI,
28
+ [Provider.OPENAI]: Model.OpenAI.GPT_5_4_MINI,
27
29
  [Provider.GOOGLE]: Model.Google.GEMINI_3_FLASH_PREVIEW
28
30
  };
29
31
  var DEFAULT_MAX_TOKENS = 4096;
@@ -33,6 +35,11 @@ var MODEL_TO_PROVIDER = new Map([
33
35
  ...Object.values(Model.Google).map((m) => [m, Provider.GOOGLE])
34
36
  ]);
35
37
  var VALID_PROVIDERS = Object.values(Provider);
38
+ var PROVIDER_DEFAULT_REASONING = {
39
+ openai: "medium",
40
+ anthropic: "off",
41
+ google: "off"
42
+ };
36
43
  var Content = {
37
44
  /** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */
38
45
  PLACEHOLDER_TEXT: "placeholder-text",
@@ -660,23 +667,6 @@ function inferProviderFromModel(model) {
660
667
  return prefixMatch?.[1];
661
668
  }
662
669
  function resolveProvider(config) {
663
- if (config.provider) {
664
- if (!VALID_PROVIDERS.includes(config.provider)) {
665
- throw new VisualAIConfigError(
666
- `Unknown provider: "${config.provider}". Supported: ${VALID_PROVIDERS.join(", ")}`
667
- );
668
- }
669
- return config.provider;
670
- }
671
- const envProvider = process.env.VISUAL_AI_PROVIDER;
672
- if (envProvider) {
673
- if (VALID_PROVIDERS.includes(envProvider)) {
674
- return envProvider;
675
- }
676
- throw new VisualAIConfigError(
677
- `Invalid VISUAL_AI_PROVIDER: "${envProvider}". Supported: ${VALID_PROVIDERS.join(", ")}`
678
- );
679
- }
680
670
  const model = config.model ?? process.env.VISUAL_AI_MODEL;
681
671
  if (model) {
682
672
  const inferred = inferProviderFromModel(model);
@@ -690,7 +680,7 @@ function resolveProvider(config) {
690
680
  const detected = apiKeyProviderMap.find(([key]) => process.env[key]);
691
681
  if (detected) return detected[1];
692
682
  throw new VisualAIConfigError(
693
- "No provider specified. Set `provider` in config, `VISUAL_AI_PROVIDER` env variable, or an API key env variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY)."
683
+ "Cannot determine provider. Set a model name (config or VISUAL_AI_MODEL) or an API key env variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY)."
694
684
  );
695
685
  }
696
686
  function parseBooleanEnv(envName, value) {
@@ -702,13 +692,27 @@ function parseBooleanEnv(envName, value) {
702
692
  `Invalid ${envName} value: "${value}". Use "true", "1", "false", or "0".`
703
693
  );
704
694
  }
695
+ var VALID_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
696
+ function parseReasoningEffortEnv(envName, value) {
697
+ if (value === void 0 || value === "") return void 0;
698
+ const lower = value.toLowerCase();
699
+ if (VALID_REASONING_EFFORTS.includes(lower)) return lower;
700
+ throw new VisualAIConfigError(
701
+ `Invalid ${envName} value: "${value}". Use "low", "medium", "high", or "xhigh".`
702
+ );
703
+ }
704
+ var debugDeprecationWarned = false;
705
705
  function resolveConfig(config) {
706
706
  const provider = resolveProvider(config);
707
707
  const model = config.model ?? process.env.VISUAL_AI_MODEL ?? DEFAULT_MODELS[provider];
708
- const modelProvider = inferProviderFromModel(model);
709
- if (modelProvider && modelProvider !== provider) {
710
- throw new VisualAIConfigError(
711
- `Model "${model}" appears to be a ${modelProvider} model, but provider is "${provider}". Either change the provider or use a ${provider}-compatible model.`
708
+ const debug = config.debug ?? parseBooleanEnv("VISUAL_AI_DEBUG", process.env.VISUAL_AI_DEBUG) ?? false;
709
+ const debugPrompt = config.debugPrompt ?? parseBooleanEnv("VISUAL_AI_DEBUG_PROMPT", process.env.VISUAL_AI_DEBUG_PROMPT) ?? false;
710
+ const debugResponse = config.debugResponse ?? parseBooleanEnv("VISUAL_AI_DEBUG_RESPONSE", process.env.VISUAL_AI_DEBUG_RESPONSE) ?? false;
711
+ if (debug && !debugPrompt && !debugResponse && !debugDeprecationWarned) {
712
+ debugDeprecationWarned = true;
713
+ process.stderr.write(
714
+ `[visual-ai-assertions] Warning: VISUAL_AI_DEBUG no longer enables prompt/response logging. Use VISUAL_AI_DEBUG_PROMPT=true and/or VISUAL_AI_DEBUG_RESPONSE=true instead.
715
+ `
712
716
  );
713
717
  }
714
718
  return {
@@ -716,8 +720,10 @@ function resolveConfig(config) {
716
720
  apiKey: config.apiKey,
717
721
  model,
718
722
  maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
719
- reasoningEffort: config.reasoningEffort,
720
- debug: config.debug ?? parseBooleanEnv("VISUAL_AI_DEBUG", process.env.VISUAL_AI_DEBUG) ?? false,
723
+ reasoningEffort: config.reasoningEffort ?? parseReasoningEffortEnv("VISUAL_AI_REASONING_EFFORT", process.env.VISUAL_AI_REASONING_EFFORT),
724
+ debug,
725
+ debugPrompt,
726
+ debugResponse,
721
727
  trackUsage: config.trackUsage ?? parseBooleanEnv("VISUAL_AI_TRACK_USAGE", process.env.VISUAL_AI_TRACK_USAGE) ?? false
722
728
  };
723
729
  }
@@ -749,6 +755,14 @@ var PRICING_TABLE = {
749
755
  inputPricePerToken: 1.75 / PER_MILLION,
750
756
  outputPricePerToken: 14 / PER_MILLION
751
757
  },
758
+ [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_MINI}`]: {
759
+ inputPricePerToken: 0.75 / PER_MILLION,
760
+ outputPricePerToken: 4.5 / PER_MILLION
761
+ },
762
+ [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_NANO}`]: {
763
+ inputPricePerToken: 0.2 / PER_MILLION,
764
+ outputPricePerToken: 1.25 / PER_MILLION
765
+ },
752
766
  [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_MINI}`]: {
753
767
  inputPricePerToken: 0.25 / PER_MILLION,
754
768
  outputPricePerToken: 2 / PER_MILLION
@@ -770,8 +784,9 @@ function calculateCost(provider, model, inputTokens, outputTokens) {
770
784
  }
771
785
 
772
786
  // src/core/debug.ts
773
- function debugLog(config, label, data) {
774
- if (config.debug) {
787
+ function debugLog(config, label, data, kind = "error") {
788
+ const enabled = kind === "prompt" ? config.debugPrompt : kind === "response" ? config.debugResponse : config.debug;
789
+ if (enabled) {
775
790
  process.stderr.write(`[visual-ai-assertions] ${label}: ${data}
776
791
  `);
777
792
  }
@@ -779,8 +794,9 @@ function debugLog(config, label, data) {
779
794
  function usageLog(config, method, usage) {
780
795
  if (!config.trackUsage) return;
781
796
  const costStr = usage.estimatedCost !== void 0 ? `$${usage.estimatedCost.toFixed(6)}` : "unknown";
797
+ const reasoningStr = config.reasoningEffort ? `reasoning: ${config.reasoningEffort}` : `reasoning: ${PROVIDER_DEFAULT_REASONING[config.provider]} (provider default)`;
782
798
  process.stderr.write(
783
- `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? "0.000"}s [${config.model}]
799
+ `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? "0.000"}s [${config.model}, ${reasoningStr}]
784
800
  `
785
801
  );
786
802
  }
@@ -796,6 +812,28 @@ function processUsage(method, rawUsage, durationSeconds, config) {
796
812
  usageLog(config, method, usage);
797
813
  return usage;
798
814
  }
815
+ var MAX_RAW_RESPONSE_PREVIEW = 500;
816
+ function formatError(error) {
817
+ if (error instanceof VisualAIResponseParseError) {
818
+ const truncated = error.rawResponse.length > MAX_RAW_RESPONSE_PREVIEW ? error.rawResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + "..." : error.rawResponse;
819
+ return `${error.name} (${error.code}): ${error.message}. Raw (truncated): ${truncated}`;
820
+ }
821
+ if (error instanceof VisualAIError) {
822
+ return `${error.name} (${error.code}): ${error.message}`;
823
+ }
824
+ if (error instanceof Error) {
825
+ return `${error.name}: ${error.message}`;
826
+ }
827
+ return String(error);
828
+ }
829
+ async function withErrorDebug(config, method, fn) {
830
+ try {
831
+ return await fn();
832
+ } catch (error) {
833
+ debugLog(config, `${method} error`, formatError(error), "error");
834
+ throw error;
835
+ }
836
+ }
799
837
  async function timedSendMessage(driver, images, prompt) {
800
838
  const start = performance.now();
801
839
  const response = await driver.sendMessage(images, prompt);
@@ -1124,16 +1162,18 @@ function visualAI(config = {}) {
1124
1162
  if (elements.length === 0) {
1125
1163
  throw new VisualAIConfigError(`At least one element is required for ${methodName}()`);
1126
1164
  }
1127
- const img = await normalizeImage(image);
1128
- const prompt = buildElementsVisibilityPrompt(elements, visible, options);
1129
- debugLog(resolvedConfig, `${methodName} prompt`, prompt);
1130
- const response = await timedSendMessage(driver, [img], prompt);
1131
- debugLog(resolvedConfig, `${methodName} response`, response.text);
1132
- const result = parseCheckResponse(response.text);
1133
- return {
1134
- ...result,
1135
- usage: processUsage(methodName, response.usage, response.durationSeconds, resolvedConfig)
1136
- };
1165
+ return withErrorDebug(resolvedConfig, methodName, async () => {
1166
+ const img = await normalizeImage(image);
1167
+ const prompt = buildElementsVisibilityPrompt(elements, visible, options);
1168
+ debugLog(resolvedConfig, `${methodName} prompt`, prompt, "prompt");
1169
+ const response = await timedSendMessage(driver, [img], prompt);
1170
+ debugLog(resolvedConfig, `${methodName} response`, response.text, "response");
1171
+ const result = parseCheckResponse(response.text);
1172
+ return {
1173
+ ...result,
1174
+ usage: processUsage(methodName, response.usage, response.durationSeconds, resolvedConfig)
1175
+ };
1176
+ });
1137
1177
  }
1138
1178
  return {
1139
1179
  async check(image, statements, options) {
@@ -1141,61 +1181,64 @@ function visualAI(config = {}) {
1141
1181
  if (stmts.length === 0) {
1142
1182
  throw new VisualAIConfigError("At least one statement is required for check()");
1143
1183
  }
1144
- const img = await normalizeImage(image);
1145
- const prompt = buildCheckPrompt(stmts, { instructions: options?.instructions });
1146
- debugLog(resolvedConfig, "check prompt", prompt);
1147
- const response = await timedSendMessage(driver, [img], prompt);
1148
- debugLog(resolvedConfig, "check response", response.text);
1149
- const result = parseCheckResponse(response.text);
1150
- return {
1151
- ...result,
1152
- usage: processUsage("check", response.usage, response.durationSeconds, resolvedConfig)
1153
- };
1184
+ return withErrorDebug(resolvedConfig, "check", async () => {
1185
+ const img = await normalizeImage(image);
1186
+ const prompt = buildCheckPrompt(stmts, { instructions: options?.instructions });
1187
+ debugLog(resolvedConfig, "check prompt", prompt, "prompt");
1188
+ const response = await timedSendMessage(driver, [img], prompt);
1189
+ debugLog(resolvedConfig, "check response", response.text, "response");
1190
+ const result = parseCheckResponse(response.text);
1191
+ return {
1192
+ ...result,
1193
+ usage: processUsage("check", response.usage, response.durationSeconds, resolvedConfig)
1194
+ };
1195
+ });
1154
1196
  },
1155
1197
  async ask(image, userPrompt, options) {
1156
- const img = await normalizeImage(image);
1157
- const prompt = buildAskPrompt(userPrompt, { instructions: options?.instructions });
1158
- debugLog(resolvedConfig, "ask prompt", prompt);
1159
- const response = await timedSendMessage(driver, [img], prompt);
1160
- debugLog(resolvedConfig, "ask response", response.text);
1161
- const result = parseAskResponse(response.text);
1162
- return {
1163
- ...result,
1164
- usage: processUsage("ask", response.usage, response.durationSeconds, resolvedConfig)
1165
- };
1198
+ return withErrorDebug(resolvedConfig, "ask", async () => {
1199
+ const img = await normalizeImage(image);
1200
+ const prompt = buildAskPrompt(userPrompt, { instructions: options?.instructions });
1201
+ debugLog(resolvedConfig, "ask prompt", prompt, "prompt");
1202
+ const response = await timedSendMessage(driver, [img], prompt);
1203
+ debugLog(resolvedConfig, "ask response", response.text, "response");
1204
+ const result = parseAskResponse(response.text);
1205
+ return {
1206
+ ...result,
1207
+ usage: processUsage("ask", response.usage, response.durationSeconds, resolvedConfig)
1208
+ };
1209
+ });
1166
1210
  },
1167
1211
  async compare(imageA, imageB, options) {
1168
- const [imgA, imgB] = await Promise.all([normalizeImage(imageA), normalizeImage(imageB)]);
1169
- const prompt = buildComparePrompt({
1170
- userPrompt: options?.prompt,
1171
- instructions: options?.instructions
1172
- });
1173
- debugLog(resolvedConfig, "compare prompt", prompt);
1174
- const response = await timedSendMessage(driver, [imgA, imgB], prompt);
1175
- debugLog(resolvedConfig, "compare response", response.text);
1176
- const supportsAnnotatedDiff = resolvedConfig.provider === "google" && resolvedConfig.model === Model.Google.GEMINI_3_FLASH_PREVIEW;
1177
- const effectiveDiffImage = options?.diffImage ?? (supportsAnnotatedDiff ? true : false);
1178
- let diffImage;
1179
- if (effectiveDiffImage) {
1180
- try {
1181
- diffImage = await generateAiDiff(imgA, imgB, resolvedConfig.model, driver);
1182
- } catch (err) {
1183
- const msg = err instanceof Error ? err.message : String(err);
1184
- debugLog(resolvedConfig, "ai diff error", msg);
1185
- if (!resolvedConfig.debug) {
1212
+ return withErrorDebug(resolvedConfig, "compare", async () => {
1213
+ const [imgA, imgB] = await Promise.all([normalizeImage(imageA), normalizeImage(imageB)]);
1214
+ const prompt = buildComparePrompt({
1215
+ userPrompt: options?.prompt,
1216
+ instructions: options?.instructions
1217
+ });
1218
+ debugLog(resolvedConfig, "compare prompt", prompt, "prompt");
1219
+ const response = await timedSendMessage(driver, [imgA, imgB], prompt);
1220
+ debugLog(resolvedConfig, "compare response", response.text, "response");
1221
+ const supportsAnnotatedDiff = resolvedConfig.provider === "google" && resolvedConfig.model === Model.Google.GEMINI_3_FLASH_PREVIEW;
1222
+ const effectiveDiffImage = options?.diffImage ?? (supportsAnnotatedDiff ? true : false);
1223
+ let diffImage;
1224
+ if (effectiveDiffImage) {
1225
+ try {
1226
+ diffImage = await generateAiDiff(imgA, imgB, resolvedConfig.model, driver);
1227
+ } catch (err) {
1228
+ const msg = err instanceof Error ? err.message : String(err);
1186
1229
  process.stderr.write(
1187
1230
  `[visual-ai-assertions] warning: diff generation failed: ${msg}
1188
1231
  `
1189
1232
  );
1190
1233
  }
1191
1234
  }
1192
- }
1193
- const result = parseCompareResponse(response.text);
1194
- return {
1195
- ...result,
1196
- ...diffImage ? { diffImage } : {},
1197
- usage: processUsage("compare", response.usage, response.durationSeconds, resolvedConfig)
1198
- };
1235
+ const result = parseCompareResponse(response.text);
1236
+ return {
1237
+ ...result,
1238
+ ...diffImage ? { diffImage } : {},
1239
+ usage: processUsage("compare", response.usage, response.durationSeconds, resolvedConfig)
1240
+ };
1241
+ });
1199
1242
  },
1200
1243
  elementsVisible(image, elements, options) {
1201
1244
  return checkElementsVisibility(image, elements, true, options);
@@ -1204,57 +1247,65 @@ function visualAI(config = {}) {
1204
1247
  return checkElementsVisibility(image, elements, false, options);
1205
1248
  },
1206
1249
  async accessibility(image, options) {
1207
- const img = await normalizeImage(image);
1208
- const prompt = buildAccessibilityPrompt(options);
1209
- debugLog(resolvedConfig, "accessibility prompt", prompt);
1210
- const response = await timedSendMessage(driver, [img], prompt);
1211
- debugLog(resolvedConfig, "accessibility response", response.text);
1212
- const result = parseCheckResponse(response.text);
1213
- return {
1214
- ...result,
1215
- usage: processUsage(
1216
- "accessibility",
1217
- response.usage,
1218
- response.durationSeconds,
1219
- resolvedConfig
1220
- )
1221
- };
1250
+ return withErrorDebug(resolvedConfig, "accessibility", async () => {
1251
+ const img = await normalizeImage(image);
1252
+ const prompt = buildAccessibilityPrompt(options);
1253
+ debugLog(resolvedConfig, "accessibility prompt", prompt, "prompt");
1254
+ const response = await timedSendMessage(driver, [img], prompt);
1255
+ debugLog(resolvedConfig, "accessibility response", response.text, "response");
1256
+ const result = parseCheckResponse(response.text);
1257
+ return {
1258
+ ...result,
1259
+ usage: processUsage(
1260
+ "accessibility",
1261
+ response.usage,
1262
+ response.durationSeconds,
1263
+ resolvedConfig
1264
+ )
1265
+ };
1266
+ });
1222
1267
  },
1223
1268
  async layout(image, options) {
1224
- const img = await normalizeImage(image);
1225
- const prompt = buildLayoutPrompt(options);
1226
- debugLog(resolvedConfig, "layout prompt", prompt);
1227
- const response = await timedSendMessage(driver, [img], prompt);
1228
- debugLog(resolvedConfig, "layout response", response.text);
1229
- const result = parseCheckResponse(response.text);
1230
- return {
1231
- ...result,
1232
- usage: processUsage("layout", response.usage, response.durationSeconds, resolvedConfig)
1233
- };
1269
+ return withErrorDebug(resolvedConfig, "layout", async () => {
1270
+ const img = await normalizeImage(image);
1271
+ const prompt = buildLayoutPrompt(options);
1272
+ debugLog(resolvedConfig, "layout prompt", prompt, "prompt");
1273
+ const response = await timedSendMessage(driver, [img], prompt);
1274
+ debugLog(resolvedConfig, "layout response", response.text, "response");
1275
+ const result = parseCheckResponse(response.text);
1276
+ return {
1277
+ ...result,
1278
+ usage: processUsage("layout", response.usage, response.durationSeconds, resolvedConfig)
1279
+ };
1280
+ });
1234
1281
  },
1235
1282
  async pageLoad(image, options) {
1236
- const img = await normalizeImage(image);
1237
- const prompt = buildPageLoadPrompt(options);
1238
- debugLog(resolvedConfig, "pageLoad prompt", prompt);
1239
- const response = await timedSendMessage(driver, [img], prompt);
1240
- debugLog(resolvedConfig, "pageLoad response", response.text);
1241
- const result = parseCheckResponse(response.text);
1242
- return {
1243
- ...result,
1244
- usage: processUsage("pageLoad", response.usage, response.durationSeconds, resolvedConfig)
1245
- };
1283
+ return withErrorDebug(resolvedConfig, "pageLoad", async () => {
1284
+ const img = await normalizeImage(image);
1285
+ const prompt = buildPageLoadPrompt(options);
1286
+ debugLog(resolvedConfig, "pageLoad prompt", prompt, "prompt");
1287
+ const response = await timedSendMessage(driver, [img], prompt);
1288
+ debugLog(resolvedConfig, "pageLoad response", response.text, "response");
1289
+ const result = parseCheckResponse(response.text);
1290
+ return {
1291
+ ...result,
1292
+ usage: processUsage("pageLoad", response.usage, response.durationSeconds, resolvedConfig)
1293
+ };
1294
+ });
1246
1295
  },
1247
1296
  async content(image, options) {
1248
- const img = await normalizeImage(image);
1249
- const prompt = buildContentPrompt(options);
1250
- debugLog(resolvedConfig, "content prompt", prompt);
1251
- const response = await timedSendMessage(driver, [img], prompt);
1252
- debugLog(resolvedConfig, "content response", response.text);
1253
- const result = parseCheckResponse(response.text);
1254
- return {
1255
- ...result,
1256
- usage: processUsage("content", response.usage, response.durationSeconds, resolvedConfig)
1257
- };
1297
+ return withErrorDebug(resolvedConfig, "content", async () => {
1298
+ const img = await normalizeImage(image);
1299
+ const prompt = buildContentPrompt(options);
1300
+ debugLog(resolvedConfig, "content prompt", prompt, "prompt");
1301
+ const response = await timedSendMessage(driver, [img], prompt);
1302
+ debugLog(resolvedConfig, "content response", response.text, "response");
1303
+ const result = parseCheckResponse(response.text);
1304
+ return {
1305
+ ...result,
1306
+ usage: processUsage("content", response.usage, response.durationSeconds, resolvedConfig)
1307
+ };
1308
+ });
1258
1309
  }
1259
1310
  };
1260
1311
  }
@@ -1330,7 +1381,6 @@ export {
1330
1381
  Provider,
1331
1382
  StatementResultSchema,
1332
1383
  UsageInfoSchema,
1333
- VALID_PROVIDERS,
1334
1384
  VisualAIAssertionError,
1335
1385
  VisualAIAuthError,
1336
1386
  VisualAIConfigError,