visual-ai-assertions 0.7.2 → 0.9.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/README.md +69 -16
- package/dist/index.cjs +588 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -25
- package/dist/index.d.ts +178 -25
- package/dist/index.js +587 -52
- package/dist/index.js.map +1 -1
- package/package.json +31 -21
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/core/prompt.ts","../src/templates/elements-visibility.ts","../src/templates/accessibility.ts","../src/templates/layout.ts","../src/templates/page-load.ts","../src/templates/content.ts","../src/providers/error-mapper.ts","../src/providers/anthropic.ts","../src/providers/google.ts","../src/providers/openai.ts","../src/core/config.ts","../src/core/pricing.ts","../src/core/debug.ts","../src/core/diff.ts","../src/core/image.ts","../src/types.ts","../src/core/response.ts","../src/core/client.ts","../src/format.ts"],"sourcesContent":["import type { ProviderName } from \"./types.js\";\n\n// --- Reasoning effort constants ---\n\n/** Supported reasoning effort levels. */\nexport const ReasoningEffort = {\n LOW: \"low\",\n MEDIUM: \"medium\",\n HIGH: \"high\",\n XHIGH: \"xhigh\",\n} as const;\n\n/** Union of valid reasoning effort values, derived from the ReasoningEffort constant. */\nexport type ReasoningEffortLevel = (typeof ReasoningEffort)[keyof typeof ReasoningEffort];\n\n// --- Provider constants ---\n\n/** Supported provider identifiers used internally for pricing and provider selection. */\nexport const Provider = {\n ANTHROPIC: \"anthropic\",\n OPENAI: \"openai\",\n GOOGLE: \"google\",\n} as const satisfies Record<string, ProviderName>;\n\n// --- Model constants (grouped by provider) ---\n\n/** Known model names grouped by provider. */\nexport const Model = {\n Anthropic: {\n OPUS_4_6: \"claude-opus-4-6\",\n SONNET_4_6: \"claude-sonnet-4-6\",\n HAIKU_4_5: \"claude-haiku-4-5\",\n },\n OpenAI: {\n GPT_5_4: \"gpt-5.4\",\n GPT_5_4_PRO: \"gpt-5.4-pro\",\n GPT_5_4_MINI: \"gpt-5.4-mini\",\n GPT_5_4_NANO: \"gpt-5.4-nano\",\n GPT_5_2: \"gpt-5.2\",\n GPT_5_MINI: \"gpt-5-mini\",\n },\n Google: {\n GEMINI_3_1_PRO_PREVIEW: \"gemini-3.1-pro-preview\",\n GEMINI_3_1_FLASH_LITE_PREVIEW: \"gemini-3.1-flash-lite-preview\",\n GEMINI_3_FLASH_PREVIEW: \"gemini-3-flash-preview\",\n },\n} as const;\n\n// --- Derived utility types ---\n\n/** Union of all built-in model name literals exposed by `Model`. */\nexport type KnownModelName =\n | (typeof Model.Anthropic)[keyof typeof Model.Anthropic]\n | (typeof Model.OpenAI)[keyof typeof Model.OpenAI]\n | (typeof Model.Google)[keyof typeof Model.Google];\n\n// --- Default model per provider ---\n\n/** Default model selection used when a caller omits `config.model`. */\nexport const DEFAULT_MODELS = {\n [Provider.ANTHROPIC]: Model.Anthropic.SONNET_4_6,\n [Provider.OPENAI]: Model.OpenAI.GPT_5_4_MINI,\n [Provider.GOOGLE]: Model.Google.GEMINI_3_FLASH_PREVIEW,\n} as const satisfies Record<ProviderName, KnownModelName>;\n\nexport const DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Increased token budget for OpenAI when reasoning effort is high/xhigh.\n * Reasoning tokens share the output budget on OpenAI, so the default 4096\n * is insufficient for higher reasoning levels.\n */\nexport const OPENAI_REASONING_MAX_TOKENS = 16384;\n\n// --- Reverse map: model → provider ---\n\nexport const MODEL_TO_PROVIDER: ReadonlyMap<string, ProviderName> = new Map([\n ...Object.values(Model.Anthropic).map((m) => [m, Provider.ANTHROPIC] as const),\n ...Object.values(Model.OpenAI).map((m) => [m, Provider.OPENAI] as const),\n ...Object.values(Model.Google).map((m) => [m, Provider.GOOGLE] as const),\n]);\n\n// --- Valid providers array ---\n\n/** List of accepted provider names for validation and public consumption. */\nexport const VALID_PROVIDERS: readonly ProviderName[] = Object.values(Provider);\n\n// --- Provider default reasoning ---\n\n/**\n * What each provider uses when no reasoning effort is explicitly requested.\n * These are informational only — displayed in usage logs, not sent to providers.\n */\nexport const PROVIDER_DEFAULT_REASONING: Readonly<Record<ProviderName, string>> = {\n openai: \"medium\",\n anthropic: \"off\",\n google: \"off\",\n};\n\n// --- Check name constants ---\n\n/** Built-in content checks available through `client.content()`. */\nexport const Content = {\n /** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */\n PLACEHOLDER_TEXT: \"placeholder-text\",\n /** Detects error messages, banners, stack traces, or error codes */\n ERROR_MESSAGES: \"error-messages\",\n /** Detects broken image icons or failed-to-load image indicators */\n BROKEN_IMAGES: \"broken-images\",\n /** Detects UI elements that unintentionally overlap and obscure content */\n OVERLAPPING_ELEMENTS: \"overlapping-elements\",\n} as const;\n\n/** Built-in layout checks available through `client.layout()`. */\nexport const Layout = {\n /** Detects elements that unintentionally overlap each other */\n OVERLAP: \"overlap\",\n /** Detects content cut off or extending beyond container boundaries */\n OVERFLOW: \"overflow\",\n /** Detects inconsistent alignment of text, images, and UI components */\n ALIGNMENT: \"alignment\",\n} as const;\n\n/** Built-in accessibility checks available through `client.accessibility()`. */\nexport const Accessibility = {\n /** Detects insufficient color contrast between text and backgrounds */\n CONTRAST: \"contrast\",\n /** Detects text that is cut off, overlapping, too small, or obscured */\n READABILITY: \"readability\",\n /** Detects interactive elements that are not visually distinct */\n INTERACTIVE_VISIBILITY: \"interactive-visibility\",\n} as const;\n\n// --- Derived check-name union types ---\n\n/** Union of all built-in content check names. */\nexport type ContentCheckName = (typeof Content)[keyof typeof Content];\n/** Union of all built-in layout check names. */\nexport type LayoutCheckName = (typeof Layout)[keyof typeof Layout];\n/** Union of all built-in accessibility check names. */\nexport type AccessibilityCheckName = (typeof Accessibility)[keyof typeof Accessibility];\n","import type { CheckResult, CompareResult } from \"./types.js\";\n\n/**\n * Discrete error codes exposed by visual-ai-assertions for programmatic handling.\n */\nexport type VisualAIErrorCode =\n | \"VISUAL_AI_ERROR\"\n | \"AUTH_FAILED\"\n | \"RATE_LIMITED\"\n | \"PROVIDER_ERROR\"\n | \"IMAGE_INVALID\"\n | \"RESPONSE_PARSE_FAILED\"\n | \"RESPONSE_TRUNCATED\"\n | \"CONFIG_INVALID\"\n | \"ASSERTION_FAILED\";\n\n/**\n * Base class for all library errors.\n *\n * @example\n * ```ts\n * try {\n * // ...\n * } catch (error) {\n * if (error instanceof VisualAIError) {\n * console.error(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class VisualAIError<TCode extends VisualAIErrorCode = VisualAIErrorCode> extends Error {\n readonly code: TCode;\n\n constructor(message: string, code: TCode = \"VISUAL_AI_ERROR\" as TCode) {\n super(message);\n this.code = code;\n this.name = \"VisualAIError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a provider rejects the configured API credentials.\n *\n * @example\n * ```ts\n * throw new VisualAIAuthError(\"Anthropic API key not found\");\n * ```\n */\nexport class VisualAIAuthError extends VisualAIError<\"AUTH_FAILED\"> {\n declare readonly code: \"AUTH_FAILED\";\n\n constructor(message: string) {\n super(message, \"AUTH_FAILED\");\n this.name = \"VisualAIAuthError\";\n }\n}\n\n/**\n * Thrown when a provider enforces a rate limit.\n *\n * Carries `retryAfter` when the provider includes retry guidance.\n *\n * @example\n * ```ts\n * throw new VisualAIRateLimitError(\"Rate limited\", 30);\n * ```\n */\nexport class VisualAIRateLimitError extends VisualAIError<\"RATE_LIMITED\"> {\n declare readonly code: \"RATE_LIMITED\";\n retryAfter?: number;\n\n constructor(message: string, retryAfter?: number) {\n super(message, \"RATE_LIMITED\");\n this.name = \"VisualAIRateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Thrown when a provider returns an unexpected non-auth, non-rate-limit failure.\n *\n * Carries `statusCode` when the provider exposes an HTTP status.\n *\n * @example\n * ```ts\n * throw new VisualAIProviderError(\"Provider returned 500\", 500);\n * ```\n */\nexport class VisualAIProviderError extends VisualAIError<\"PROVIDER_ERROR\"> {\n declare readonly code: \"PROVIDER_ERROR\";\n statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message, \"PROVIDER_ERROR\");\n this.name = \"VisualAIProviderError\";\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Thrown when an image input cannot be loaded, decoded, or validated.\n *\n * @example\n * ```ts\n * throw new VisualAIImageError(\"Unsupported image format: image/bmp\");\n * ```\n */\nexport class VisualAIImageError extends VisualAIError<\"IMAGE_INVALID\"> {\n declare readonly code: \"IMAGE_INVALID\";\n\n constructor(message: string) {\n super(message, \"IMAGE_INVALID\");\n this.name = \"VisualAIImageError\";\n }\n}\n\n/**\n * Thrown when a provider response cannot be parsed into the library result schema.\n *\n * Carries `rawResponse` so callers can inspect the original model output.\n *\n * @example\n * ```ts\n * throw new VisualAIResponseParseError(\"Invalid JSON\", rawText);\n * ```\n */\nexport class VisualAIResponseParseError extends VisualAIError<\"RESPONSE_PARSE_FAILED\"> {\n declare readonly code: \"RESPONSE_PARSE_FAILED\";\n rawResponse: string;\n\n constructor(message: string, rawResponse: string) {\n super(message, \"RESPONSE_PARSE_FAILED\");\n this.name = \"VisualAIResponseParseError\";\n this.rawResponse = rawResponse;\n }\n}\n\n/**\n * Thrown when a provider response is truncated due to token budget exhaustion.\n *\n * Carries `partialResponse` and `maxTokens` so callers can inspect the output\n * and adjust configuration.\n *\n * @example\n * ```ts\n * throw new VisualAITruncationError(\"Response truncated\", partialText, 4096);\n * ```\n */\nexport class VisualAITruncationError extends VisualAIError<\"RESPONSE_TRUNCATED\"> {\n declare readonly code: \"RESPONSE_TRUNCATED\";\n readonly partialResponse: string;\n readonly maxTokens: number;\n\n constructor(message: string, partialResponse: string, maxTokens: number) {\n super(message, \"RESPONSE_TRUNCATED\");\n this.name = \"VisualAITruncationError\";\n this.partialResponse = partialResponse;\n this.maxTokens = maxTokens;\n }\n}\n\n/**\n * Thrown when library configuration is missing or invalid.\n *\n * @example\n * ```ts\n * throw new VisualAIConfigError(\"At least one statement is required for check()\");\n * ```\n */\nexport class VisualAIConfigError extends VisualAIError<\"CONFIG_INVALID\"> {\n declare readonly code: \"CONFIG_INVALID\";\n\n constructor(message: string) {\n super(message, \"CONFIG_INVALID\");\n this.name = \"VisualAIConfigError\";\n }\n}\n\n/**\n * Thrown by assertion helpers when a visual check or comparison fails.\n *\n * Carries the failed `result` for further inspection.\n *\n * @example\n * ```ts\n * throw new VisualAIAssertionError(\"Visual assertion failed\", result);\n * ```\n */\nexport class VisualAIAssertionError extends VisualAIError<\"ASSERTION_FAILED\"> {\n declare readonly code: \"ASSERTION_FAILED\";\n result: CheckResult | CompareResult;\n\n constructor(message: string, result: CheckResult | CompareResult) {\n super(message, \"ASSERTION_FAILED\");\n this.name = \"VisualAIAssertionError\";\n this.result = result;\n }\n}\n\n/**\n * Union of all concrete error subclasses exposed by the library.\n */\nexport type VisualAIKnownError =\n | VisualAIAuthError\n | VisualAIRateLimitError\n | VisualAIProviderError\n | VisualAIImageError\n | VisualAIResponseParseError\n | VisualAITruncationError\n | VisualAIConfigError\n | VisualAIAssertionError;\n\n/**\n * Narrows an unknown thrown value to the concrete visual-ai-assertions error union.\n *\n * Use this helper when you want `switch (error.code)` to narrow to subclass-specific fields.\n *\n * @param error Unknown thrown value.\n * @returns `true` when the value is one of the concrete library error subclasses.\n * @example\n * ```ts\n * try {\n * // ...\n * } catch (error) {\n * if (isVisualAIKnownError(error)) {\n * switch (error.code) {\n * case \"RATE_LIMITED\":\n * console.error(error.retryAfter);\n * break;\n * case \"PROVIDER_ERROR\":\n * console.error(error.statusCode);\n * break;\n * }\n * }\n * }\n * ```\n */\nexport function isVisualAIKnownError(error: unknown): error is VisualAIKnownError {\n return (\n error instanceof VisualAIAuthError ||\n error instanceof VisualAIRateLimitError ||\n error instanceof VisualAIProviderError ||\n error instanceof VisualAIImageError ||\n error instanceof VisualAIResponseParseError ||\n error instanceof VisualAITruncationError ||\n error instanceof VisualAIConfigError ||\n error instanceof VisualAIAssertionError\n );\n}\n","const JSON_INSTRUCTIONS = `\nIMPORTANT: You MUST respond with valid JSON only. No markdown, no code blocks, no extra text.\n`;\n\nconst ISSUE_SCHEMA_INSTRUCTIONS = `\nEach issue must have:\n- \"priority\": \"critical\" | \"major\" | \"minor\"\n- \"category\": \"accessibility\" | \"missing-element\" | \"layout\" | \"content\" | \"styling\" | \"functionality\" | \"performance\" | \"other\"\n- \"description\": what the issue is\n- \"suggestion\": how to fix or improve it\n`;\n\nconst CHECK_OUTPUT_SCHEMA = `IMPORTANT: Follow this evaluation order:\n1. First, evaluate EACH statement independently and populate the \"statements\" array\n2. Then, set \"pass\" to true ONLY if every statement passed (logical AND of all statement results)\n3. Write \"reasoning\" as a brief overall summary of the evaluation\n4. Include \"issues\" only for statements that failed\n\nRespond with a JSON object matching this exact structure:\n{\n \"pass\": boolean, // true ONLY if ALL statements passed — derive from statements array\n \"reasoning\": string, // brief overall summary of the evaluation\n \"issues\": [...], // one issue per failing statement (empty if all pass)\n \"statements\": [ // one entry per statement, in order — evaluate these FIRST\n {\n \"statement\": string, // the original statement text\n \"pass\": boolean, // whether this statement is true\n \"reasoning\": string, // explanation for this statement\n \"confidence\": \"high\" | \"medium\" | \"low\"\n // high = clearly visible/absent with no ambiguity\n // medium = present but partially obscured, small, or borderline\n // low = cannot determine with certainty from the screenshot\n }\n ]\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nOnly include issues for statements that fail. If all statements pass, issues should be an empty array.\n\nExample for a failing check:\n{\n \"pass\": false,\n \"reasoning\": \"The submit button is not visible on the page.\",\n \"issues\": [\n { \"priority\": \"major\", \"category\": \"missing-element\", \"description\": \"Submit button is not visible on the page\", \"suggestion\": \"Verify the submit button component is rendered and not hidden by CSS\" }\n ],\n \"statements\": [\n { \"statement\": \"The page header is visible\", \"pass\": true, \"reasoning\": \"Header with logo is clearly visible at the top\", \"confidence\": \"high\" },\n { \"statement\": \"The submit button is visible\", \"pass\": false, \"reasoning\": \"No submit button found in the visible area of the page\", \"confidence\": \"high\" }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n\nconst ASK_OUTPUT_SCHEMA = `Respond with a JSON object matching this exact structure:\n{\n \"summary\": string, // high-level analysis summary\n \"issues\": [...] // list of issues/findings, can be empty\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nPrioritize issues by severity:\n- \"critical\": blocks functionality, breaks accessibility, data loss risk\n- \"major\": significant usability or visual problem\n- \"minor\": cosmetic issue, minor improvement suggestion\n\nExample:\n{\n \"summary\": \"Found 2 issues: a critical accessibility problem and a minor cosmetic issue.\",\n \"issues\": [\n { \"priority\": \"critical\", \"category\": \"accessibility\", \"description\": \"Submit button has insufficient color contrast\", \"suggestion\": \"Increase contrast so text is clearly readable against the background\" },\n { \"priority\": \"minor\", \"category\": \"content\", \"description\": \"Placeholder text 'Lorem ipsum' visible in sidebar\", \"suggestion\": \"Replace with actual content or remove the placeholder section\" }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n\nconst COMPARE_OUTPUT_SCHEMA = `Respond with a JSON object matching this exact structure:\n{\n \"pass\": boolean, // true if no critical or major changes found\n \"reasoning\": string, // overall summary of changes detected\n \"changes\": [ // list of all visual differences detected (empty if images are identical)\n {\n \"description\": string, // what changed between the images\n \"severity\": \"critical\" | \"major\" | \"minor\"\n // critical = element removed, layout broken, functionality lost\n // major = significant visual change that may be unintentional\n // minor = small stylistic difference (color, spacing, font)\n }\n ]\n}\n\nIf the images appear identical, set pass to true, explain in reasoning, and return an empty changes array.\n${JSON_INSTRUCTIONS}`;\n\nconst DEFAULT_CHECK_ROLE =\n \"You are a visual QA assistant. Evaluate the provided image precisely and objectively.\";\n\nconst DEFAULT_ASK_ROLE =\n \"You are a visual QA assistant. Analyze the provided image based on the user's request.\";\n\nconst COMPARE_ROLE =\n \"You are performing a visual regression test. Compare the BEFORE image (baseline) to the AFTER image (current) and identify all visual differences. Flag changes that appear unintentional or problematic.\";\n\nconst COMPARE_EDGE_RULES: readonly string[] = [\n \"The BEFORE image is the baseline/expected state.\",\n \"Flag removals and layout changes as higher severity. Color and spacing changes are lower severity unless they break readability.\",\n \"If the images appear identical, report no changes and pass.\",\n];\n\nexport interface CheckPromptOptions {\n readonly role?: string;\n readonly instructions?: readonly string[];\n}\n\nexport interface ComparePromptOptions {\n readonly userPrompt?: string;\n readonly instructions?: readonly string[];\n}\n\nfunction buildInstructionsSection(instructions: readonly string[]): string {\n return (\n \"Additional instructions:\\n\" + instructions.map((instruction) => `- ${instruction}`).join(\"\\n\")\n );\n}\n\nexport function buildCheckPrompt(\n statements: string | string[],\n options?: CheckPromptOptions,\n): string {\n const stmts = Array.isArray(statements) ? statements : [statements];\n const statementsBlock = stmts.map((s, i) => `${i + 1}. \"${s}\"`).join(\"\\n\");\n\n const sections = [options?.role ?? DEFAULT_CHECK_ROLE];\n\n if (options?.instructions && options.instructions.length > 0) {\n sections.push(buildInstructionsSection(options.instructions));\n }\n\n sections.push(`Statements to evaluate:\\n${statementsBlock}`);\n sections.push(CHECK_OUTPUT_SCHEMA);\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function buildAskPrompt(\n userPrompt: string,\n options?: { readonly instructions?: readonly string[] },\n): string {\n const sections = [DEFAULT_ASK_ROLE];\n\n if (options?.instructions && options.instructions.length > 0) {\n sections.push(buildInstructionsSection(options.instructions));\n }\n\n sections.push(`User request: ${userPrompt}`);\n sections.push(ASK_OUTPUT_SCHEMA);\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function buildAiDiffPrompt(): string {\n return `You are given two screenshots of the same page or component.\nGenerate a single annotated image that clearly highlights the visual differences between the first and second images.\n- Overlay semi-transparent red rectangles or outlines on areas that changed\n- Keep unchanged areas visible but slightly dimmed\n- The output image should match the dimensions of the input images\n- Focus on meaningful visual differences: layout shifts, missing elements, color changes, text differences`;\n}\n\nexport function buildAiDiffCodeExecutionPrompt(): string {\n return `${buildAiDiffPrompt()}\n\nWrite Python code using PIL/Pillow for image processing and matplotlib for rendering to accomplish the above.\nYour code MUST load both input images and display the result using matplotlib.`;\n}\n\nexport function buildComparePrompt(options?: ComparePromptOptions): string {\n const evaluation = options?.userPrompt\n ? `User request: ${options.userPrompt}`\n : \"Identify all visual differences between the baseline and current screenshot. Flag any changes that appear unintentional or problematic.\";\n\n const instructions = options?.instructions\n ? [...COMPARE_EDGE_RULES, ...options.instructions]\n : COMPARE_EDGE_RULES;\n\n const sections = [\n COMPARE_ROLE,\n buildInstructionsSection(instructions),\n evaluation,\n COMPARE_OUTPUT_SCHEMA,\n ];\n\n return sections.join(\"\\n\\n\");\n}\n","import type { ElementsVisibilityOptions } from \"../types.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ELEMENTS_VISIBLE_ROLE =\n \"Check whether specific UI elements are present and fully visible in this screenshot.\";\n\nconst ELEMENTS_HIDDEN_ROLE =\n \"Check whether specific UI elements are absent or hidden in this screenshot.\";\n\nconst ELEMENTS_VISIBLE_EDGE_RULES: readonly string[] = [\n \"If an element is partially visible (cut off by screenshot boundary), it is NOT considered fully visible — the check for that element should fail. Note the partial visibility in your reasoning.\",\n];\n\nconst ELEMENTS_HIDDEN_EDGE_RULES: readonly string[] = [\n \"If an element is partially visible (cut off by screenshot boundary), it is NOT considered hidden — the check for that element should fail. Note the partial visibility in your reasoning.\",\n];\n\nexport function buildElementsVisibilityPrompt(\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n): string {\n const statements = visible\n ? elements.map((el) => `The element \"${el}\" is fully visible on the page`)\n : elements.map((el) => `The element \"${el}\" is NOT visible on the page`);\n\n const defaultRules = visible ? ELEMENTS_VISIBLE_EDGE_RULES : ELEMENTS_HIDDEN_EDGE_RULES;\n const instructions = options?.instructions\n ? [...defaultRules, ...options.instructions]\n : defaultRules;\n\n return buildCheckPrompt(statements, {\n role: visible ? ELEMENTS_VISIBLE_ROLE : ELEMENTS_HIDDEN_ROLE,\n instructions,\n });\n}\n","import type { AccessibilityOptions } from \"../types.js\";\nimport { Accessibility, type AccessibilityCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: AccessibilityCheckName[] = Object.values(Accessibility);\n\nconst ACCESSIBILITY_ROLE =\n \"Evaluate this screenshot for visual accessibility. Focus on what you can actually perceive — apparent contrast levels, text legibility, and visual distinctiveness of interactive elements.\";\n\nconst ACCESSIBILITY_EDGE_RULES: readonly string[] = [\n \"Do not state specific contrast ratios. Describe contrast as 'appears sufficient' or 'appears low'.\",\n \"Dark mode and light mode themes are both valid. Do not flag a valid dark theme as a contrast issue.\",\n];\n\nconst CHECK_STATEMENTS: Record<AccessibilityCheckName, string> = {\n [Accessibility.CONTRAST]:\n \"All text and interactive elements appear to have sufficient color contrast — text is clearly readable against its background\",\n [Accessibility.READABILITY]:\n \"All text is readable — no text is cut off, overlapping, too small to read, or obscured by background images\",\n [Accessibility.INTERACTIVE_VISIBILITY]:\n \"All interactive elements (buttons, links, inputs) are clearly identifiable and visually distinct from non-interactive content\",\n};\n\nexport function buildAccessibilityPrompt(options?: AccessibilityOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n const instructions = options?.instructions\n ? [...ACCESSIBILITY_EDGE_RULES, ...options.instructions]\n : ACCESSIBILITY_EDGE_RULES;\n\n return buildCheckPrompt(statements, {\n role: ACCESSIBILITY_ROLE,\n instructions,\n });\n}\n","import type { LayoutOptions } from \"../types.js\";\nimport { Layout, type LayoutCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: LayoutCheckName[] = Object.values(Layout);\n\nconst LAYOUT_ROLE =\n \"Evaluate this screenshot for visual layout problems — overlapping elements, content that appears cut off or overflowing, and inconsistent alignment patterns.\";\n\nconst LAYOUT_EDGE_RULES: readonly string[] = [\n \"Intentional overlaps (stacked avatars, dropdown menus, overlapping cards) are acceptable. Flag only overlaps that obscure content or appear broken.\",\n \"Scrollable containers with partially visible content are not overflow issues.\",\n];\n\nconst CHECK_STATEMENTS: Record<LayoutCheckName, string> = {\n [Layout.OVERLAP]:\n \"No elements overlap each other unintentionally — all content is clearly separated and readable\",\n [Layout.OVERFLOW]:\n \"No content appears to be unintentionally cut off or extending beyond its container boundaries\",\n [Layout.ALIGNMENT]:\n \"Elements are properly aligned — text, images, and UI components follow a consistent grid or alignment pattern\",\n};\n\nexport function buildLayoutPrompt(options?: LayoutOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n const instructions = options?.instructions\n ? [...LAYOUT_EDGE_RULES, ...options.instructions]\n : LAYOUT_EDGE_RULES;\n\n return buildCheckPrompt(statements, { role: LAYOUT_ROLE, instructions });\n}\n","import type { PageLoadOptions } from \"../types.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst PAGE_LOAD_ROLE =\n \"Evaluate whether this page has finished loading. Look for loading indicators, empty content areas, and missing resources.\";\n\nexport function buildPageLoadPrompt(options?: PageLoadOptions): string {\n const expectLoaded = options?.expectLoaded ?? true;\n\n const statements = expectLoaded\n ? [\n \"The page content has finished loading — no spinning indicators, skeleton placeholders, or progress bars are visible\",\n \"The main content area has actual content displayed (not blank or empty)\",\n \"No broken image icons or missing resource indicators are visible\",\n ]\n : [\n \"The page shows a loading state — loading spinners, skeleton screens, or progress indicators are visible\",\n ];\n\n return buildCheckPrompt(statements, {\n role: PAGE_LOAD_ROLE,\n instructions: options?.instructions,\n });\n}\n","import type { ContentOptions } from \"../types.js\";\nimport { Content, type ContentCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: ContentCheckName[] = Object.values(Content);\n\nconst CONTENT_ROLE =\n \"Evaluate this screenshot for content quality problems — placeholder or dummy content, error states, and broken resources that should not appear in a production UI.\";\n\nconst CHECK_STATEMENTS: Record<ContentCheckName, string> = {\n [Content.PLACEHOLDER_TEXT]:\n \"No placeholder text like 'Lorem ipsum', 'TODO', 'TBD', 'placeholder', or similar dummy content is visible on the page\",\n [Content.ERROR_MESSAGES]:\n \"No error messages, error banners, stack traces, or error codes are visible on the page\",\n [Content.BROKEN_IMAGES]:\n \"No broken image icons, missing image placeholders, or failed-to-load image indicators are visible\",\n [Content.OVERLAPPING_ELEMENTS]:\n \"No UI elements are unintentionally overlapping each other, obscuring text, buttons, or other interactive content\",\n};\n\nexport function buildContentPrompt(options?: ContentOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n return buildCheckPrompt(statements, {\n role: CONTENT_ROLE,\n instructions: options?.instructions,\n });\n}\n","import { VisualAIAuthError, VisualAIProviderError, VisualAIRateLimitError } from \"../errors.js\";\n\nexport function mapProviderError(err: unknown): Error {\n if (!(err instanceof Error)) {\n return new VisualAIProviderError(String(err));\n }\n\n const status = (err as { status?: number }).status;\n\n if (status === 401 || status === 403) {\n return new VisualAIAuthError(err.message);\n }\n if (status === 429) {\n const headers = (err as { headers?: Record<string, string> }).headers;\n const retryAfter = parseRetryAfter(headers?.[\"retry-after\"]);\n return new VisualAIRateLimitError(err.message, retryAfter);\n }\n if (status !== undefined) {\n return new VisualAIProviderError(err.message, status);\n }\n\n return new VisualAIProviderError(err.message);\n}\n\nfunction parseRetryAfter(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n","import { VisualAIAuthError, VisualAIConfigError, VisualAITruncationError } from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\n/** Minimal interface for the Anthropic SDK client used by this driver. */\ninterface AnthropicMessage {\n content: Array<{ type: string; text?: string }>;\n usage: { input_tokens: number; output_tokens: number };\n stop_reason?: string;\n}\n\ninterface AnthropicClient {\n messages: {\n create(params: Record<string, unknown>): Promise<AnthropicMessage>;\n };\n}\n\nexport class AnthropicDriver implements ProviderDriver {\n private client: AnthropicClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private async getClient(): Promise<AnthropicClient> {\n if (this.client) return this.client;\n\n let Anthropic: unknown;\n try {\n const mod: unknown = await import(\"@anthropic-ai/sdk\");\n Anthropic = (mod as { default: unknown }).default;\n } catch {\n throw new VisualAIConfigError(\n \"Anthropic SDK not installed. Run: npm install @anthropic-ai/sdk\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"Anthropic API key not found. Set ANTHROPIC_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (Anthropic as new (opts: { apiKey: string }) => AnthropicClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n const imageBlocks = images.map((img) => ({\n type: \"image\" as const,\n source: {\n type: \"base64\" as const,\n media_type: img.mimeType,\n data: img.base64,\n },\n }));\n\n try {\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_tokens: this.maxTokens,\n messages: [\n {\n role: \"user\",\n content: [...imageBlocks, { type: \"text\" as const, text: prompt }],\n },\n ],\n };\n\n if (this.reasoningEffort) {\n requestParams.thinking = { type: \"adaptive\" };\n requestParams.output_config = {\n effort: this.reasoningEffort === \"xhigh\" ? \"max\" : this.reasoningEffort,\n };\n }\n\n const message = await client.messages.create(requestParams);\n\n const textBlock = message.content.find((block) => block.type === \"text\");\n const text = textBlock?.text ?? \"\";\n\n if (message.stop_reason === \"max_tokens\") {\n throw new VisualAITruncationError(\n `Response truncated: Anthropic stopped due to max_tokens limit (${this.maxTokens} tokens). Increase maxTokens in your config or lower reasoningEffort.`,\n text,\n this.maxTokens,\n );\n }\n\n return {\n text,\n usage: {\n inputTokens: message.usage.input_tokens,\n outputTokens: message.usage.output_tokens,\n },\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import { buildAiDiffCodeExecutionPrompt } from \"../core/prompt.js\";\nimport {\n VisualAIAuthError,\n VisualAIConfigError,\n VisualAIProviderError,\n VisualAITruncationError,\n} from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type { ReasoningEffortLevel } from \"../constants.js\";\nimport type {\n ImageGenerationOptions,\n ImageGenerationResponse,\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst DEFAULT_IMAGE_GEN_MODEL = \"gemini-2.5-flash-image\";\n\n/** Gemini 3+ models use code execution for image generation instead of responseModalities. */\nexport function needsCodeExecution(model: string): boolean {\n const match = model.match(/^gemini-(\\d+)/);\n return match !== null && match[1] !== undefined && parseInt(match[1], 10) >= 3;\n}\n\n/** Response parts from Gemini API. Code execution responses include executableCode/codeExecutionResult\n * parts alongside inlineData; these fields are kept for type accuracy of the full response shape. */\ninterface GeminiImagePart {\n text?: string;\n inlineData?: {\n data: string;\n mimeType: string;\n };\n executableCode?: {\n code: string;\n language?: string;\n };\n codeExecutionResult?: {\n outcome?: string;\n output?: string;\n };\n}\n\n/** Minimal interface for the Google GenAI SDK client used by this driver. */\ninterface GoogleGenerateContentResponse {\n text?: string;\n candidates?: Array<{\n content?: {\n parts?: GeminiImagePart[];\n };\n finishReason?: string;\n }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n thoughtsTokenCount?: number;\n };\n}\n\ninterface GoogleClient {\n models: {\n generateContent(params: Record<string, unknown>): Promise<GoogleGenerateContentResponse>;\n };\n}\n\nconst GOOGLE_THINKING_LEVEL = {\n low: \"minimal\",\n medium: \"low\",\n high: \"medium\",\n xhigh: \"high\",\n} as const satisfies Record<ReasoningEffortLevel, string>;\n\nexport class GoogleDriver implements ProviderDriver {\n private client: GoogleClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private toGeminiParts(images: NormalizedImage[]) {\n return images.map((img) => ({\n inlineData: { data: img.base64, mimeType: img.mimeType },\n }));\n }\n\n private async getClient(): Promise<GoogleClient> {\n if (this.client) return this.client;\n\n let GoogleGenAI: unknown;\n try {\n const mod: unknown = await import(\"@google/genai\");\n GoogleGenAI = (mod as { GoogleGenAI: unknown }).GoogleGenAI;\n } catch {\n throw new VisualAIConfigError(\n \"Google GenAI SDK not installed. Run: npm install @google/genai\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.GOOGLE_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"Google API key not found. Set GOOGLE_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (GoogleGenAI as new (opts: { apiKey: string }) => GoogleClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n try {\n const response = await client.models.generateContent({\n model: this.model,\n contents: [...this.toGeminiParts(images), prompt],\n config: {\n responseMimeType: \"application/json\",\n maxOutputTokens: this.maxTokens,\n ...(this.reasoningEffort && {\n thinkingConfig: {\n thinkingLevel: GOOGLE_THINKING_LEVEL[this.reasoningEffort],\n },\n }),\n },\n });\n\n const finishReason = response.candidates?.[0]?.finishReason;\n if (finishReason === \"MAX_TOKENS\") {\n throw new VisualAITruncationError(\n `Response truncated: Google returned finishReason \"MAX_TOKENS\". The model exhausted the output token budget (${this.maxTokens} tokens). Increase maxTokens in your config or lower reasoningEffort.`,\n response.text ?? \"\",\n this.maxTokens,\n );\n }\n if (finishReason && finishReason !== \"STOP\") {\n throw new VisualAIProviderError(\n `Response blocked: Google returned finishReason \"${finishReason}\".`,\n );\n }\n\n const text = response.text ?? \"\";\n const thoughtsTokenCount = response.usageMetadata?.thoughtsTokenCount;\n\n return {\n text,\n usage: response.usageMetadata\n ? {\n inputTokens: response.usageMetadata.promptTokenCount ?? 0,\n outputTokens: response.usageMetadata.candidatesTokenCount ?? 0,\n ...(thoughtsTokenCount !== undefined && { reasoningTokens: thoughtsTokenCount }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n async generateImage(\n images: NormalizedImage[],\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const client = await this.getClient();\n const imageModel = options?.model ?? DEFAULT_IMAGE_GEN_MODEL;\n const resolvedPrompt =\n options?.promptKind === \"ai-diff\" && needsCodeExecution(imageModel)\n ? buildAiDiffCodeExecutionPrompt()\n : prompt;\n\n // Gemini 3+ models require code execution to generate images;\n // older models use native image generation via responseModalities.\n const config = needsCodeExecution(imageModel)\n ? { tools: [{ codeExecution: {} }] }\n : { responseModalities: [\"TEXT\", \"IMAGE\"] };\n\n try {\n const response = await client.models.generateContent({\n model: imageModel,\n contents: [...this.toGeminiParts(images), resolvedPrompt],\n config,\n });\n\n const parts = response.candidates?.[0]?.content?.parts;\n if (!parts) {\n throw new VisualAIProviderError(\"Gemini image generation returned no response parts\");\n }\n\n const imagePart = parts.find((p) => p.inlineData?.data);\n if (!imagePart?.inlineData) {\n throw new VisualAIProviderError(\n \"Gemini image generation returned no image data. Ensure the model supports image output.\",\n );\n }\n\n return {\n imageData: Buffer.from(imagePart.inlineData.data, \"base64\"),\n mimeType: imagePart.inlineData.mimeType,\n usage: response.usageMetadata\n ? {\n inputTokens: response.usageMetadata.promptTokenCount ?? 0,\n outputTokens: response.usageMetadata.candidatesTokenCount ?? 0,\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import { VisualAIAuthError, VisualAIConfigError, VisualAITruncationError } from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\n/** Minimal interface for the OpenAI SDK client used by this driver. */\ninterface OpenAIResponseResult {\n output_text?: string;\n usage?: {\n input_tokens: number;\n output_tokens: number;\n output_tokens_details?: { reasoning_tokens?: number };\n };\n status?: string;\n incomplete_details?: { reason?: string };\n}\n\ninterface OpenAIClient {\n responses: {\n create(params: Record<string, unknown>): Promise<OpenAIResponseResult>;\n };\n}\n\nexport class OpenAIDriver implements ProviderDriver {\n private client: OpenAIClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private async getClient(): Promise<OpenAIClient> {\n if (this.client) return this.client;\n\n let OpenAI: unknown;\n try {\n const mod: unknown = await import(\"openai\");\n OpenAI = (mod as { default: unknown }).default;\n } catch {\n throw new VisualAIConfigError(\"OpenAI SDK not installed. Run: npm install openai\");\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"OpenAI API key not found. Set OPENAI_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (OpenAI as new (opts: { apiKey: string }) => OpenAIClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n const imageBlocks = images.map((img) => ({\n type: \"input_image\" as const,\n image_url: `data:${img.mimeType};base64,${img.base64}`,\n }));\n\n try {\n const format = options?.responseSchema\n ? {\n type: \"json_schema\" as const,\n name: \"visual_ai_response\",\n strict: true,\n schema: options.responseSchema,\n }\n : { type: \"json_object\" as const };\n\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_output_tokens: this.maxTokens,\n text: { format },\n input: [\n {\n role: \"user\",\n content: [...imageBlocks, { type: \"input_text\" as const, text: prompt }],\n },\n ],\n };\n\n if (this.reasoningEffort) {\n requestParams.reasoning = { effort: this.reasoningEffort };\n }\n\n const response = await client.responses.create(requestParams);\n\n if (response.status && response.status !== \"completed\") {\n const detail = response.incomplete_details?.reason\n ? ` (${response.incomplete_details.reason})`\n : \"\";\n throw new VisualAITruncationError(\n `Response truncated: OpenAI returned status \"${response.status}\"${detail}. The model exhausted the output token budget (${this.maxTokens} tokens). This commonly happens with higher reasoning effort levels. Increase maxTokens in your config (e.g., maxTokens: 16384) or lower reasoningEffort.`,\n response.output_text ?? \"\",\n this.maxTokens,\n );\n }\n\n const text = response.output_text ?? \"\";\n const reasoningTokens = response.usage?.output_tokens_details?.reasoning_tokens;\n\n return {\n text,\n usage: response.usage\n ? {\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n ...(reasoningTokens !== undefined && { reasoningTokens }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import {\n DEFAULT_MAX_TOKENS,\n DEFAULT_MODELS,\n MODEL_TO_PROVIDER,\n OPENAI_REASONING_MAX_TOKENS,\n} from \"../constants.js\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport type { ProviderName, VisualAIConfig } from \"../types.js\";\n\nexport interface ResolvedConfig {\n provider: ProviderName;\n apiKey: string | undefined;\n model: string;\n maxTokens: number;\n reasoningEffort: VisualAIConfig[\"reasoningEffort\"];\n debug: boolean;\n debugPrompt: boolean;\n debugResponse: boolean;\n trackUsage: boolean;\n}\n\nconst MODEL_PREFIX_TO_PROVIDER: [string, ProviderName][] = [\n [\"claude-\", \"anthropic\"],\n [\"gpt-\", \"openai\"],\n [\"o1-\", \"openai\"],\n [\"o3-\", \"openai\"],\n [\"o4-\", \"openai\"],\n [\"gemini-\", \"google\"],\n];\n\nfunction inferProviderFromModel(model: string): ProviderName | undefined {\n const known = MODEL_TO_PROVIDER.get(model);\n if (known) return known;\n\n const prefixMatch = MODEL_PREFIX_TO_PROVIDER.find(([prefix]) => model.startsWith(prefix));\n return prefixMatch?.[1];\n}\n\nfunction resolveProvider(config: VisualAIConfig): ProviderName {\n const model = config.model ?? process.env.VISUAL_AI_MODEL;\n if (model) {\n const inferred = inferProviderFromModel(model);\n if (inferred) return inferred;\n }\n\n const apiKeyProviderMap: [string, ProviderName][] = [\n [\"ANTHROPIC_API_KEY\", \"anthropic\"],\n [\"OPENAI_API_KEY\", \"openai\"],\n [\"GOOGLE_API_KEY\", \"google\"],\n ];\n const detected = apiKeyProviderMap.find(([key]) => process.env[key]);\n if (detected) return detected[1];\n\n throw new VisualAIConfigError(\n \"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).\",\n );\n}\n\nfunction parseBooleanEnv(envName: string, value: string | undefined): boolean | undefined {\n if (value === undefined || value === \"\") return undefined;\n const lower = value.toLowerCase();\n if (lower === \"true\" || lower === \"1\") return true;\n if (lower === \"false\" || lower === \"0\") return false;\n throw new VisualAIConfigError(\n `Invalid ${envName} value: \"${value}\". Use \"true\", \"1\", \"false\", or \"0\".`,\n );\n}\n\nlet debugDeprecationWarned = false;\n\n/** @internal Reset the deprecation warning guard. For testing only. */\nexport function resetDebugDeprecationWarning(): void {\n debugDeprecationWarned = false;\n}\n\nexport function resolveConfig(config: VisualAIConfig): ResolvedConfig {\n const provider = resolveProvider(config);\n const model = config.model ?? process.env.VISUAL_AI_MODEL ?? DEFAULT_MODELS[provider];\n const debug =\n config.debug ?? parseBooleanEnv(\"VISUAL_AI_DEBUG\", process.env.VISUAL_AI_DEBUG) ?? false;\n const debugPrompt =\n config.debugPrompt ??\n parseBooleanEnv(\"VISUAL_AI_DEBUG_PROMPT\", process.env.VISUAL_AI_DEBUG_PROMPT) ??\n false;\n const debugResponse =\n config.debugResponse ??\n parseBooleanEnv(\"VISUAL_AI_DEBUG_RESPONSE\", process.env.VISUAL_AI_DEBUG_RESPONSE) ??\n false;\n\n if (debug && !debugPrompt && !debugResponse && !debugDeprecationWarned) {\n debugDeprecationWarned = true;\n process.stderr.write(\n `[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.\\n`,\n );\n }\n\n const userSetMaxTokens = config.maxTokens !== undefined;\n let maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // OpenAI reasoning tokens share the output budget, so auto-increase for high/xhigh\n if (\n !userSetMaxTokens &&\n provider === \"openai\" &&\n (config.reasoningEffort === \"high\" || config.reasoningEffort === \"xhigh\")\n ) {\n maxTokens = OPENAI_REASONING_MAX_TOKENS;\n if (debug) {\n process.stderr.write(\n `[visual-ai-assertions] Auto-increased maxTokens from ${DEFAULT_MAX_TOKENS} to ${OPENAI_REASONING_MAX_TOKENS} for OpenAI with reasoningEffort \"${config.reasoningEffort}\".\\n`,\n );\n }\n }\n\n return {\n provider,\n apiKey: config.apiKey,\n model,\n maxTokens,\n reasoningEffort: config.reasoningEffort,\n debug,\n debugPrompt,\n debugResponse,\n trackUsage:\n config.trackUsage ??\n parseBooleanEnv(\"VISUAL_AI_TRACK_USAGE\", process.env.VISUAL_AI_TRACK_USAGE) ??\n false,\n };\n}\n","import { Model, Provider } from \"../constants.js\";\nimport type { ProviderName } from \"../types.js\";\n\ninterface ModelPricing {\n inputPricePerToken: number;\n outputPricePerToken: number;\n}\n\nconst PER_MILLION = 1_000_000;\n\nconst PRICING_TABLE: Record<string, ModelPricing> = {\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_4_6}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_4_6}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.HAIKU_4_5}`]: {\n inputPricePerToken: 1 / PER_MILLION,\n outputPricePerToken: 5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4}`]: {\n inputPricePerToken: 2.5 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_PRO}`]: {\n inputPricePerToken: 30 / PER_MILLION,\n outputPricePerToken: 180 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_2}`]: {\n inputPricePerToken: 1.75 / PER_MILLION,\n outputPricePerToken: 14 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_MINI}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 4.5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_NANO}`]: {\n inputPricePerToken: 0.2 / PER_MILLION,\n outputPricePerToken: 1.25 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_MINI}`]: {\n inputPricePerToken: 0.25 / PER_MILLION,\n outputPricePerToken: 2 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_1_PRO_PREVIEW}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 12 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_1_FLASH_LITE_PREVIEW}`]: {\n inputPricePerToken: 0.25 / PER_MILLION,\n outputPricePerToken: 1.5 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_FLASH_PREVIEW}`]: {\n inputPricePerToken: 0.5 / PER_MILLION,\n outputPricePerToken: 3 / PER_MILLION,\n },\n};\n\nexport function calculateCost(\n provider: ProviderName,\n model: string,\n inputTokens: number,\n outputTokens: number,\n): number | undefined {\n const key = `${provider}:${model}`;\n const pricing = PRICING_TABLE[key];\n if (!pricing) return undefined;\n\n return inputTokens * pricing.inputPricePerToken + outputTokens * pricing.outputPricePerToken;\n}\n","import { PROVIDER_DEFAULT_REASONING } from \"../constants.js\";\nimport { calculateCost } from \"./pricing.js\";\nimport type { ResolvedConfig } from \"./config.js\";\nimport type {\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport type { NormalizedImage, UsageInfo } from \"../types.js\";\nimport { VisualAIError, VisualAIResponseParseError, VisualAITruncationError } from \"../errors.js\";\n\nexport type DebugLogKind = \"prompt\" | \"response\" | \"error\";\n\nexport function debugLog(\n config: ResolvedConfig,\n label: string,\n data: string,\n kind: DebugLogKind = \"error\",\n): void {\n const enabled =\n kind === \"prompt\"\n ? config.debugPrompt\n : kind === \"response\"\n ? config.debugResponse\n : config.debug;\n\n if (enabled) {\n process.stderr.write(`[visual-ai-assertions] ${label}: ${data}\\n`);\n }\n}\n\nexport function usageLog(config: ResolvedConfig, method: string, usage: UsageInfo): void {\n if (!config.trackUsage) return;\n const costStr =\n usage.estimatedCost !== undefined ? `$${usage.estimatedCost.toFixed(6)}` : \"unknown\";\n const reasoningStr = config.reasoningEffort\n ? `reasoning: ${config.reasoningEffort}`\n : `reasoning: ${PROVIDER_DEFAULT_REASONING[config.provider]} (provider default)`;\n const reasoningTokenStr =\n usage.reasoningTokens !== undefined ? ` (${usage.reasoningTokens} reasoning)` : \"\";\n process.stderr.write(\n `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output${reasoningTokenStr} tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? \"0.000\"}s [${config.model}, ${reasoningStr}]\\n`,\n );\n}\n\nexport function processUsage(\n method: string,\n rawUsage: RawProviderResponse[\"usage\"],\n durationSeconds: number,\n config: ResolvedConfig,\n): UsageInfo {\n const inputTokens = rawUsage?.inputTokens ?? 0;\n const outputTokens = rawUsage?.outputTokens ?? 0;\n const usage: UsageInfo = {\n inputTokens,\n outputTokens,\n ...(rawUsage?.reasoningTokens !== undefined && { reasoningTokens: rawUsage.reasoningTokens }),\n estimatedCost: calculateCost(config.provider, config.model, inputTokens, outputTokens),\n durationSeconds,\n };\n usageLog(config, method, usage);\n return usage;\n}\n\nconst MAX_RAW_RESPONSE_PREVIEW = 500;\n\nexport function formatError(error: unknown): string {\n if (error instanceof VisualAITruncationError) {\n const preview =\n error.partialResponse.length > MAX_RAW_RESPONSE_PREVIEW\n ? error.partialResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + \"...\"\n : error.partialResponse;\n return `${error.name} (${error.code}): ${error.message}. Partial response: ${preview}`;\n }\n if (error instanceof VisualAIResponseParseError) {\n const truncated =\n error.rawResponse.length > MAX_RAW_RESPONSE_PREVIEW\n ? error.rawResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + \"...\"\n : error.rawResponse;\n return `${error.name} (${error.code}): ${error.message}. Raw (truncated): ${truncated}`;\n }\n if (error instanceof VisualAIError) {\n return `${error.name} (${error.code}): ${error.message}`;\n }\n if (error instanceof Error) {\n return `${error.name}: ${error.message}`;\n }\n return String(error);\n}\n\nexport async function withErrorDebug<T>(\n config: ResolvedConfig,\n method: string,\n fn: () => Promise<T>,\n): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n debugLog(config, `${method} error`, formatError(error), \"error\");\n throw error;\n }\n}\n\nexport async function timedSendMessage(\n driver: ProviderDriver,\n images: NormalizedImage[],\n prompt: string,\n options?: SendMessageOptions,\n): Promise<RawProviderResponse & { durationSeconds: number }> {\n const start = performance.now();\n const response = await driver.sendMessage(images, prompt, options);\n const durationSeconds = (performance.now() - start) / 1000;\n return { ...response, durationSeconds };\n}\n","import { Model } from \"../constants.js\";\nimport sharp from \"sharp\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport type { DiffImageResult, NormalizedImage } from \"../types.js\";\nimport { buildAiDiffPrompt } from \"./prompt.js\";\n\ninterface ImageGenerationDriver {\n generateImage?: (\n images: NormalizedImage[],\n prompt: string,\n options?: { model?: string; promptKind?: \"ai-diff\" },\n ) => Promise<{\n imageData: Buffer;\n mimeType: string;\n }>;\n}\n\nexport async function generateAiDiff(\n imgA: NormalizedImage,\n imgB: NormalizedImage,\n model: string,\n driver: ImageGenerationDriver,\n): Promise<DiffImageResult> {\n if (!driver.generateImage) {\n throw new VisualAIConfigError(\n \"AI-generated diff images require a provider that supports image generation. Currently only the Google (Gemini) provider supports this.\",\n );\n }\n\n if (model !== Model.Google.GEMINI_3_FLASH_PREVIEW) {\n throw new VisualAIConfigError(\n \"Annotated diff images are only supported when visualAI is configured with the Google model gemini-3-flash-preview.\",\n );\n }\n\n const response = await driver.generateImage([imgA, imgB], buildAiDiffPrompt(), {\n model,\n promptKind: \"ai-diff\",\n });\n\n const img = sharp(response.imageData);\n const meta = await img.metadata();\n const pngData = await img.png().toBuffer();\n\n return {\n data: pngData,\n width: meta.width ?? 0,\n height: meta.height ?? 0,\n mimeType: \"image/png\",\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\nimport sharp from \"sharp\";\nimport { VisualAIImageError } from \"../errors.js\";\nimport type { NormalizedImage, SupportedMimeType } from \"../types.js\";\n\nconst SUPPORTED_FORMATS: ReadonlySet<SupportedMimeType> = new Set([\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"image/gif\",\n]);\n\nconst EXTENSION_TO_MIME: Record<string, SupportedMimeType> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".webp\": \"image/webp\",\n \".gif\": \"image/gif\",\n};\n\nconst MAX_DIMENSION = 1568;\nconst URL_FETCH_TIMEOUT_MS = 10_000;\n\nfunction isSupportedMimeType(value: string): value is SupportedMimeType {\n return SUPPORTED_FORMATS.has(value as SupportedMimeType);\n}\n\nfunction getMimeFromExtension(filePath: string): SupportedMimeType | undefined {\n const ext = extname(filePath).toLowerCase();\n return EXTENSION_TO_MIME[ext];\n}\n\nfunction isFilePath(input: string): boolean {\n return (\n input.startsWith(\"/\") ||\n input.startsWith(\"./\") ||\n input.startsWith(\"../\") ||\n input.includes(\"\\\\\")\n );\n}\n\nfunction isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\nfunction isBase64Image(input: string): boolean {\n return (\n input.startsWith(\"iVBOR\") || // PNG (0x89 0x50 0x4E 0x47)\n input.startsWith(\"/9j/\") || // JPEG (0xFF 0xD8 0xFF)\n input.startsWith(\"R0lGOD\") || // GIF (0x47 0x49 0x46)\n input.startsWith(\"UklGR\") // WebP (0x52 0x49 0x46 0x46)\n );\n}\n\nfunction detectMimeType(data: Buffer): SupportedMimeType {\n // Check magic bytes\n if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {\n return \"image/jpeg\";\n }\n if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) {\n return \"image/png\";\n }\n if (\n data[0] === 0x52 &&\n data[1] === 0x49 &&\n data[2] === 0x46 &&\n data[3] === 0x46 &&\n data[8] === 0x57 &&\n data[9] === 0x45 &&\n data[10] === 0x42 &&\n data[11] === 0x50\n ) {\n return \"image/webp\";\n }\n if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) {\n return \"image/gif\";\n }\n\n throw new VisualAIImageError(\"Unable to detect image format from file content\");\n}\n\nasync function resizeIfNeeded(data: Buffer, mimeType: SupportedMimeType): Promise<Buffer> {\n if (mimeType === \"image/gif\") {\n return data;\n }\n\n // Fast path for PNG: read dimensions directly from header bytes\n if (mimeType === \"image/png\" && data.length >= 24) {\n const width = data.readUInt32BE(16);\n const height = data.readUInt32BE(20);\n if (width <= MAX_DIMENSION && height <= MAX_DIMENSION) {\n return data;\n }\n }\n\n // Fall back to sharp for other formats or when resizing is needed\n const pipeline = sharp(data);\n const metadata = await pipeline.metadata();\n const width = metadata.width ?? 0;\n const height = metadata.height ?? 0;\n\n if (width <= MAX_DIMENSION && height <= MAX_DIMENSION) {\n return data;\n }\n\n return pipeline\n .resize({\n width: MAX_DIMENSION,\n height: MAX_DIMENSION,\n fit: \"inside\",\n withoutEnlargement: true,\n })\n .toBuffer();\n}\n\nasync function loadFromFilePath(\n filePath: string,\n): Promise<{ data: Buffer; mimeType: SupportedMimeType }> {\n let fileData: Buffer;\n try {\n fileData = await readFile(filePath);\n } catch (err) {\n throw new VisualAIImageError(\n `Failed to read image file: ${filePath} — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const mimeType = getMimeFromExtension(filePath) ?? detectMimeType(fileData);\n return { data: fileData, mimeType };\n}\n\nasync function loadFromUrl(url: string): Promise<{ data: Buffer; mimeType: SupportedMimeType }> {\n let response: Response;\n try {\n response = await fetch(url, {\n signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),\n });\n } catch (err) {\n throw new VisualAIImageError(\n `Failed to fetch image from URL: ${url} — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (!response.ok) {\n throw new VisualAIImageError(\n `Failed to fetch image from URL: ${url} — HTTP ${response.status}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const data = Buffer.from(arrayBuffer);\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0]?.trim() ?? null;\n const mimeType =\n contentType && isSupportedMimeType(contentType) ? contentType : detectMimeType(data);\n\n return { data, mimeType };\n}\n\nfunction loadFromBase64(input: string): { data: Buffer; mimeType: SupportedMimeType } {\n // Handle data URLs: data:image/png;base64,iVBOR...\n let base64Data = input;\n let mimeType: SupportedMimeType | undefined;\n\n if (input.startsWith(\"data:\")) {\n const match = /^data:(image\\/[^;]+);base64,(.+)$/.exec(input);\n if (!match?.[1] || !match[2]) {\n throw new VisualAIImageError(\"Invalid data URL format\");\n }\n if (!isSupportedMimeType(match[1])) {\n throw new VisualAIImageError(`Unsupported image format: ${match[1]}`);\n }\n mimeType = match[1];\n base64Data = match[2];\n }\n\n // Validate base64 characters\n if (!/^[A-Za-z0-9+/\\n\\r]+=*$/.test(base64Data)) {\n throw new VisualAIImageError(\"Invalid base64 string\");\n }\n\n const data = Buffer.from(base64Data, \"base64\");\n\n if (data.length === 0) {\n throw new VisualAIImageError(\"Empty image data after base64 decode\");\n }\n\n return { data, mimeType: mimeType ?? detectMimeType(data) };\n}\n\nexport async function normalizeImage(\n input: Buffer | Uint8Array | string,\n): Promise<NormalizedImage> {\n let data: Buffer;\n let mimeType: SupportedMimeType;\n\n if (Buffer.isBuffer(input)) {\n mimeType = detectMimeType(input);\n data = input;\n } else if (input instanceof Uint8Array) {\n const buf = Buffer.from(input);\n mimeType = detectMimeType(buf);\n data = buf;\n } else if (typeof input === \"string\") {\n if (isUrl(input)) {\n ({ data, mimeType } = await loadFromUrl(input));\n } else if (input.startsWith(\"data:\")) {\n ({ data, mimeType } = loadFromBase64(input));\n } else if (isBase64Image(input)) {\n ({ data, mimeType } = loadFromBase64(input));\n } else if (isFilePath(input)) {\n ({ data, mimeType } = await loadFromFilePath(input));\n } else {\n throw new VisualAIImageError(\n `Unrecognized image input: \"${input.slice(0, 80)}\". ` +\n `Expected a file path, URL, data URL, or base64-encoded image string.`,\n );\n }\n } else {\n throw new VisualAIImageError(\n \"Invalid image input: expected Buffer, Uint8Array, file path, URL, or base64 string\",\n );\n }\n\n data = await resizeIfNeeded(data, mimeType);\n\n let cachedBase64: string | undefined;\n return {\n data,\n mimeType,\n get base64(): string {\n if (cachedBase64 === undefined) {\n cachedBase64 = data.toString(\"base64\");\n }\n return cachedBase64;\n },\n };\n}\n","import { z } from \"zod\";\nimport type {\n AccessibilityCheckName,\n ContentCheckName,\n LayoutCheckName,\n ReasoningEffortLevel,\n} from \"./constants.js\";\n\n// --- Issue types ---\n\n/** Zod schema for issue severity levels returned by checks and questions. */\nexport const IssuePrioritySchema = z.enum([\"critical\", \"major\", \"minor\"]);\n/** Severity level for a detected visual issue. */\nexport type IssuePriority = z.infer<typeof IssuePrioritySchema>;\n\n/** Zod schema for the categories assigned to detected visual issues. */\nexport const IssueCategorySchema = z.enum([\n \"accessibility\",\n \"missing-element\",\n \"layout\",\n \"content\",\n \"styling\",\n \"functionality\",\n \"performance\",\n \"other\",\n]);\n/** Category assigned to a detected visual issue. */\nexport type IssueCategory = z.infer<typeof IssueCategorySchema>;\n\n/** Zod schema for a structured issue reported by the model. */\nexport const IssueSchema = z.object({\n priority: IssuePrioritySchema,\n category: IssueCategorySchema,\n description: z.string(),\n suggestion: z.string(),\n});\n/** Structured issue reported by a visual check or question. */\nexport type Issue = z.infer<typeof IssueSchema>;\n\n// --- Per-statement result (for check) ---\n\n/** Zod schema for model confidence labels on statement-level results. */\nexport const ConfidenceSchema = z.enum([\"high\", \"medium\", \"low\"]);\n/** Confidence level attached to a statement result. */\nexport type Confidence = z.infer<typeof ConfidenceSchema>;\n\n/** Zod schema for an individual statement evaluation within `check()`. */\nexport const StatementResultSchema = z.object({\n statement: z.string(),\n pass: z.boolean(),\n reasoning: z.string(),\n confidence: ConfidenceSchema.optional(),\n});\n/** Outcome of a single statement evaluated by `check()`. */\nexport type StatementResult = z.infer<typeof StatementResultSchema>;\n\n// --- Usage info ---\n\n/** Zod schema for token and latency metadata attached to API calls. */\nexport const UsageInfoSchema = z.object({\n inputTokens: z.number(),\n outputTokens: z.number(),\n /** Reasoning/thinking tokens consumed by the model (informational, typically included within outputTokens). */\n reasoningTokens: z.number().optional(),\n estimatedCost: z.number().optional(),\n durationSeconds: z.number().nonnegative().optional(),\n});\n/** Token usage and optional cost/latency metadata for a provider call. */\nexport type UsageInfo = z.infer<typeof UsageInfoSchema>;\n\n// --- Base result (shared fields) ---\n\nconst BaseResultSchema = z.object({\n pass: z.boolean(),\n reasoning: z.string(),\n usage: UsageInfoSchema.optional(),\n});\n\n// --- check() / template result ---\n\n/** Zod schema for results returned by `check()` and template helpers. */\nexport const CheckResultSchema = BaseResultSchema.extend({\n issues: z.array(IssueSchema),\n statements: z.array(StatementResultSchema),\n});\n/** Result returned by `check()` and the template convenience methods. */\nexport type CheckResult = z.infer<typeof CheckResultSchema>;\n\n// --- compare() result ---\n\n/** Zod schema for an individual visual change reported by `compare()`. */\nexport const ChangeEntrySchema = z.object({\n description: z.string(),\n severity: IssuePrioritySchema,\n});\n/** Single visual change reported by `compare()`. */\nexport type ChangeEntry = z.infer<typeof ChangeEntrySchema>;\n\n/** Zod schema for the parsed model response returned by `compare()`. */\nexport const CompareResultSchema = BaseResultSchema.extend({\n changes: z.array(ChangeEntrySchema).max(50),\n});\n// diffImage is appended client-side after the AI response is parsed,\n// so it intentionally does not appear in CompareResultSchema.\n/** Result returned by `compare()`, optionally including an AI-generated diff image. */\nexport type CompareResult = z.infer<typeof CompareResultSchema> & {\n diffImage?: DiffImageResult;\n};\n\n// --- ask() result ---\n\n/** Zod schema for results returned by `ask()`. */\nexport const AskResultSchema = z.object({\n summary: z.string(),\n issues: z.array(IssueSchema),\n usage: UsageInfoSchema.optional(),\n});\n/** Result returned by `ask()`. */\nexport type AskResult = z.infer<typeof AskResultSchema>;\n\n// --- Image input ---\n\n/** Supported input shapes for image arguments accepted by the client. */\nexport type ImageInput = Buffer | Uint8Array | string;\n\n/** Supported image MIME types accepted by all providers. */\nexport type SupportedMimeType = \"image/jpeg\" | \"image/png\" | \"image/webp\" | \"image/gif\";\n\n// --- Provider names ---\n\n/** Supported provider identifiers. */\nexport type ProviderName = \"anthropic\" | \"openai\" | \"google\";\n\n// --- VisualAI config ---\n\n/**\n * Configuration for creating a visual AI client.\n *\n * @example\n * ```ts\n * const client = visualAI({\n * model: \"gpt-5-mini\",\n * apiKey: process.env.OPENAI_API_KEY,\n * });\n * ```\n */\nexport interface VisualAIConfig {\n apiKey?: string;\n model?: string;\n /** Enable error diagnostic logging to stderr. Does not enable prompt/response logging — use `debugPrompt` and `debugResponse` for that. */\n debug?: boolean;\n /** Log prompts to stderr. */\n debugPrompt?: boolean;\n /** Log responses to stderr. */\n debugResponse?: boolean;\n maxTokens?: number;\n reasoningEffort?: ReasoningEffortLevel;\n trackUsage?: boolean;\n}\n\n// --- Template option types ---\n\n/** Optional instructions for `check()`. */\nexport interface CheckOptions {\n instructions?: readonly string[];\n}\n\n/** Optional instructions for `ask()`. */\nexport interface AskOptions {\n instructions?: readonly string[];\n}\n\n// --- Diff image types ---\n\n/** Metadata and binary content for an AI-generated diff image. */\nexport interface DiffImageResult {\n data: Buffer;\n width: number;\n height: number;\n mimeType: \"image/png\";\n}\n\n/** Optional prompt, instructions, and diff configuration for `compare()`. */\nexport interface CompareOptions {\n prompt?: string;\n instructions?: readonly string[];\n diffImage?: boolean;\n}\n\n/** Optional instructions for `elementsVisible()` and `elementsHidden()`. */\nexport interface ElementsVisibilityOptions {\n instructions?: readonly string[];\n}\n\n/** Options for the built-in accessibility template. */\nexport interface AccessibilityOptions {\n checks?: AccessibilityCheckName[];\n instructions?: readonly string[];\n}\n\n/** Options for the built-in layout template. */\nexport interface LayoutOptions {\n checks?: LayoutCheckName[];\n instructions?: readonly string[];\n}\n\n/** Options for the built-in page-load template. */\nexport interface PageLoadOptions {\n expectLoaded?: boolean;\n instructions?: readonly string[];\n}\n\n/** Options for the built-in content template. */\nexport interface ContentOptions {\n checks?: ContentCheckName[];\n instructions?: readonly string[];\n}\n\n// --- Normalized image (internal) ---\n\n/** Internal normalized image representation passed to provider drivers. */\nexport interface NormalizedImage {\n readonly data: Buffer;\n readonly mimeType: SupportedMimeType;\n readonly base64: string;\n}\n","import { z } from \"zod\";\nimport { VisualAIResponseParseError } from \"../errors.js\";\nimport { AskResultSchema, CheckResultSchema, CompareResultSchema } from \"../types.js\";\nimport type { AskResult, CheckResult, CompareResult } from \"../types.js\";\n\nfunction stripCodeFences(text: string): string {\n const trimmed = text.trim();\n const match = /^```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/s.exec(trimmed);\n return match?.[1] ?? trimmed;\n}\n\nexport const CheckResponseSchema = CheckResultSchema.omit({ usage: true });\nexport const AskResponseSchema = AskResultSchema.omit({ usage: true });\nexport const CompareResponseSchema = CompareResultSchema.omit({ usage: true });\n\nfunction parseResponse<T>(raw: string, schema: z.ZodType<T>): T {\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripCodeFences(raw));\n } catch {\n throw new VisualAIResponseParseError(\n `Failed to parse AI response as JSON: ${raw.slice(0, 200)}`,\n raw,\n );\n }\n\n const result = schema.safeParse(parsed);\n if (!result.success) {\n throw new VisualAIResponseParseError(\n `AI response does not match expected schema: ${result.error.message}`,\n raw,\n );\n }\n\n return result.data;\n}\n\nfunction reconcileCheckResult(result: Omit<CheckResult, \"usage\">): Omit<CheckResult, \"usage\"> {\n if (result.statements.length === 0) {\n return result;\n }\n\n const passCount = result.statements.filter((s) => s.pass).length;\n const total = result.statements.length;\n const computedPass = passCount === total;\n const countPrefix = `${passCount} of ${total} checks passed`;\n const reasoning = `${countPrefix}. ${result.reasoning}`;\n\n return {\n ...result,\n pass: computedPass,\n reasoning,\n };\n}\n\nexport function parseCheckResponse(raw: string): Omit<CheckResult, \"usage\"> {\n const result = parseResponse(raw, CheckResponseSchema);\n return reconcileCheckResult(result);\n}\n\nexport function parseAskResponse(raw: string): Omit<AskResult, \"usage\"> {\n return parseResponse(raw, AskResponseSchema);\n}\n\nexport function parseCompareResponse(raw: string): Omit<CompareResult, \"usage\"> {\n return parseResponse(raw, CompareResponseSchema);\n}\n","import { Model } from \"../constants.js\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport {\n buildAccessibilityPrompt,\n buildContentPrompt,\n buildElementsVisibilityPrompt,\n buildLayoutPrompt,\n buildPageLoadPrompt,\n} from \"../templates/index.js\";\nimport type {\n AccessibilityOptions,\n AskOptions,\n AskResult,\n CheckOptions,\n CheckResult,\n CompareOptions,\n CompareResult,\n ContentOptions,\n ElementsVisibilityOptions,\n ImageInput,\n LayoutOptions,\n PageLoadOptions,\n ProviderName,\n VisualAIConfig,\n} from \"../types.js\";\nimport { AnthropicDriver } from \"../providers/anthropic.js\";\nimport { GoogleDriver } from \"../providers/google.js\";\nimport { OpenAIDriver } from \"../providers/openai.js\";\nimport type { ProviderConfig, ProviderDriver, SendMessageOptions } from \"../providers/types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { debugLog, processUsage, timedSendMessage, withErrorDebug } from \"./debug.js\";\nimport { generateAiDiff } from \"./diff.js\";\nimport { normalizeImage } from \"./image.js\";\nimport { buildAskPrompt, buildCheckPrompt, buildComparePrompt } from \"./prompt.js\";\nimport {\n AskResponseSchema,\n CheckResponseSchema,\n CompareResponseSchema,\n parseAskResponse,\n parseCheckResponse,\n parseCompareResponse,\n} from \"./response.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport type { z } from \"zod\";\n\nfunction toSchemaOptions(schema: z.ZodType): SendMessageOptions {\n return {\n responseSchema: zodToJsonSchema(schema, { target: \"openAi\" }) as Record<string, unknown>,\n };\n}\n\n/**\n * High-level client for running visual checks against screenshots or other images.\n *\n * @example\n * ```ts\n * const client = visualAI({ model: \"gpt-5-mini\" });\n * const result = await client.check(\"./tests/fixtures/small.png\", \"The button is visible\");\n * ```\n */\nexport interface VisualAIClient {\n /**\n * Verifies one or more statements against a single image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param statements One or more statements to validate against the image.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A structured result describing pass/fail, issues, and statement reasoning.\n * @throws {VisualAIConfigError} When no statements are provided.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.check(screenshot, [\n * \"The primary CTA is visible\",\n * \"There is no error banner\",\n * ]);\n * ```\n */\n check(\n image: ImageInput,\n statements: string | string[],\n options?: CheckOptions,\n ): Promise<CheckResult>;\n /**\n * Asks an open-ended question about an image and returns a structured summary.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param prompt Prompt describing what to inspect in the image.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A summary with any detected issues.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.ask(screenshot, \"What looks visually broken on this page?\");\n * ```\n */\n ask(image: ImageInput, prompt: string, options?: AskOptions): Promise<AskResult>;\n /**\n * Compares two images and reports meaningful visual differences.\n *\n * @param imageA Baseline image source.\n * @param imageB Candidate image source.\n * @param options Optional comparison prompt, instructions, and diff-image settings.\n * `gemini-3-flash-preview` generates an annotated diff image by default;\n * pass `{ diffImage: false }` to opt out.\n * @returns A structured comparison result with optional diff image metadata.\n * @throws {VisualAIImageError} When either image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.compare(beforeScreenshot, afterScreenshot, {\n * diffImage: true,\n * });\n * ```\n */\n compare(imageA: ImageInput, imageB: ImageInput, options?: CompareOptions): Promise<CompareResult>;\n /**\n * Checks that the listed elements are visible in an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param elements Element descriptions that should be present and visible.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A structured pass/fail result for the requested elements.\n * @throws {VisualAIConfigError} When `elements` is empty.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.elementsVisible(screenshot, [\"Save button\", \"Profile avatar\"]);\n * ```\n */\n elementsVisible(\n image: ImageInput,\n elements: string[],\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult>;\n /**\n * Checks that the listed elements are not visible in an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param elements Element descriptions that should be absent or hidden.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A structured pass/fail result for the requested elements.\n * @throws {VisualAIConfigError} When `elements` is empty.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.elementsHidden(screenshot, [\"Cookie banner\"]);\n * ```\n */\n elementsHidden(\n image: ImageInput,\n elements: string[],\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult>;\n /**\n * Runs the built-in accessibility template against an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional checks and extra instructions for the accessibility prompt.\n * @returns A structured accessibility-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.accessibility(screenshot, { checks: [\"contrast\"] });\n * ```\n */\n accessibility(image: ImageInput, options?: AccessibilityOptions): Promise<CheckResult>;\n /**\n * Runs the built-in layout template against an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional checks and extra instructions for the layout prompt.\n * @returns A structured layout-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.layout(screenshot, { checks: [\"overflow\", \"alignment\"] });\n * ```\n */\n layout(image: ImageInput, options?: LayoutOptions): Promise<CheckResult>;\n /**\n * Runs the built-in page-load template against an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional page-load expectations and extra instructions.\n * @returns A structured page-load check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.pageLoad(screenshot, { expectLoaded: true });\n * ```\n */\n pageLoad(image: ImageInput, options?: PageLoadOptions): Promise<CheckResult>;\n /**\n * Runs the built-in content template against an image.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional content checks and extra instructions.\n * @returns A structured content-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.content(screenshot, { checks: [\"placeholder-text\"] });\n * ```\n */\n content(image: ImageInput, options?: ContentOptions): Promise<CheckResult>;\n}\n\ntype ProviderFactory = (config: ProviderConfig) => ProviderDriver;\n\nconst PROVIDER_REGISTRY = {\n anthropic: (config) => new AnthropicDriver(config),\n openai: (config) => new OpenAIDriver(config),\n google: (config) => new GoogleDriver(config),\n} as const satisfies Record<ProviderName, ProviderFactory>;\n\nfunction createDriver(provider: ProviderName, config: ProviderConfig): ProviderDriver {\n return PROVIDER_REGISTRY[provider](config);\n}\n\nconst checkSchemaOptions = toSchemaOptions(CheckResponseSchema);\nconst askSchemaOptions = toSchemaOptions(AskResponseSchema);\nconst compareSchemaOptions = toSchemaOptions(CompareResponseSchema);\n\n/**\n * Creates a configured visual AI client.\n *\n * @param config Model selection and runtime options for subsequent requests.\n * @returns A `VisualAIClient` instance with check, compare, ask, and template helpers.\n * @throws {VisualAIConfigError} When the provider or model configuration is invalid.\n * @throws {VisualAIAuthError} When required API credentials are missing.\n * @example\n * ```ts\n * import { expect, test } from \"@playwright/test\";\n * import { visualAI } from \"visual-ai-assertions\";\n *\n * test(\"hero loads correctly\", async ({ page }) => {\n * const client = visualAI({\n * model: \"gpt-5-mini\",\n * apiKey: process.env.OPENAI_API_KEY,\n * });\n *\n * await page.goto(\"https://example.com\");\n * const screenshot = await page.screenshot();\n * const result = await client.check(screenshot, [\n * \"The hero heading is visible\",\n * \"There is no loading spinner\",\n * ]);\n *\n * expect(result.pass).toBe(true);\n * });\n * ```\n */\nexport function visualAI(config: VisualAIConfig = {}): VisualAIClient {\n const resolvedConfig = resolveConfig(config);\n const driverConfig: ProviderConfig = {\n apiKey: resolvedConfig.apiKey,\n model: resolvedConfig.model,\n maxTokens: resolvedConfig.maxTokens,\n reasoningEffort: resolvedConfig.reasoningEffort,\n };\n const driver = createDriver(resolvedConfig.provider, driverConfig);\n\n async function checkElementsVisibility(\n image: ImageInput,\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult> {\n const methodName = visible ? \"elementsVisible\" : \"elementsHidden\";\n if (elements.length === 0) {\n throw new VisualAIConfigError(`At least one element is required for ${methodName}()`);\n }\n\n return withErrorDebug(resolvedConfig, methodName, async () => {\n const img = await normalizeImage(image);\n const prompt = buildElementsVisibilityPrompt(elements, visible, options);\n debugLog(resolvedConfig, `${methodName} prompt`, prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, `${methodName} response`, response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(methodName, response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n }\n\n return {\n async check(image, statements, options) {\n const stmts = Array.isArray(statements) ? statements : [statements];\n if (stmts.length === 0) {\n throw new VisualAIConfigError(\"At least one statement is required for check()\");\n }\n\n return withErrorDebug(resolvedConfig, \"check\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildCheckPrompt(stmts, { instructions: options?.instructions });\n debugLog(resolvedConfig, \"check prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"check response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"check\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async ask(image, userPrompt, options) {\n return withErrorDebug(resolvedConfig, \"ask\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildAskPrompt(userPrompt, { instructions: options?.instructions });\n debugLog(resolvedConfig, \"ask prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, askSchemaOptions);\n debugLog(resolvedConfig, \"ask response\", response.text, \"response\");\n\n const result = parseAskResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"ask\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async compare(imageA, imageB, options) {\n return withErrorDebug(resolvedConfig, \"compare\", async () => {\n const [imgA, imgB] = await Promise.all([normalizeImage(imageA), normalizeImage(imageB)]);\n const prompt = buildComparePrompt({\n userPrompt: options?.prompt,\n instructions: options?.instructions,\n });\n debugLog(resolvedConfig, \"compare prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [imgA, imgB], prompt, compareSchemaOptions);\n debugLog(resolvedConfig, \"compare response\", response.text, \"response\");\n\n const supportsAnnotatedDiff =\n resolvedConfig.provider === \"google\" &&\n resolvedConfig.model === Model.Google.GEMINI_3_FLASH_PREVIEW;\n const effectiveDiffImage = options?.diffImage ?? (supportsAnnotatedDiff ? true : false);\n\n let diffImage;\n if (effectiveDiffImage) {\n try {\n diffImage = await generateAiDiff(imgA, imgB, resolvedConfig.model, driver);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[visual-ai-assertions] warning: diff generation failed: ${msg}\\n`,\n );\n }\n }\n\n const result = parseCompareResponse(response.text);\n return {\n ...result,\n ...(diffImage ? { diffImage } : {}),\n usage: processUsage(\"compare\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n elementsVisible(image, elements, options) {\n return checkElementsVisibility(image, elements, true, options);\n },\n\n elementsHidden(image, elements, options) {\n return checkElementsVisibility(image, elements, false, options);\n },\n\n async accessibility(image, options) {\n return withErrorDebug(resolvedConfig, \"accessibility\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildAccessibilityPrompt(options);\n debugLog(resolvedConfig, \"accessibility prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"accessibility response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\n \"accessibility\",\n response.usage,\n response.durationSeconds,\n resolvedConfig,\n ),\n };\n });\n },\n\n async layout(image, options) {\n return withErrorDebug(resolvedConfig, \"layout\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildLayoutPrompt(options);\n debugLog(resolvedConfig, \"layout prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"layout response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"layout\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async pageLoad(image, options) {\n return withErrorDebug(resolvedConfig, \"pageLoad\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildPageLoadPrompt(options);\n debugLog(resolvedConfig, \"pageLoad prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"pageLoad response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"pageLoad\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async content(image, options) {\n return withErrorDebug(resolvedConfig, \"content\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildContentPrompt(options);\n debugLog(resolvedConfig, \"content prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"content response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"content\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n };\n}\n","import type { CheckResult, CompareResult } from \"./types.js\";\nimport { VisualAIAssertionError } from \"./errors.js\";\n\n/**\n * Formats a check result into a readable multiline string for logs or test failures.\n *\n * @param result Structured result returned by `check()` or a template helper.\n * @param label Optional label appended to the output header.\n * @returns A human-readable summary of the check result.\n * @example\n * ```ts\n * console.log(formatCheckResult(result, \"Checkout page\"));\n * ```\n */\nexport function formatCheckResult(result: CheckResult, label?: string): string {\n if (result.pass) {\n const header = label ? `Visual AI Check Passed (${label})` : \"Visual AI Check Passed\";\n return `${header}\\n${\"=\".repeat(header.length)}\\n${result.reasoning}`;\n }\n\n const header = label ? `Visual AI Check Failed (${label})` : \"Visual AI Check Failed\";\n const lines: string[] = [header, \"=\".repeat(header.length), result.reasoning];\n\n if (result.statements.length > 0) {\n lines.push(\"\", \"Statements:\");\n for (const s of result.statements) {\n const status = s.pass ? \"PASS\" : \"FAIL\";\n const confidence = s.confidence ? ` (${s.confidence})` : \"\";\n lines.push(` ${status} \"${s.statement}\"`);\n lines.push(` ${s.reasoning}${confidence}`);\n }\n }\n\n if (result.issues.length > 0) {\n lines.push(\"\", \"Issues:\");\n for (const issue of result.issues) {\n lines.push(` [${issue.priority}/${issue.category}] ${issue.description}`);\n lines.push(` → ${issue.suggestion}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Formats a compare result into a readable multiline string for logs or test failures.\n *\n * @param result Structured result returned by `compare()`.\n * @param label Optional label appended to the output header.\n * @returns A human-readable summary of the compare result.\n * @example\n * ```ts\n * console.log(formatCompareResult(result, \"Before/after deploy\"));\n * ```\n */\nexport function formatCompareResult(result: CompareResult, label?: string): string {\n const status = result.pass ? \"Passed\" : \"Failed\";\n const header = label ? `Visual AI Compare ${status} (${label})` : `Visual AI Compare ${status}`;\n const lines: string[] = [header, \"=\".repeat(header.length), result.reasoning];\n\n if (result.changes.length > 0) {\n lines.push(\"\", \"Changes:\");\n for (const change of result.changes) {\n lines.push(` [${change.severity}] ${change.description}`);\n }\n }\n\n if (result.diffImage) {\n const { width, height } = result.diffImage;\n lines.push(\"\", `Diff image: ${width}x${height} (AI-generated)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Throws a `VisualAIAssertionError` when a check result did not pass.\n *\n * @param result Structured result returned by `check()` or a template helper.\n * @param label Optional label appended to the assertion message.\n * @returns Nothing when the result passes.\n * @throws {VisualAIAssertionError} When `result.pass` is `false`.\n * @example\n * ```ts\n * assertVisualResult(result, \"Homepage\");\n * ```\n */\nexport function assertVisualResult(result: CheckResult, label?: string): void {\n if (!result.pass) {\n throw new VisualAIAssertionError(formatCheckResult(result, label), result);\n }\n}\n\n/**\n * Throws a `VisualAIAssertionError` when a compare result did not pass.\n *\n * @param result Structured result returned by `compare()`.\n * @param label Optional label appended to the assertion message.\n * @returns Nothing when the result passes.\n * @throws {VisualAIAssertionError} When `result.pass` is `false`.\n * @example\n * ```ts\n * assertVisualCompareResult(result, \"Login flow\");\n * ```\n */\nexport function assertVisualCompareResult(result: CompareResult, label?: string): void {\n if (!result.pass) {\n throw new VisualAIAssertionError(formatCompareResult(result, label), result);\n }\n}\n"],"mappings":";AAKO,IAAM,kBAAkB;AAAA,EAC7B,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAQO,IAAM,WAAW;AAAA,EACtB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AACV;AAKO,IAAM,QAAQ;AAAA,EACnB,WAAW;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,cAAc;AAAA,IACd,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,wBAAwB;AAAA,IACxB,+BAA+B;AAAA,IAC/B,wBAAwB;AAAA,EAC1B;AACF;AAaO,IAAM,iBAAiB;AAAA,EAC5B,CAAC,SAAS,SAAS,GAAG,MAAM,UAAU;AAAA,EACtC,CAAC,SAAS,MAAM,GAAG,MAAM,OAAO;AAAA,EAChC,CAAC,SAAS,MAAM,GAAG,MAAM,OAAO;AAClC;AAEO,IAAM,qBAAqB;AAO3B,IAAM,8BAA8B;AAIpC,IAAM,oBAAuD,IAAI,IAAI;AAAA,EAC1E,GAAG,OAAO,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,SAAS,CAAU;AAAA,EAC7E,GAAG,OAAO,OAAO,MAAM,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,CAAU;AAAA,EACvE,GAAG,OAAO,OAAO,MAAM,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,CAAU;AACzE,CAAC;AAKM,IAAM,kBAA2C,OAAO,OAAO,QAAQ;AAQvE,IAAM,6BAAqE;AAAA,EAChF,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AACV;AAKO,IAAM,UAAU;AAAA;AAAA,EAErB,kBAAkB;AAAA;AAAA,EAElB,gBAAgB;AAAA;AAAA,EAEhB,eAAe;AAAA;AAAA,EAEf,sBAAsB;AACxB;AAGO,IAAM,SAAS;AAAA;AAAA,EAEpB,SAAS;AAAA;AAAA,EAET,UAAU;AAAA;AAAA,EAEV,WAAW;AACb;AAGO,IAAM,gBAAgB;AAAA;AAAA,EAE3B,UAAU;AAAA;AAAA,EAEV,aAAa;AAAA;AAAA,EAEb,wBAAwB;AAC1B;;;ACrGO,IAAM,gBAAN,cAAiF,MAAM;AAAA,EACnF;AAAA,EAET,YAAY,SAAiB,OAAc,mBAA4B;AACrE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAUO,IAAM,oBAAN,cAAgC,cAA6B;AAAA,EAGlE,YAAY,SAAiB;AAC3B,UAAM,SAAS,aAAa;AAC5B,SAAK,OAAO;AAAA,EACd;AACF;AAYO,IAAM,yBAAN,cAAqC,cAA8B;AAAA,EAExE;AAAA,EAEA,YAAY,SAAiB,YAAqB;AAChD,UAAM,SAAS,cAAc;AAC7B,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAYO,IAAM,wBAAN,cAAoC,cAAgC;AAAA,EAEzE;AAAA,EAEA,YAAY,SAAiB,YAAqB;AAChD,UAAM,SAAS,gBAAgB;AAC/B,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAUO,IAAM,qBAAN,cAAiC,cAA+B;AAAA,EAGrE,YAAY,SAAiB;AAC3B,UAAM,SAAS,eAAe;AAC9B,SAAK,OAAO;AAAA,EACd;AACF;AAYO,IAAM,6BAAN,cAAyC,cAAuC;AAAA,EAErF;AAAA,EAEA,YAAY,SAAiB,aAAqB;AAChD,UAAM,SAAS,uBAAuB;AACtC,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACrB;AACF;AAaO,IAAM,0BAAN,cAAsC,cAAoC;AAAA,EAEtE;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,iBAAyB,WAAmB;AACvE,UAAM,SAAS,oBAAoB;AACnC,SAAK,OAAO;AACZ,SAAK,kBAAkB;AACvB,SAAK,YAAY;AAAA,EACnB;AACF;AAUO,IAAM,sBAAN,cAAkC,cAAgC;AAAA,EAGvE,YAAY,SAAiB;AAC3B,UAAM,SAAS,gBAAgB;AAC/B,SAAK,OAAO;AAAA,EACd;AACF;AAYO,IAAM,yBAAN,cAAqC,cAAkC;AAAA,EAE5E;AAAA,EAEA,YAAY,SAAiB,QAAqC;AAChE,UAAM,SAAS,kBAAkB;AACjC,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAwCO,SAAS,qBAAqB,OAA6C;AAChF,SACE,iBAAiB,qBACjB,iBAAiB,0BACjB,iBAAiB,yBACjB,iBAAiB,sBACjB,iBAAiB,8BACjB,iBAAiB,2BACjB,iBAAiB,uBACjB,iBAAiB;AAErB;;;ACzPA,IAAM,oBAAoB;AAAA;AAAA;AAI1B,IAAM,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlC,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuB1B,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBzB,iBAAiB;AAEnB,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAezB,iBAAiB;AAEnB,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB5B,iBAAiB;AAEnB,IAAM,qBACJ;AAEF,IAAM,mBACJ;AAEF,IAAM,eACJ;AAEF,IAAM,qBAAwC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AACF;AAYA,SAAS,yBAAyB,cAAyC;AACzE,SACE,+BAA+B,aAAa,IAAI,CAAC,gBAAgB,KAAK,WAAW,EAAE,EAAE,KAAK,IAAI;AAElG;AAEO,SAAS,iBACd,YACA,SACQ;AACR,QAAM,QAAQ,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU;AAClE,QAAM,kBAAkB,MAAM,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,IAAI;AAEzE,QAAM,WAAW,CAAC,SAAS,QAAQ,kBAAkB;AAErD,MAAI,SAAS,gBAAgB,QAAQ,aAAa,SAAS,GAAG;AAC5D,aAAS,KAAK,yBAAyB,QAAQ,YAAY,CAAC;AAAA,EAC9D;AAEA,WAAS,KAAK;AAAA,EAA4B,eAAe,EAAE;AAC3D,WAAS,KAAK,mBAAmB;AAEjC,SAAO,SAAS,KAAK,MAAM;AAC7B;AAEO,SAAS,eACd,YACA,SACQ;AACR,QAAM,WAAW,CAAC,gBAAgB;AAElC,MAAI,SAAS,gBAAgB,QAAQ,aAAa,SAAS,GAAG;AAC5D,aAAS,KAAK,yBAAyB,QAAQ,YAAY,CAAC;AAAA,EAC9D;AAEA,WAAS,KAAK,iBAAiB,UAAU,EAAE;AAC3C,WAAS,KAAK,iBAAiB;AAE/B,SAAO,SAAS,KAAK,MAAM;AAC7B;AAEO,SAAS,oBAA4B;AAC1C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAMT;AAEO,SAAS,iCAAyC;AACvD,SAAO,GAAG,kBAAkB,CAAC;AAAA;AAAA;AAAA;AAI/B;AAEO,SAAS,mBAAmB,SAAwC;AACzE,QAAM,aAAa,SAAS,aACxB,iBAAiB,QAAQ,UAAU,KACnC;AAEJ,QAAM,eAAe,SAAS,eAC1B,CAAC,GAAG,oBAAoB,GAAG,QAAQ,YAAY,IAC/C;AAEJ,QAAM,WAAW;AAAA,IACf;AAAA,IACA,yBAAyB,YAAY;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,MAAM;AAC7B;;;AC7LA,IAAM,wBACJ;AAEF,IAAM,uBACJ;AAEF,IAAM,8BAAiD;AAAA,EACrD;AACF;AAEA,IAAM,6BAAgD;AAAA,EACpD;AACF;AAEO,SAAS,8BACd,UACA,SACA,SACQ;AACR,QAAM,aAAa,UACf,SAAS,IAAI,CAAC,OAAO,gBAAgB,EAAE,gCAAgC,IACvE,SAAS,IAAI,CAAC,OAAO,gBAAgB,EAAE,8BAA8B;AAEzE,QAAM,eAAe,UAAU,8BAA8B;AAC7D,QAAM,eAAe,SAAS,eAC1B,CAAC,GAAG,cAAc,GAAG,QAAQ,YAAY,IACzC;AAEJ,SAAO,iBAAiB,YAAY;AAAA,IAClC,MAAM,UAAU,wBAAwB;AAAA,IACxC;AAAA,EACF,CAAC;AACH;;;AC/BA,IAAM,aAAuC,OAAO,OAAO,aAAa;AAExE,IAAM,qBACJ;AAEF,IAAM,2BAA8C;AAAA,EAClD;AAAA,EACA;AACF;AAEA,IAAM,mBAA2D;AAAA,EAC/D,CAAC,cAAc,QAAQ,GACrB;AAAA,EACF,CAAC,cAAc,WAAW,GACxB;AAAA,EACF,CAAC,cAAc,sBAAsB,GACnC;AACJ;AAEO,SAAS,yBAAyB,SAAwC;AAC/E,QAAM,SAAS,SAAS,UAAU,CAAC,GAAG,UAAU;AAChD,QAAM,aAAa,OAAO,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAC;AAExD,QAAM,eAAe,SAAS,eAC1B,CAAC,GAAG,0BAA0B,GAAG,QAAQ,YAAY,IACrD;AAEJ,SAAO,iBAAiB,YAAY;AAAA,IAClC,MAAM;AAAA,IACN;AAAA,EACF,CAAC;AACH;;;AC/BA,IAAMA,cAAgC,OAAO,OAAO,MAAM;AAE1D,IAAM,cACJ;AAEF,IAAM,oBAAuC;AAAA,EAC3C;AAAA,EACA;AACF;AAEA,IAAMC,oBAAoD;AAAA,EACxD,CAAC,OAAO,OAAO,GACb;AAAA,EACF,CAAC,OAAO,QAAQ,GACd;AAAA,EACF,CAAC,OAAO,SAAS,GACf;AACJ;AAEO,SAAS,kBAAkB,SAAiC;AACjE,QAAM,SAAS,SAAS,UAAU,CAAC,GAAGD,WAAU;AAChD,QAAM,aAAa,OAAO,IAAI,CAAC,MAAMC,kBAAiB,CAAC,CAAC;AAExD,QAAM,eAAe,SAAS,eAC1B,CAAC,GAAG,mBAAmB,GAAG,QAAQ,YAAY,IAC9C;AAEJ,SAAO,iBAAiB,YAAY,EAAE,MAAM,aAAa,aAAa,CAAC;AACzE;;;AC7BA,IAAM,iBACJ;AAEK,SAAS,oBAAoB,SAAmC;AACrE,QAAM,eAAe,SAAS,gBAAgB;AAE9C,QAAM,aAAa,eACf;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF,IACA;AAAA,IACE;AAAA,EACF;AAEJ,SAAO,iBAAiB,YAAY;AAAA,IAClC,MAAM;AAAA,IACN,cAAc,SAAS;AAAA,EACzB,CAAC;AACH;;;ACnBA,IAAMC,cAAiC,OAAO,OAAO,OAAO;AAE5D,IAAM,eACJ;AAEF,IAAMC,oBAAqD;AAAA,EACzD,CAAC,QAAQ,gBAAgB,GACvB;AAAA,EACF,CAAC,QAAQ,cAAc,GACrB;AAAA,EACF,CAAC,QAAQ,aAAa,GACpB;AAAA,EACF,CAAC,QAAQ,oBAAoB,GAC3B;AACJ;AAEO,SAAS,mBAAmB,SAAkC;AACnE,QAAM,SAAS,SAAS,UAAU,CAAC,GAAGD,WAAU;AAChD,QAAM,aAAa,OAAO,IAAI,CAAC,MAAMC,kBAAiB,CAAC,CAAC;AAExD,SAAO,iBAAiB,YAAY;AAAA,IAClC,MAAM;AAAA,IACN,cAAc,SAAS;AAAA,EACzB,CAAC;AACH;;;AC1BO,SAAS,iBAAiB,KAAqB;AACpD,MAAI,EAAE,eAAe,QAAQ;AAC3B,WAAO,IAAI,sBAAsB,OAAO,GAAG,CAAC;AAAA,EAC9C;AAEA,QAAM,SAAU,IAA4B;AAE5C,MAAI,WAAW,OAAO,WAAW,KAAK;AACpC,WAAO,IAAI,kBAAkB,IAAI,OAAO;AAAA,EAC1C;AACA,MAAI,WAAW,KAAK;AAClB,UAAM,UAAW,IAA6C;AAC9D,UAAM,aAAa,gBAAgB,UAAU,aAAa,CAAC;AAC3D,WAAO,IAAI,uBAAuB,IAAI,SAAS,UAAU;AAAA,EAC3D;AACA,MAAI,WAAW,QAAW;AACxB,WAAO,IAAI,sBAAsB,IAAI,SAAS,MAAM;AAAA,EACtD;AAEA,SAAO,IAAI,sBAAsB,IAAI,OAAO;AAC9C;AAEA,SAAS,gBAAgB,OAA+C;AACtE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,OAAO,SAAS,OAAO,IAAI,UAAU;AAC9C;;;ACLO,IAAM,kBAAN,MAAgD;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,SAAS;AACd,SAAK,cAAc,OAAO;AAC1B,SAAK,kBAAkB,OAAO;AAAA,EAChC;AAAA,EAEA,MAAc,YAAsC;AAClD,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI;AACJ,QAAI;AACF,YAAM,MAAe,MAAM,OAAO,mBAAmB;AACrD,kBAAa,IAA6B;AAAA,IAC5C,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,eAAe,QAAQ,IAAI;AAC/C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,SAAS,IAAK,UAAgE,EAAE,OAAO,CAAC;AAC7F,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YACJ,QACA,QACA,UAC8B;AAC9B,UAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,UAAM,cAAc,OAAO,IAAI,CAAC,SAAS;AAAA,MACvC,MAAM;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,YAAY,IAAI;AAAA,QAChB,MAAM,IAAI;AAAA,MACZ;AAAA,IACF,EAAE;AAEF,QAAI;AACF,YAAM,gBAAyC;AAAA,QAC7C,OAAO,KAAK;AAAA,QACZ,YAAY,KAAK;AAAA,QACjB,UAAU;AAAA,UACR;AAAA,YACE,MAAM;AAAA,YACN,SAAS,CAAC,GAAG,aAAa,EAAE,MAAM,QAAiB,MAAM,OAAO,CAAC;AAAA,UACnE;AAAA,QACF;AAAA,MACF;AAEA,UAAI,KAAK,iBAAiB;AACxB,sBAAc,WAAW,EAAE,MAAM,WAAW;AAC5C,sBAAc,gBAAgB;AAAA,UAC5B,QAAQ,KAAK,oBAAoB,UAAU,QAAQ,KAAK;AAAA,QAC1D;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,OAAO,SAAS,OAAO,aAAa;AAE1D,YAAM,YAAY,QAAQ,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AACvE,YAAM,OAAO,WAAW,QAAQ;AAEhC,UAAI,QAAQ,gBAAgB,cAAc;AACxC,cAAM,IAAI;AAAA,UACR,kEAAkE,KAAK,SAAS;AAAA,UAChF;AAAA,UACA,KAAK;AAAA,QACP;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAAA,UACL,aAAa,QAAQ,MAAM;AAAA,UAC3B,cAAc,QAAQ,MAAM;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,wBAAyB,OAAM;AAClD,YAAM,iBAAiB,GAAG;AAAA,IAC5B;AAAA,EACF;AACF;;;ACvGA,IAAM,0BAA0B;AAGzB,SAAS,mBAAmB,OAAwB;AACzD,QAAM,QAAQ,MAAM,MAAM,eAAe;AACzC,SAAO,UAAU,QAAQ,MAAM,CAAC,MAAM,UAAa,SAAS,MAAM,CAAC,GAAG,EAAE,KAAK;AAC/E;AA0CA,IAAM,wBAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEO,IAAM,eAAN,MAA6C;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,SAAS;AACd,SAAK,cAAc,OAAO;AAC1B,SAAK,kBAAkB,OAAO;AAAA,EAChC;AAAA,EAEQ,cAAc,QAA2B;AAC/C,WAAO,OAAO,IAAI,CAAC,SAAS;AAAA,MAC1B,YAAY,EAAE,MAAM,IAAI,QAAQ,UAAU,IAAI,SAAS;AAAA,IACzD,EAAE;AAAA,EACJ;AAAA,EAEA,MAAc,YAAmC;AAC/C,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI;AACJ,QAAI;AACF,YAAM,MAAe,MAAM,OAAO,eAAe;AACjD,oBAAe,IAAiC;AAAA,IAClD,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,eAAe,QAAQ,IAAI;AAC/C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,SAAS,IAAK,YAA+D,EAAE,OAAO,CAAC;AAC5F,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YACJ,QACA,QACA,UAC8B;AAC9B,UAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,QAAI;AACF,YAAM,WAAW,MAAM,OAAO,OAAO,gBAAgB;AAAA,QACnD,OAAO,KAAK;AAAA,QACZ,UAAU,CAAC,GAAG,KAAK,cAAc,MAAM,GAAG,MAAM;AAAA,QAChD,QAAQ;AAAA,UACN,kBAAkB;AAAA,UAClB,iBAAiB,KAAK;AAAA,UACtB,GAAI,KAAK,mBAAmB;AAAA,YAC1B,gBAAgB;AAAA,cACd,eAAe,sBAAsB,KAAK,eAAe;AAAA,YAC3D;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAM,eAAe,SAAS,aAAa,CAAC,GAAG;AAC/C,UAAI,iBAAiB,cAAc;AACjC,cAAM,IAAI;AAAA,UACR,+GAA+G,KAAK,SAAS;AAAA,UAC7H,SAAS,QAAQ;AAAA,UACjB,KAAK;AAAA,QACP;AAAA,MACF;AACA,UAAI,gBAAgB,iBAAiB,QAAQ;AAC3C,cAAM,IAAI;AAAA,UACR,mDAAmD,YAAY;AAAA,QACjE;AAAA,MACF;AAEA,YAAM,OAAO,SAAS,QAAQ;AAC9B,YAAM,qBAAqB,SAAS,eAAe;AAEnD,aAAO;AAAA,QACL;AAAA,QACA,OAAO,SAAS,gBACZ;AAAA,UACE,aAAa,SAAS,cAAc,oBAAoB;AAAA,UACxD,cAAc,SAAS,cAAc,wBAAwB;AAAA,UAC7D,GAAI,uBAAuB,UAAa,EAAE,iBAAiB,mBAAmB;AAAA,QAChF,IACA;AAAA,MACN;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,2BAA2B,eAAe,sBAAuB,OAAM;AAC1F,YAAM,iBAAiB,GAAG;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,QACA,QACA,SACkC;AAClC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,aAAa,SAAS,SAAS;AACrC,UAAM,iBACJ,SAAS,eAAe,aAAa,mBAAmB,UAAU,IAC9D,+BAA+B,IAC/B;AAIN,UAAM,SAAS,mBAAmB,UAAU,IACxC,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC,EAAE,CAAC,EAAE,IACjC,EAAE,oBAAoB,CAAC,QAAQ,OAAO,EAAE;AAE5C,QAAI;AACF,YAAM,WAAW,MAAM,OAAO,OAAO,gBAAgB;AAAA,QACnD,OAAO;AAAA,QACP,UAAU,CAAC,GAAG,KAAK,cAAc,MAAM,GAAG,cAAc;AAAA,QACxD;AAAA,MACF,CAAC;AAED,YAAM,QAAQ,SAAS,aAAa,CAAC,GAAG,SAAS;AACjD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,sBAAsB,oDAAoD;AAAA,MACtF;AAEA,YAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,IAAI;AACtD,UAAI,CAAC,WAAW,YAAY;AAC1B,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,WAAW,OAAO,KAAK,UAAU,WAAW,MAAM,QAAQ;AAAA,QAC1D,UAAU,UAAU,WAAW;AAAA,QAC/B,OAAO,SAAS,gBACZ;AAAA,UACE,aAAa,SAAS,cAAc,oBAAoB;AAAA,UACxD,cAAc,SAAS,cAAc,wBAAwB;AAAA,QAC/D,IACA;AAAA,MACN;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,sBAAuB,OAAM;AAChD,YAAM,iBAAiB,GAAG;AAAA,IAC5B;AAAA,EACF;AACF;;;ACtMO,IAAM,eAAN,MAA6C;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,SAAS;AACd,SAAK,cAAc,OAAO;AAC1B,SAAK,kBAAkB,OAAO;AAAA,EAChC;AAAA,EAEA,MAAc,YAAmC;AAC/C,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI;AACJ,QAAI;AACF,YAAM,MAAe,MAAM,OAAO,QAAQ;AAC1C,eAAU,IAA6B;AAAA,IACzC,QAAQ;AACN,YAAM,IAAI,oBAAoB,mDAAmD;AAAA,IACnF;AAEA,UAAM,SAAS,KAAK,eAAe,QAAQ,IAAI;AAC/C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,SAAS,IAAK,OAA0D,EAAE,OAAO,CAAC;AACvF,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YACJ,QACA,QACA,SAC8B;AAC9B,UAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,UAAM,cAAc,OAAO,IAAI,CAAC,SAAS;AAAA,MACvC,MAAM;AAAA,MACN,WAAW,QAAQ,IAAI,QAAQ,WAAW,IAAI,MAAM;AAAA,IACtD,EAAE;AAEF,QAAI;AACF,YAAM,SAAS,SAAS,iBACpB;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,QAAQ;AAAA,MAClB,IACA,EAAE,MAAM,cAAuB;AAEnC,YAAM,gBAAyC;AAAA,QAC7C,OAAO,KAAK;AAAA,QACZ,mBAAmB,KAAK;AAAA,QACxB,MAAM,EAAE,OAAO;AAAA,QACf,OAAO;AAAA,UACL;AAAA,YACE,MAAM;AAAA,YACN,SAAS,CAAC,GAAG,aAAa,EAAE,MAAM,cAAuB,MAAM,OAAO,CAAC;AAAA,UACzE;AAAA,QACF;AAAA,MACF;AAEA,UAAI,KAAK,iBAAiB;AACxB,sBAAc,YAAY,EAAE,QAAQ,KAAK,gBAAgB;AAAA,MAC3D;AAEA,YAAM,WAAW,MAAM,OAAO,UAAU,OAAO,aAAa;AAE5D,UAAI,SAAS,UAAU,SAAS,WAAW,aAAa;AACtD,cAAM,SAAS,SAAS,oBAAoB,SACxC,KAAK,SAAS,mBAAmB,MAAM,MACvC;AACJ,cAAM,IAAI;AAAA,UACR,+CAA+C,SAAS,MAAM,IAAI,MAAM,kDAAkD,KAAK,SAAS;AAAA,UACxI,SAAS,eAAe;AAAA,UACxB,KAAK;AAAA,QACP;AAAA,MACF;AAEA,YAAM,OAAO,SAAS,eAAe;AACrC,YAAM,kBAAkB,SAAS,OAAO,uBAAuB;AAE/D,aAAO;AAAA,QACL;AAAA,QACA,OAAO,SAAS,QACZ;AAAA,UACE,aAAa,SAAS,MAAM;AAAA,UAC5B,cAAc,SAAS,MAAM;AAAA,UAC7B,GAAI,oBAAoB,UAAa,EAAE,gBAAgB;AAAA,QACzD,IACA;AAAA,MACN;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,wBAAyB,OAAM;AAClD,YAAM,iBAAiB,GAAG;AAAA,IAC5B;AAAA,EACF;AACF;;;ACjHA,IAAM,2BAAqD;AAAA,EACzD,CAAC,WAAW,WAAW;AAAA,EACvB,CAAC,QAAQ,QAAQ;AAAA,EACjB,CAAC,OAAO,QAAQ;AAAA,EAChB,CAAC,OAAO,QAAQ;AAAA,EAChB,CAAC,OAAO,QAAQ;AAAA,EAChB,CAAC,WAAW,QAAQ;AACtB;AAEA,SAAS,uBAAuB,OAAyC;AACvE,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AACzC,MAAI,MAAO,QAAO;AAElB,QAAM,cAAc,yBAAyB,KAAK,CAAC,CAAC,MAAM,MAAM,MAAM,WAAW,MAAM,CAAC;AACxF,SAAO,cAAc,CAAC;AACxB;AAEA,SAAS,gBAAgB,QAAsC;AAC7D,QAAM,QAAQ,OAAO,SAAS,QAAQ,IAAI;AAC1C,MAAI,OAAO;AACT,UAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAI,SAAU,QAAO;AAAA,EACvB;AAEA,QAAM,oBAA8C;AAAA,IAClD,CAAC,qBAAqB,WAAW;AAAA,IACjC,CAAC,kBAAkB,QAAQ;AAAA,IAC3B,CAAC,kBAAkB,QAAQ;AAAA,EAC7B;AACA,QAAM,WAAW,kBAAkB,KAAK,CAAC,CAAC,GAAG,MAAM,QAAQ,IAAI,GAAG,CAAC;AACnE,MAAI,SAAU,QAAO,SAAS,CAAC;AAE/B,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,SAAiB,OAAgD;AACxF,MAAI,UAAU,UAAa,UAAU,GAAI,QAAO;AAChD,QAAM,QAAQ,MAAM,YAAY;AAChC,MAAI,UAAU,UAAU,UAAU,IAAK,QAAO;AAC9C,MAAI,UAAU,WAAW,UAAU,IAAK,QAAO;AAC/C,QAAM,IAAI;AAAA,IACR,WAAW,OAAO,YAAY,KAAK;AAAA,EACrC;AACF;AAEA,IAAI,yBAAyB;AAOtB,SAAS,cAAc,QAAwC;AACpE,QAAM,WAAW,gBAAgB,MAAM;AACvC,QAAM,QAAQ,OAAO,SAAS,QAAQ,IAAI,mBAAmB,eAAe,QAAQ;AACpF,QAAM,QACJ,OAAO,SAAS,gBAAgB,mBAAmB,QAAQ,IAAI,eAAe,KAAK;AACrF,QAAM,cACJ,OAAO,eACP,gBAAgB,0BAA0B,QAAQ,IAAI,sBAAsB,KAC5E;AACF,QAAM,gBACJ,OAAO,iBACP,gBAAgB,4BAA4B,QAAQ,IAAI,wBAAwB,KAChF;AAEF,MAAI,SAAS,CAAC,eAAe,CAAC,iBAAiB,CAAC,wBAAwB;AACtE,6BAAyB;AACzB,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB,OAAO,cAAc;AAC9C,MAAI,YAAY,OAAO,aAAa;AAGpC,MACE,CAAC,oBACD,aAAa,aACZ,OAAO,oBAAoB,UAAU,OAAO,oBAAoB,UACjE;AACA,gBAAY;AACZ,QAAI,OAAO;AACT,cAAQ,OAAO;AAAA,QACb,wDAAwD,kBAAkB,OAAO,2BAA2B,qCAAqC,OAAO,eAAe;AAAA;AAAA,MACzK;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO;AAAA,IACf;AAAA,IACA;AAAA,IACA,iBAAiB,OAAO;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA,YACE,OAAO,cACP,gBAAgB,yBAAyB,QAAQ,IAAI,qBAAqB,KAC1E;AAAA,EACJ;AACF;;;ACvHA,IAAM,cAAc;AAEpB,IAAM,gBAA8C;AAAA,EAClD,CAAC,GAAG,SAAS,SAAS,IAAI,MAAM,UAAU,QAAQ,EAAE,GAAG;AAAA,IACrD,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,KAAK;AAAA,EAC5B;AAAA,EACA,CAAC,GAAG,SAAS,SAAS,IAAI,MAAM,UAAU,UAAU,EAAE,GAAG;AAAA,IACvD,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,KAAK;AAAA,EAC5B;AAAA,EACA,CAAC,GAAG,SAAS,SAAS,IAAI,MAAM,UAAU,SAAS,EAAE,GAAG;AAAA,IACtD,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,IAAI;AAAA,EAC3B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,OAAO,EAAE,GAAG;AAAA,IAC9C,oBAAoB,MAAM;AAAA,IAC1B,qBAAqB,KAAK;AAAA,EAC5B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,WAAW,EAAE,GAAG;AAAA,IAClD,oBAAoB,KAAK;AAAA,IACzB,qBAAqB,MAAM;AAAA,EAC7B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,OAAO,EAAE,GAAG;AAAA,IAC9C,oBAAoB,OAAO;AAAA,IAC3B,qBAAqB,KAAK;AAAA,EAC5B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,YAAY,EAAE,GAAG;AAAA,IACnD,oBAAoB,OAAO;AAAA,IAC3B,qBAAqB,MAAM;AAAA,EAC7B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,YAAY,EAAE,GAAG;AAAA,IACnD,oBAAoB,MAAM;AAAA,IAC1B,qBAAqB,OAAO;AAAA,EAC9B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,UAAU,EAAE,GAAG;AAAA,IACjD,oBAAoB,OAAO;AAAA,IAC3B,qBAAqB,IAAI;AAAA,EAC3B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,sBAAsB,EAAE,GAAG;AAAA,IAC7D,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,KAAK;AAAA,EAC5B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,6BAA6B,EAAE,GAAG;AAAA,IACpE,oBAAoB,OAAO;AAAA,IAC3B,qBAAqB,MAAM;AAAA,EAC7B;AAAA,EACA,CAAC,GAAG,SAAS,MAAM,IAAI,MAAM,OAAO,sBAAsB,EAAE,GAAG;AAAA,IAC7D,oBAAoB,MAAM;AAAA,IAC1B,qBAAqB,IAAI;AAAA,EAC3B;AACF;AAEO,SAAS,cACd,UACA,OACA,aACA,cACoB;AACpB,QAAM,MAAM,GAAG,QAAQ,IAAI,KAAK;AAChC,QAAM,UAAU,cAAc,GAAG;AACjC,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO,cAAc,QAAQ,qBAAqB,eAAe,QAAQ;AAC3E;;;AC3DO,SAAS,SACd,QACA,OACA,MACA,OAAqB,SACf;AACN,QAAM,UACJ,SAAS,WACL,OAAO,cACP,SAAS,aACP,OAAO,gBACP,OAAO;AAEf,MAAI,SAAS;AACX,YAAQ,OAAO,MAAM,0BAA0B,KAAK,KAAK,IAAI;AAAA,CAAI;AAAA,EACnE;AACF;AAEO,SAAS,SAAS,QAAwB,QAAgB,OAAwB;AACvF,MAAI,CAAC,OAAO,WAAY;AACxB,QAAM,UACJ,MAAM,kBAAkB,SAAY,IAAI,MAAM,cAAc,QAAQ,CAAC,CAAC,KAAK;AAC7E,QAAM,eAAe,OAAO,kBACxB,cAAc,OAAO,eAAe,KACpC,cAAc,2BAA2B,OAAO,QAAQ,CAAC;AAC7D,QAAM,oBACJ,MAAM,oBAAoB,SAAY,KAAK,MAAM,eAAe,gBAAgB;AAClF,UAAQ,OAAO;AAAA,IACb,0BAA0B,MAAM,WAAW,MAAM,WAAW,YAAY,MAAM,YAAY,UAAU,iBAAiB,YAAY,OAAO,QAAQ,MAAM,iBAAiB,QAAQ,CAAC,KAAK,OAAO,MAAM,OAAO,KAAK,KAAK,YAAY;AAAA;AAAA,EACjO;AACF;AAEO,SAAS,aACd,QACA,UACA,iBACA,QACW;AACX,QAAM,cAAc,UAAU,eAAe;AAC7C,QAAM,eAAe,UAAU,gBAAgB;AAC/C,QAAM,QAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,GAAI,UAAU,oBAAoB,UAAa,EAAE,iBAAiB,SAAS,gBAAgB;AAAA,IAC3F,eAAe,cAAc,OAAO,UAAU,OAAO,OAAO,aAAa,YAAY;AAAA,IACrF;AAAA,EACF;AACA,WAAS,QAAQ,QAAQ,KAAK;AAC9B,SAAO;AACT;AAEA,IAAM,2BAA2B;AAE1B,SAAS,YAAY,OAAwB;AAClD,MAAI,iBAAiB,yBAAyB;AAC5C,UAAM,UACJ,MAAM,gBAAgB,SAAS,2BAC3B,MAAM,gBAAgB,MAAM,GAAG,wBAAwB,IAAI,QAC3D,MAAM;AACZ,WAAO,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,MAAM,MAAM,OAAO,uBAAuB,OAAO;AAAA,EACtF;AACA,MAAI,iBAAiB,4BAA4B;AAC/C,UAAM,YACJ,MAAM,YAAY,SAAS,2BACvB,MAAM,YAAY,MAAM,GAAG,wBAAwB,IAAI,QACvD,MAAM;AACZ,WAAO,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,MAAM,MAAM,OAAO,sBAAsB,SAAS;AAAA,EACvF;AACA,MAAI,iBAAiB,eAAe;AAClC,WAAO,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,MAAM,MAAM,OAAO;AAAA,EACxD;AACA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO;AAAA,EACxC;AACA,SAAO,OAAO,KAAK;AACrB;AAEA,eAAsB,eACpB,QACA,QACA,IACY;AACZ,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,SAAS,OAAO;AACd,aAAS,QAAQ,GAAG,MAAM,UAAU,YAAY,KAAK,GAAG,OAAO;AAC/D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,iBACpB,QACA,QACA,QACA,SAC4D;AAC5D,QAAM,QAAQ,YAAY,IAAI;AAC9B,QAAM,WAAW,MAAM,OAAO,YAAY,QAAQ,QAAQ,OAAO;AACjE,QAAM,mBAAmB,YAAY,IAAI,IAAI,SAAS;AACtD,SAAO,EAAE,GAAG,UAAU,gBAAgB;AACxC;;;AChHA,OAAO,WAAW;AAgBlB,eAAsB,eACpB,MACA,MACA,OACA,QAC0B;AAC1B,MAAI,CAAC,OAAO,eAAe;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,MAAM,OAAO,wBAAwB;AACjD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,OAAO,cAAc,CAAC,MAAM,IAAI,GAAG,kBAAkB,GAAG;AAAA,IAC7E;AAAA,IACA,YAAY;AAAA,EACd,CAAC;AAED,QAAM,MAAM,MAAM,SAAS,SAAS;AACpC,QAAM,OAAO,MAAM,IAAI,SAAS;AAChC,QAAM,UAAU,MAAM,IAAI,IAAI,EAAE,SAAS;AAEzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,KAAK,SAAS;AAAA,IACrB,QAAQ,KAAK,UAAU;AAAA,IACvB,UAAU;AAAA,EACZ;AACF;;;AClDA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,OAAOC,YAAW;AAIlB,IAAM,oBAAoD,oBAAI,IAAI;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,oBAAuD;AAAA,EAC3D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AACV;AAEA,IAAM,gBAAgB;AACtB,IAAM,uBAAuB;AAE7B,SAAS,oBAAoB,OAA2C;AACtE,SAAO,kBAAkB,IAAI,KAA0B;AACzD;AAEA,SAAS,qBAAqB,UAAiD;AAC7E,QAAM,MAAM,QAAQ,QAAQ,EAAE,YAAY;AAC1C,SAAO,kBAAkB,GAAG;AAC9B;AAEA,SAAS,WAAW,OAAwB;AAC1C,SACE,MAAM,WAAW,GAAG,KACpB,MAAM,WAAW,IAAI,KACrB,MAAM,WAAW,KAAK,KACtB,MAAM,SAAS,IAAI;AAEvB;AAEA,SAAS,MAAM,OAAwB;AACrC,SAAO,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU;AACnE;AAEA,SAAS,cAAc,OAAwB;AAC7C,SACE,MAAM,WAAW,OAAO;AAAA,EACxB,MAAM,WAAW,MAAM;AAAA,EACvB,MAAM,WAAW,QAAQ;AAAA,EACzB,MAAM,WAAW,OAAO;AAE5B;AAEA,SAAS,eAAe,MAAiC;AAEvD,MAAI,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,KAAM;AAC5D,WAAO;AAAA,EACT;AACA,MAAI,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,IAAM;AAChF,WAAO;AAAA,EACT;AACA,MACE,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,EAAE,MAAM,MACb,KAAK,EAAE,MAAM,IACb;AACA,WAAO;AAAA,EACT;AACA,MAAI,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,IAAM;AAC5D,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,mBAAmB,iDAAiD;AAChF;AAEA,eAAe,eAAe,MAAc,UAA8C;AACxF,MAAI,aAAa,aAAa;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,eAAe,KAAK,UAAU,IAAI;AACjD,UAAMC,SAAQ,KAAK,aAAa,EAAE;AAClC,UAAMC,UAAS,KAAK,aAAa,EAAE;AACnC,QAAID,UAAS,iBAAiBC,WAAU,eAAe;AACrD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,WAAWC,OAAM,IAAI;AAC3B,QAAM,WAAW,MAAM,SAAS,SAAS;AACzC,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,MAAI,SAAS,iBAAiB,UAAU,eAAe;AACrD,WAAO;AAAA,EACT;AAEA,SAAO,SACJ,OAAO;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,oBAAoB;AAAA,EACtB,CAAC,EACA,SAAS;AACd;AAEA,eAAe,iBACb,UACwD;AACxD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,QAAQ;AAAA,EACpC,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,8BAA8B,QAAQ,WAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,WAAW,qBAAqB,QAAQ,KAAK,eAAe,QAAQ;AAC1E,SAAO,EAAE,MAAM,UAAU,SAAS;AACpC;AAEA,eAAe,YAAY,KAAqE;AAC9F,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,QAAQ,YAAY,QAAQ,oBAAoB;AAAA,IAClD,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,mCAAmC,GAAG,WAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9F;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,mCAAmC,GAAG,gBAAW,SAAS,MAAM;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,cAAc,MAAM,SAAS,YAAY;AAC/C,QAAM,OAAO,OAAO,KAAK,WAAW;AACpC,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAAK;AACnF,QAAM,WACJ,eAAe,oBAAoB,WAAW,IAAI,cAAc,eAAe,IAAI;AAErF,SAAO,EAAE,MAAM,SAAS;AAC1B;AAEA,SAAS,eAAe,OAA8D;AAEpF,MAAI,aAAa;AACjB,MAAI;AAEJ,MAAI,MAAM,WAAW,OAAO,GAAG;AAC7B,UAAM,QAAQ,oCAAoC,KAAK,KAAK;AAC5D,QAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;AAC5B,YAAM,IAAI,mBAAmB,yBAAyB;AAAA,IACxD;AACA,QAAI,CAAC,oBAAoB,MAAM,CAAC,CAAC,GAAG;AAClC,YAAM,IAAI,mBAAmB,6BAA6B,MAAM,CAAC,CAAC,EAAE;AAAA,IACtE;AACA,eAAW,MAAM,CAAC;AAClB,iBAAa,MAAM,CAAC;AAAA,EACtB;AAGA,MAAI,CAAC,yBAAyB,KAAK,UAAU,GAAG;AAC9C,UAAM,IAAI,mBAAmB,uBAAuB;AAAA,EACtD;AAEA,QAAM,OAAO,OAAO,KAAK,YAAY,QAAQ;AAE7C,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI,mBAAmB,sCAAsC;AAAA,EACrE;AAEA,SAAO,EAAE,MAAM,UAAU,YAAY,eAAe,IAAI,EAAE;AAC5D;AAEA,eAAsB,eACpB,OAC0B;AAC1B,MAAI;AACJ,MAAI;AAEJ,MAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,eAAW,eAAe,KAAK;AAC/B,WAAO;AAAA,EACT,WAAW,iBAAiB,YAAY;AACtC,UAAM,MAAM,OAAO,KAAK,KAAK;AAC7B,eAAW,eAAe,GAAG;AAC7B,WAAO;AAAA,EACT,WAAW,OAAO,UAAU,UAAU;AACpC,QAAI,MAAM,KAAK,GAAG;AAChB,OAAC,EAAE,MAAM,SAAS,IAAI,MAAM,YAAY,KAAK;AAAA,IAC/C,WAAW,MAAM,WAAW,OAAO,GAAG;AACpC,OAAC,EAAE,MAAM,SAAS,IAAI,eAAe,KAAK;AAAA,IAC5C,WAAW,cAAc,KAAK,GAAG;AAC/B,OAAC,EAAE,MAAM,SAAS,IAAI,eAAe,KAAK;AAAA,IAC5C,WAAW,WAAW,KAAK,GAAG;AAC5B,OAAC,EAAE,MAAM,SAAS,IAAI,MAAM,iBAAiB,KAAK;AAAA,IACpD,OAAO;AACL,YAAM,IAAI;AAAA,QACR,8BAA8B,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MAElD;AAAA,IACF;AAAA,EACF,OAAO;AACL,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,eAAe,MAAM,QAAQ;AAE1C,MAAI;AACJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,IAAI,SAAiB;AACnB,UAAI,iBAAiB,QAAW;AAC9B,uBAAe,KAAK,SAAS,QAAQ;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC7OA,SAAS,SAAS;AAWX,IAAM,sBAAsB,EAAE,KAAK,CAAC,YAAY,SAAS,OAAO,CAAC;AAKjE,IAAM,sBAAsB,EAAE,KAAK;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa,EAAE,OAAO;AAAA,EACtB,YAAY,EAAE,OAAO;AACvB,CAAC;AAOM,IAAM,mBAAmB,EAAE,KAAK,CAAC,QAAQ,UAAU,KAAK,CAAC;AAKzD,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,WAAW,EAAE,OAAO;AAAA,EACpB,MAAM,EAAE,QAAQ;AAAA,EAChB,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,iBAAiB,SAAS;AACxC,CAAC;AAOM,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,aAAa,EAAE,OAAO;AAAA,EACtB,cAAc,EAAE,OAAO;AAAA;AAAA,EAEvB,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACrC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,iBAAiB,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS;AACrD,CAAC;AAMD,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,MAAM,EAAE,QAAQ;AAAA,EAChB,WAAW,EAAE,OAAO;AAAA,EACpB,OAAO,gBAAgB,SAAS;AAClC,CAAC;AAKM,IAAM,oBAAoB,iBAAiB,OAAO;AAAA,EACvD,QAAQ,EAAE,MAAM,WAAW;AAAA,EAC3B,YAAY,EAAE,MAAM,qBAAqB;AAC3C,CAAC;AAOM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,aAAa,EAAE,OAAO;AAAA,EACtB,UAAU;AACZ,CAAC;AAKM,IAAM,sBAAsB,iBAAiB,OAAO;AAAA,EACzD,SAAS,EAAE,MAAM,iBAAiB,EAAE,IAAI,EAAE;AAC5C,CAAC;AAWM,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,SAAS,EAAE,OAAO;AAAA,EAClB,QAAQ,EAAE,MAAM,WAAW;AAAA,EAC3B,OAAO,gBAAgB,SAAS;AAClC,CAAC;;;AC/GD,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,QAAQ,2CAA2C,KAAK,OAAO;AACrE,SAAO,QAAQ,CAAC,KAAK;AACvB;AAEO,IAAM,sBAAsB,kBAAkB,KAAK,EAAE,OAAO,KAAK,CAAC;AAClE,IAAM,oBAAoB,gBAAgB,KAAK,EAAE,OAAO,KAAK,CAAC;AAC9D,IAAM,wBAAwB,oBAAoB,KAAK,EAAE,OAAO,KAAK,CAAC;AAE7E,SAAS,cAAiB,KAAa,QAAyB;AAC9D,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,gBAAgB,GAAG,CAAC;AAAA,EAC1C,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,wCAAwC,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,UAAU,MAAM;AACtC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,+CAA+C,OAAO,MAAM,OAAO;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO;AAChB;AAEA,SAAS,qBAAqB,QAAgE;AAC5F,MAAI,OAAO,WAAW,WAAW,GAAG;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,OAAO,WAAW,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE;AAC1D,QAAM,QAAQ,OAAO,WAAW;AAChC,QAAM,eAAe,cAAc;AACnC,QAAM,cAAc,GAAG,SAAS,OAAO,KAAK;AAC5C,QAAM,YAAY,GAAG,WAAW,KAAK,OAAO,SAAS;AAErD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,KAAyC;AAC1E,QAAM,SAAS,cAAc,KAAK,mBAAmB;AACrD,SAAO,qBAAqB,MAAM;AACpC;AAEO,SAAS,iBAAiB,KAAuC;AACtE,SAAO,cAAc,KAAK,iBAAiB;AAC7C;AAEO,SAAS,qBAAqB,KAA2C;AAC9E,SAAO,cAAc,KAAK,qBAAqB;AACjD;;;ACxBA,SAAS,uBAAuB;AAGhC,SAAS,gBAAgB,QAAuC;AAC9D,SAAO;AAAA,IACL,gBAAgB,gBAAgB,QAAQ,EAAE,QAAQ,SAAS,CAAC;AAAA,EAC9D;AACF;AAyKA,IAAM,oBAAoB;AAAA,EACxB,WAAW,CAAC,WAAW,IAAI,gBAAgB,MAAM;AAAA,EACjD,QAAQ,CAAC,WAAW,IAAI,aAAa,MAAM;AAAA,EAC3C,QAAQ,CAAC,WAAW,IAAI,aAAa,MAAM;AAC7C;AAEA,SAAS,aAAa,UAAwB,QAAwC;AACpF,SAAO,kBAAkB,QAAQ,EAAE,MAAM;AAC3C;AAEA,IAAM,qBAAqB,gBAAgB,mBAAmB;AAC9D,IAAM,mBAAmB,gBAAgB,iBAAiB;AAC1D,IAAM,uBAAuB,gBAAgB,qBAAqB;AA+B3D,SAAS,SAAS,SAAyB,CAAC,GAAmB;AACpE,QAAM,iBAAiB,cAAc,MAAM;AAC3C,QAAM,eAA+B;AAAA,IACnC,QAAQ,eAAe;AAAA,IACvB,OAAO,eAAe;AAAA,IACtB,WAAW,eAAe;AAAA,IAC1B,iBAAiB,eAAe;AAAA,EAClC;AACA,QAAM,SAAS,aAAa,eAAe,UAAU,YAAY;AAEjE,iBAAe,wBACb,OACA,UACA,SACA,SACsB;AACtB,UAAM,aAAa,UAAU,oBAAoB;AACjD,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI,oBAAoB,wCAAwC,UAAU,IAAI;AAAA,IACtF;AAEA,WAAO,eAAe,gBAAgB,YAAY,YAAY;AAC5D,YAAM,MAAM,MAAM,eAAe,KAAK;AACtC,YAAM,SAAS,8BAA8B,UAAU,SAAS,OAAO;AACvE,eAAS,gBAAgB,GAAG,UAAU,WAAW,QAAQ,QAAQ;AAEjE,YAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,eAAS,gBAAgB,GAAG,UAAU,aAAa,SAAS,MAAM,UAAU;AAE5E,YAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,aAAO;AAAA,QACL,GAAG;AAAA,QACH,OAAO,aAAa,YAAY,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,MAC1F;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,MAAM,OAAO,YAAY,SAAS;AACtC,YAAM,QAAQ,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU;AAClE,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,oBAAoB,gDAAgD;AAAA,MAChF;AAEA,aAAO,eAAe,gBAAgB,SAAS,YAAY;AACzD,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,iBAAiB,OAAO,EAAE,cAAc,SAAS,aAAa,CAAC;AAC9E,iBAAS,gBAAgB,gBAAgB,QAAQ,QAAQ;AAEzD,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,iBAAS,gBAAgB,kBAAkB,SAAS,MAAM,UAAU;AAEpE,cAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,aAAa,SAAS,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QACvF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,SAAS;AACpC,aAAO,eAAe,gBAAgB,OAAO,YAAY;AACvD,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,eAAe,YAAY,EAAE,cAAc,SAAS,aAAa,CAAC;AACjF,iBAAS,gBAAgB,cAAc,QAAQ,QAAQ;AAEvD,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,gBAAgB;AAC/E,iBAAS,gBAAgB,gBAAgB,SAAS,MAAM,UAAU;AAElE,cAAM,SAAS,iBAAiB,SAAS,IAAI;AAC7C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,aAAa,OAAO,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QACrF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,QAAQ,QAAQ,QAAQ,SAAS;AACrC,aAAO,eAAe,gBAAgB,WAAW,YAAY;AAC3D,cAAM,CAAC,MAAM,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,eAAe,MAAM,GAAG,eAAe,MAAM,CAAC,CAAC;AACvF,cAAM,SAAS,mBAAmB;AAAA,UAChC,YAAY,SAAS;AAAA,UACrB,cAAc,SAAS;AAAA,QACzB,CAAC;AACD,iBAAS,gBAAgB,kBAAkB,QAAQ,QAAQ;AAE3D,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,MAAM,IAAI,GAAG,QAAQ,oBAAoB;AAC1F,iBAAS,gBAAgB,oBAAoB,SAAS,MAAM,UAAU;AAEtE,cAAM,wBACJ,eAAe,aAAa,YAC5B,eAAe,UAAU,MAAM,OAAO;AACxC,cAAM,qBAAqB,SAAS,cAAc,wBAAwB,OAAO;AAEjF,YAAI;AACJ,YAAI,oBAAoB;AACtB,cAAI;AACF,wBAAY,MAAM,eAAe,MAAM,MAAM,eAAe,OAAO,MAAM;AAAA,UAC3E,SAAS,KAAc;AACrB,kBAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,oBAAQ,OAAO;AAAA,cACb,2DAA2D,GAAG;AAAA;AAAA,YAChE;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,qBAAqB,SAAS,IAAI;AACjD,eAAO;AAAA,UACL,GAAG;AAAA,UACH,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,UACjC,OAAO,aAAa,WAAW,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QACzF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,gBAAgB,OAAO,UAAU,SAAS;AACxC,aAAO,wBAAwB,OAAO,UAAU,MAAM,OAAO;AAAA,IAC/D;AAAA,IAEA,eAAe,OAAO,UAAU,SAAS;AACvC,aAAO,wBAAwB,OAAO,UAAU,OAAO,OAAO;AAAA,IAChE;AAAA,IAEA,MAAM,cAAc,OAAO,SAAS;AAClC,aAAO,eAAe,gBAAgB,iBAAiB,YAAY;AACjE,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,yBAAyB,OAAO;AAC/C,iBAAS,gBAAgB,wBAAwB,QAAQ,QAAQ;AAEjE,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,iBAAS,gBAAgB,0BAA0B,SAAS,MAAM,UAAU;AAE5E,cAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO;AAAA,YACL;AAAA,YACA,SAAS;AAAA,YACT,SAAS;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,OAAO,SAAS;AAC3B,aAAO,eAAe,gBAAgB,UAAU,YAAY;AAC1D,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,kBAAkB,OAAO;AACxC,iBAAS,gBAAgB,iBAAiB,QAAQ,QAAQ;AAE1D,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,iBAAS,gBAAgB,mBAAmB,SAAS,MAAM,UAAU;AAErE,cAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,aAAa,UAAU,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QACxF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,SAAS,OAAO,SAAS;AAC7B,aAAO,eAAe,gBAAgB,YAAY,YAAY;AAC5D,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,oBAAoB,OAAO;AAC1C,iBAAS,gBAAgB,mBAAmB,QAAQ,QAAQ;AAE5D,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,iBAAS,gBAAgB,qBAAqB,SAAS,MAAM,UAAU;AAEvE,cAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,aAAa,YAAY,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QAC1F;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,QAAQ,OAAO,SAAS;AAC5B,aAAO,eAAe,gBAAgB,WAAW,YAAY;AAC3D,cAAM,MAAM,MAAM,eAAe,KAAK;AACtC,cAAM,SAAS,mBAAmB,OAAO;AACzC,iBAAS,gBAAgB,kBAAkB,QAAQ,QAAQ;AAE3D,cAAM,WAAW,MAAM,iBAAiB,QAAQ,CAAC,GAAG,GAAG,QAAQ,kBAAkB;AACjF,iBAAS,gBAAgB,oBAAoB,SAAS,MAAM,UAAU;AAEtE,cAAM,SAAS,mBAAmB,SAAS,IAAI;AAC/C,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,aAAa,WAAW,SAAS,OAAO,SAAS,iBAAiB,cAAc;AAAA,QACzF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC3bO,SAAS,kBAAkB,QAAqB,OAAwB;AAC7E,MAAI,OAAO,MAAM;AACf,UAAMC,UAAS,QAAQ,2BAA2B,KAAK,MAAM;AAC7D,WAAO,GAAGA,OAAM;AAAA,EAAK,IAAI,OAAOA,QAAO,MAAM,CAAC;AAAA,EAAK,OAAO,SAAS;AAAA,EACrE;AAEA,QAAM,SAAS,QAAQ,2BAA2B,KAAK,MAAM;AAC7D,QAAM,QAAkB,CAAC,QAAQ,IAAI,OAAO,OAAO,MAAM,GAAG,OAAO,SAAS;AAE5E,MAAI,OAAO,WAAW,SAAS,GAAG;AAChC,UAAM,KAAK,IAAI,aAAa;AAC5B,eAAW,KAAK,OAAO,YAAY;AACjC,YAAM,SAAS,EAAE,OAAO,SAAS;AACjC,YAAM,aAAa,EAAE,aAAa,KAAK,EAAE,UAAU,MAAM;AACzD,YAAM,KAAK,KAAK,MAAM,MAAM,EAAE,SAAS,GAAG;AAC1C,YAAM,KAAK,WAAW,EAAE,SAAS,GAAG,UAAU,EAAE;AAAA,IAClD;AAAA,EACF;AAEA,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,UAAM,KAAK,IAAI,SAAS;AACxB,eAAW,SAAS,OAAO,QAAQ;AACjC,YAAM,KAAK,MAAM,MAAM,QAAQ,IAAI,MAAM,QAAQ,KAAK,MAAM,WAAW,EAAE;AACzE,YAAM,KAAK,cAAS,MAAM,UAAU,EAAE;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAaO,SAAS,oBAAoB,QAAuB,OAAwB;AACjF,QAAM,SAAS,OAAO,OAAO,WAAW;AACxC,QAAM,SAAS,QAAQ,qBAAqB,MAAM,KAAK,KAAK,MAAM,qBAAqB,MAAM;AAC7F,QAAM,QAAkB,CAAC,QAAQ,IAAI,OAAO,OAAO,MAAM,GAAG,OAAO,SAAS;AAE5E,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,UAAM,KAAK,IAAI,UAAU;AACzB,eAAW,UAAU,OAAO,SAAS;AACnC,YAAM,KAAK,MAAM,OAAO,QAAQ,KAAK,OAAO,WAAW,EAAE;AAAA,IAC3D;AAAA,EACF;AAEA,MAAI,OAAO,WAAW;AACpB,UAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AACjC,UAAM,KAAK,IAAI,eAAe,KAAK,IAAI,MAAM,iBAAiB;AAAA,EAChE;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAcO,SAAS,mBAAmB,QAAqB,OAAsB;AAC5E,MAAI,CAAC,OAAO,MAAM;AAChB,UAAM,IAAI,uBAAuB,kBAAkB,QAAQ,KAAK,GAAG,MAAM;AAAA,EAC3E;AACF;AAcO,SAAS,0BAA0B,QAAuB,OAAsB;AACrF,MAAI,CAAC,OAAO,MAAM;AAChB,UAAM,IAAI,uBAAuB,oBAAoB,QAAQ,KAAK,GAAG,MAAM;AAAA,EAC7E;AACF;","names":["ALL_CHECKS","CHECK_STATEMENTS","ALL_CHECKS","CHECK_STATEMENTS","sharp","width","height","sharp","header"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/core/prompt.ts","../src/templates/elements-visibility.ts","../src/templates/accessibility.ts","../src/templates/layout.ts","../src/templates/page-load.ts","../src/templates/content.ts","../src/providers/error-mapper.ts","../src/providers/anthropic.ts","../src/providers/google.ts","../src/providers/openai.ts","../src/core/config.ts","../src/core/pricing.ts","../src/core/debug.ts","../src/core/diff.ts","../src/core/image.ts","../src/core/input-detect.ts","../src/core/video.ts","../src/core/media.ts","../src/types.ts","../src/core/response.ts","../src/core/client.ts","../src/format.ts"],"sourcesContent":["import type { ProviderName } from \"./types.js\";\n\n// --- Reasoning effort constants ---\n\n/** Supported reasoning effort levels. */\nexport const ReasoningEffort = {\n LOW: \"low\",\n MEDIUM: \"medium\",\n HIGH: \"high\",\n XHIGH: \"xhigh\",\n} as const;\n\n/** Union of valid reasoning effort values, derived from the ReasoningEffort constant. */\nexport type ReasoningEffortLevel = (typeof ReasoningEffort)[keyof typeof ReasoningEffort];\n\n// --- Provider constants ---\n\n/** Supported provider identifiers used internally for pricing and provider selection. */\nexport const Provider = {\n ANTHROPIC: \"anthropic\",\n OPENAI: \"openai\",\n GOOGLE: \"google\",\n} as const satisfies Record<string, ProviderName>;\n\n// --- Model constants (grouped by provider) ---\n\n/** Known model names grouped by provider. */\nexport const Model = {\n Anthropic: {\n OPUS_4_7: \"claude-opus-4-7\",\n OPUS_4_6: \"claude-opus-4-6\",\n SONNET_4_6: \"claude-sonnet-4-6\",\n HAIKU_4_5: \"claude-haiku-4-5\",\n },\n OpenAI: {\n GPT_5_5: \"gpt-5.5\",\n GPT_5_4: \"gpt-5.4\",\n GPT_5_4_PRO: \"gpt-5.4-pro\",\n GPT_5_4_MINI: \"gpt-5.4-mini\",\n GPT_5_4_NANO: \"gpt-5.4-nano\",\n GPT_5_2: \"gpt-5.2\",\n GPT_5_MINI: \"gpt-5-mini\",\n },\n Google: {\n GEMINI_3_1_PRO_PREVIEW: \"gemini-3.1-pro-preview\",\n GEMINI_3_1_FLASH_LITE_PREVIEW: \"gemini-3.1-flash-lite-preview\",\n GEMINI_3_FLASH_PREVIEW: \"gemini-3-flash-preview\",\n },\n} as const;\n\n// --- Derived utility types ---\n\n/** Union of all built-in model name literals exposed by `Model`. */\nexport type KnownModelName =\n | (typeof Model.Anthropic)[keyof typeof Model.Anthropic]\n | (typeof Model.OpenAI)[keyof typeof Model.OpenAI]\n | (typeof Model.Google)[keyof typeof Model.Google];\n\n// --- Default model per provider ---\n\n/** Default model selection used when a caller omits `config.model`. */\nexport const DEFAULT_MODELS = {\n [Provider.ANTHROPIC]: Model.Anthropic.SONNET_4_6,\n [Provider.OPENAI]: Model.OpenAI.GPT_5_4_MINI,\n [Provider.GOOGLE]: Model.Google.GEMINI_3_FLASH_PREVIEW,\n} as const satisfies Record<ProviderName, KnownModelName>;\n\nexport const DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Increased token budget for OpenAI when reasoning effort is high/xhigh.\n * Reasoning tokens share the output budget on OpenAI, so the default 4096\n * is insufficient for higher reasoning levels.\n */\nexport const OPENAI_REASONING_MAX_TOKENS = 16384;\n\n// --- Reverse map: model → provider ---\n\nexport const MODEL_TO_PROVIDER: ReadonlyMap<string, ProviderName> = new Map([\n ...Object.values(Model.Anthropic).map((m) => [m, Provider.ANTHROPIC] as const),\n ...Object.values(Model.OpenAI).map((m) => [m, Provider.OPENAI] as const),\n ...Object.values(Model.Google).map((m) => [m, Provider.GOOGLE] as const),\n]);\n\n// --- Valid providers array ---\n\n/** List of accepted provider names for validation and public consumption. */\nexport const VALID_PROVIDERS: readonly ProviderName[] = Object.values(Provider);\n\n// --- Provider default reasoning ---\n\n/**\n * What each provider uses when no reasoning effort is explicitly requested.\n * These are informational only — displayed in usage logs, not sent to providers.\n */\nexport const PROVIDER_DEFAULT_REASONING: Readonly<Record<ProviderName, string>> = {\n openai: \"medium\",\n anthropic: \"off\",\n google: \"off\",\n};\n\n// --- Check name constants ---\n\n/** Built-in content checks available through `client.content()`. */\nexport const Content = {\n /** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */\n PLACEHOLDER_TEXT: \"placeholder-text\",\n /** Detects error messages, banners, stack traces, or error codes */\n ERROR_MESSAGES: \"error-messages\",\n /** Detects broken image icons or failed-to-load image indicators */\n BROKEN_IMAGES: \"broken-images\",\n /** Detects UI elements that unintentionally overlap and obscure content */\n OVERLAPPING_ELEMENTS: \"overlapping-elements\",\n} as const;\n\n/** Built-in layout checks available through `client.layout()`. */\nexport const Layout = {\n /** Detects elements that unintentionally overlap each other */\n OVERLAP: \"overlap\",\n /** Detects content cut off or extending beyond container boundaries */\n OVERFLOW: \"overflow\",\n /** Detects inconsistent alignment of text, images, and UI components */\n ALIGNMENT: \"alignment\",\n} as const;\n\n/** Built-in accessibility checks available through `client.accessibility()`. */\nexport const Accessibility = {\n /** Detects insufficient color contrast between text and backgrounds */\n CONTRAST: \"contrast\",\n /** Detects text that is cut off, overlapping, too small, or obscured */\n READABILITY: \"readability\",\n /** Detects interactive elements that are not visually distinct */\n INTERACTIVE_VISIBILITY: \"interactive-visibility\",\n} as const;\n\n// --- Derived check-name union types ---\n\n/** Union of all built-in content check names. */\nexport type ContentCheckName = (typeof Content)[keyof typeof Content];\n/** Union of all built-in layout check names. */\nexport type LayoutCheckName = (typeof Layout)[keyof typeof Layout];\n/** Union of all built-in accessibility check names. */\nexport type AccessibilityCheckName = (typeof Accessibility)[keyof typeof Accessibility];\n","import type { CheckResult, CompareResult } from \"./types.js\";\n\n/**\n * Discrete error codes exposed by visual-ai-assertions for programmatic handling.\n */\nexport type VisualAIErrorCode =\n | \"VISUAL_AI_ERROR\"\n | \"AUTH_FAILED\"\n | \"RATE_LIMITED\"\n | \"PROVIDER_ERROR\"\n | \"IMAGE_INVALID\"\n | \"VIDEO_INVALID\"\n | \"RESPONSE_PARSE_FAILED\"\n | \"RESPONSE_TRUNCATED\"\n | \"CONFIG_INVALID\"\n | \"ASSERTION_FAILED\";\n\n/**\n * Base class for all library errors.\n *\n * @example\n * ```ts\n * try {\n * // ...\n * } catch (error) {\n * if (error instanceof VisualAIError) {\n * console.error(error.code, error.message);\n * }\n * }\n * ```\n */\nexport class VisualAIError<TCode extends VisualAIErrorCode = VisualAIErrorCode> extends Error {\n readonly code: TCode;\n\n constructor(message: string, code: TCode = \"VISUAL_AI_ERROR\" as TCode) {\n super(message);\n this.code = code;\n this.name = \"VisualAIError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a provider rejects the configured API credentials.\n *\n * @example\n * ```ts\n * throw new VisualAIAuthError(\"Anthropic API key not found\");\n * ```\n */\nexport class VisualAIAuthError extends VisualAIError<\"AUTH_FAILED\"> {\n declare readonly code: \"AUTH_FAILED\";\n\n constructor(message: string) {\n super(message, \"AUTH_FAILED\");\n this.name = \"VisualAIAuthError\";\n }\n}\n\n/**\n * Thrown when a provider enforces a rate limit.\n *\n * Carries `retryAfter` when the provider includes retry guidance.\n *\n * @example\n * ```ts\n * throw new VisualAIRateLimitError(\"Rate limited\", 30);\n * ```\n */\nexport class VisualAIRateLimitError extends VisualAIError<\"RATE_LIMITED\"> {\n declare readonly code: \"RATE_LIMITED\";\n retryAfter?: number;\n\n constructor(message: string, retryAfter?: number) {\n super(message, \"RATE_LIMITED\");\n this.name = \"VisualAIRateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Thrown when a provider returns an unexpected non-auth, non-rate-limit failure.\n *\n * Carries `statusCode` when the provider exposes an HTTP status.\n *\n * @example\n * ```ts\n * throw new VisualAIProviderError(\"Provider returned 500\", 500);\n * ```\n */\nexport class VisualAIProviderError extends VisualAIError<\"PROVIDER_ERROR\"> {\n declare readonly code: \"PROVIDER_ERROR\";\n statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message, \"PROVIDER_ERROR\");\n this.name = \"VisualAIProviderError\";\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Thrown when an image input cannot be loaded, decoded, or validated.\n *\n * @example\n * ```ts\n * throw new VisualAIImageError(\"Unsupported image format: image/bmp\");\n * ```\n */\nexport class VisualAIImageError extends VisualAIError<\"IMAGE_INVALID\"> {\n declare readonly code: \"IMAGE_INVALID\";\n\n constructor(message: string) {\n super(message, \"IMAGE_INVALID\");\n this.name = \"VisualAIImageError\";\n }\n}\n\n/**\n * Thrown when a video input cannot be loaded, decoded, or sampled — including\n * when the optional ffmpeg peer dependencies are missing, the source is corrupt,\n * or the duration exceeds the configured cap.\n *\n * @example\n * ```ts\n * throw new VisualAIVideoError(\"Video duration 14.2s exceeds limit of 10s\");\n * ```\n */\nexport class VisualAIVideoError extends VisualAIError<\"VIDEO_INVALID\"> {\n declare readonly code: \"VIDEO_INVALID\";\n\n constructor(message: string) {\n super(message, \"VIDEO_INVALID\");\n this.name = \"VisualAIVideoError\";\n }\n}\n\n/**\n * Thrown when a provider response cannot be parsed into the library result schema.\n *\n * Carries `rawResponse` so callers can inspect the original model output.\n *\n * @example\n * ```ts\n * throw new VisualAIResponseParseError(\"Invalid JSON\", rawText);\n * ```\n */\nexport class VisualAIResponseParseError extends VisualAIError<\"RESPONSE_PARSE_FAILED\"> {\n declare readonly code: \"RESPONSE_PARSE_FAILED\";\n rawResponse: string;\n\n constructor(message: string, rawResponse: string) {\n super(message, \"RESPONSE_PARSE_FAILED\");\n this.name = \"VisualAIResponseParseError\";\n this.rawResponse = rawResponse;\n }\n}\n\n/**\n * Thrown when a provider response is truncated due to token budget exhaustion.\n *\n * Carries `partialResponse` and `maxTokens` so callers can inspect the output\n * and adjust configuration.\n *\n * @example\n * ```ts\n * throw new VisualAITruncationError(\"Response truncated\", partialText, 4096);\n * ```\n */\nexport class VisualAITruncationError extends VisualAIError<\"RESPONSE_TRUNCATED\"> {\n declare readonly code: \"RESPONSE_TRUNCATED\";\n readonly partialResponse: string;\n readonly maxTokens: number;\n\n constructor(message: string, partialResponse: string, maxTokens: number) {\n super(message, \"RESPONSE_TRUNCATED\");\n this.name = \"VisualAITruncationError\";\n this.partialResponse = partialResponse;\n this.maxTokens = maxTokens;\n }\n}\n\n/**\n * Thrown when library configuration is missing or invalid.\n *\n * @example\n * ```ts\n * throw new VisualAIConfigError(\"At least one statement is required for check()\");\n * ```\n */\nexport class VisualAIConfigError extends VisualAIError<\"CONFIG_INVALID\"> {\n declare readonly code: \"CONFIG_INVALID\";\n\n constructor(message: string) {\n super(message, \"CONFIG_INVALID\");\n this.name = \"VisualAIConfigError\";\n }\n}\n\n/**\n * Thrown by assertion helpers when a visual check or comparison fails.\n *\n * Carries the failed `result` for further inspection.\n *\n * @example\n * ```ts\n * throw new VisualAIAssertionError(\"Visual assertion failed\", result);\n * ```\n */\nexport class VisualAIAssertionError extends VisualAIError<\"ASSERTION_FAILED\"> {\n declare readonly code: \"ASSERTION_FAILED\";\n result: CheckResult | CompareResult;\n\n constructor(message: string, result: CheckResult | CompareResult) {\n super(message, \"ASSERTION_FAILED\");\n this.name = \"VisualAIAssertionError\";\n this.result = result;\n }\n}\n\n/**\n * Union of all concrete error subclasses exposed by the library.\n */\nexport type VisualAIKnownError =\n | VisualAIAuthError\n | VisualAIRateLimitError\n | VisualAIProviderError\n | VisualAIImageError\n | VisualAIVideoError\n | VisualAIResponseParseError\n | VisualAITruncationError\n | VisualAIConfigError\n | VisualAIAssertionError;\n\n/**\n * Narrows an unknown thrown value to the concrete visual-ai-assertions error union.\n *\n * Use this helper when you want `switch (error.code)` to narrow to subclass-specific fields.\n *\n * @param error Unknown thrown value.\n * @returns `true` when the value is one of the concrete library error subclasses.\n * @example\n * ```ts\n * try {\n * // ...\n * } catch (error) {\n * if (isVisualAIKnownError(error)) {\n * switch (error.code) {\n * case \"RATE_LIMITED\":\n * console.error(error.retryAfter);\n * break;\n * case \"PROVIDER_ERROR\":\n * console.error(error.statusCode);\n * break;\n * }\n * }\n * }\n * ```\n */\nexport function isVisualAIKnownError(error: unknown): error is VisualAIKnownError {\n return (\n error instanceof VisualAIAuthError ||\n error instanceof VisualAIRateLimitError ||\n error instanceof VisualAIProviderError ||\n error instanceof VisualAIImageError ||\n error instanceof VisualAIVideoError ||\n error instanceof VisualAIResponseParseError ||\n error instanceof VisualAITruncationError ||\n error instanceof VisualAIConfigError ||\n error instanceof VisualAIAssertionError\n );\n}\n","const JSON_INSTRUCTIONS = `\nIMPORTANT: You MUST respond with valid JSON only. No markdown, no code blocks, no extra text.\n`;\n\nconst ISSUE_SCHEMA_INSTRUCTIONS = `\nEach issue must have:\n- \"priority\": \"critical\" | \"major\" | \"minor\"\n- \"category\": \"accessibility\" | \"missing-element\" | \"layout\" | \"content\" | \"styling\" | \"functionality\" | \"performance\" | \"other\"\n- \"description\": what the issue is\n- \"suggestion\": how to fix or improve it\n`;\n\nconst CHECK_OUTPUT_SCHEMA_IMAGE = `IMPORTANT: Follow this evaluation order:\n1. First, evaluate EACH statement independently and populate the \"statements\" array\n2. Then, set \"pass\" to true ONLY if every statement passed (logical AND of all statement results)\n3. Write \"reasoning\" as a brief overall summary of the evaluation\n4. Include \"issues\" only for statements that failed\n\nRespond with a JSON object matching this exact structure:\n{\n \"pass\": boolean, // true ONLY if ALL statements passed — derive from statements array\n \"reasoning\": string, // brief overall summary of the evaluation\n \"issues\": [...], // one issue per failing statement (empty if all pass)\n \"statements\": [ // one entry per statement, in order — evaluate these FIRST\n {\n \"statement\": string, // the original statement text\n \"pass\": boolean, // whether this statement is true\n \"reasoning\": string, // explanation for this statement\n \"confidence\": \"high\" | \"medium\" | \"low\"\n // high = clearly visible/absent with no ambiguity\n // medium = present but partially obscured, small, or borderline\n // low = cannot determine with certainty from the screenshot\n }\n ]\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nOnly include issues for statements that fail. If all statements pass, issues should be an empty array.\n\nExample for a failing check:\n{\n \"pass\": false,\n \"reasoning\": \"The submit button is not visible on the page.\",\n \"issues\": [\n { \"priority\": \"major\", \"category\": \"missing-element\", \"description\": \"Submit button is not visible on the page\", \"suggestion\": \"Verify the submit button component is rendered and not hidden by CSS\" }\n ],\n \"statements\": [\n { \"statement\": \"The page header is visible\", \"pass\": true, \"reasoning\": \"Header with logo is clearly visible at the top\", \"confidence\": \"high\" },\n { \"statement\": \"The submit button is visible\", \"pass\": false, \"reasoning\": \"No submit button found in the visible area of the page\", \"confidence\": \"high\" }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n\nconst CHECK_OUTPUT_SCHEMA_VIDEO = `IMPORTANT: Follow this evaluation order:\n1. First, evaluate EACH statement independently across the entire timeline and populate the \"statements\" array\n2. A statement passes if it is true at ANY frame of the timeline, unless the wording explicitly says otherwise (e.g. \"throughout\", \"at all times\")\n3. For each statement that passes, set \"timestampSeconds\" to the timestamp of the frame that most clearly demonstrates it (or where it first becomes true). Use null when the statement fails or applies across the whole clip.\n4. Then, set \"pass\" to true ONLY if every statement passed (logical AND of all statement results)\n5. Write \"reasoning\" as a brief overall summary of the evaluation\n6. Include \"issues\" only for statements that failed\n\nRespond with a JSON object matching this exact structure:\n{\n \"pass\": boolean, // true ONLY if ALL statements passed — derive from statements array\n \"reasoning\": string, // brief overall summary of the evaluation\n \"issues\": [...], // one issue per failing statement (empty if all pass)\n \"statements\": [ // one entry per statement, in order — evaluate these FIRST\n {\n \"statement\": string, // the original statement text\n \"pass\": boolean, // whether this statement is true at any point in the timeline\n \"reasoning\": string, // explanation for this statement, citing frame timestamps where relevant\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"timestampSeconds\": number | null\n // seconds from the start of the clip where the statement is most clearly true,\n // or null if it failed / applies across the whole clip\n }\n ]\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nOnly include issues for statements that fail. If all statements pass, issues should be an empty array.\n\nExample for a passing video check:\n{\n \"pass\": true,\n \"reasoning\": \"The success toast appeared briefly around 3.5s.\",\n \"issues\": [],\n \"statements\": [\n { \"statement\": \"A success toast with text 'Saved' appears\", \"pass\": true, \"reasoning\": \"A green toast labeled 'Saved' is visible in the bottom-right at the 3.5s frame\", \"confidence\": \"high\", \"timestampSeconds\": 3.5 }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n\nconst ASK_OUTPUT_SCHEMA_IMAGE = `Respond with a JSON object matching this exact structure:\n{\n \"summary\": string, // high-level analysis summary\n \"issues\": [...] // list of issues/findings, can be empty\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nPrioritize issues by severity:\n- \"critical\": blocks functionality, breaks accessibility, data loss risk\n- \"major\": significant usability or visual problem\n- \"minor\": cosmetic issue, minor improvement suggestion\n\nExample:\n{\n \"summary\": \"Found 2 issues: a critical accessibility problem and a minor cosmetic issue.\",\n \"issues\": [\n { \"priority\": \"critical\", \"category\": \"accessibility\", \"description\": \"Submit button has insufficient color contrast\", \"suggestion\": \"Increase contrast so text is clearly readable against the background\" },\n { \"priority\": \"minor\", \"category\": \"content\", \"description\": \"Placeholder text 'Lorem ipsum' visible in sidebar\", \"suggestion\": \"Replace with actual content or remove the placeholder section\" }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n\nconst ASK_OUTPUT_SCHEMA_VIDEO = `Respond with a JSON object matching this exact structure:\n{\n \"summary\": string, // high-level summary of what happens across the timeline\n \"issues\": [...], // list of issues/findings, can be empty\n \"frameReferences\": number[] // 0-based indices of frames the answer relies on (in order)\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nPrioritize issues by severity (critical / major / minor) as for image input.\nCite frame indices in \"frameReferences\" so the user can locate the moments you describe.\n${JSON_INSTRUCTIONS}`;\n\nconst COMPARE_OUTPUT_SCHEMA = `Respond with a JSON object matching this exact structure:\n{\n \"pass\": boolean, // true if no critical or major changes found\n \"reasoning\": string, // overall summary of changes detected\n \"changes\": [ // list of all visual differences detected (empty if images are identical)\n {\n \"description\": string, // what changed between the images\n \"severity\": \"critical\" | \"major\" | \"minor\"\n // critical = element removed, layout broken, functionality lost\n // major = significant visual change that may be unintentional\n // minor = small stylistic difference (color, spacing, font)\n }\n ]\n}\n\nIf the images appear identical, set pass to true, explain in reasoning, and return an empty changes array.\n${JSON_INSTRUCTIONS}`;\n\nconst DEFAULT_CHECK_ROLE =\n \"You are a visual QA assistant. Evaluate the provided image precisely and objectively.\";\n\nconst DEFAULT_CHECK_ROLE_VIDEO =\n \"You are a visual QA assistant. Evaluate the provided sequence of video frames precisely and objectively, treating them as a chronological timeline.\";\n\nconst DEFAULT_ASK_ROLE =\n \"You are a visual QA assistant. Analyze the provided image based on the user's request.\";\n\nconst DEFAULT_ASK_ROLE_VIDEO =\n \"You are a visual QA assistant. Analyze the provided sequence of video frames as a chronological timeline based on the user's request.\";\n\n/**\n * Describes the media accompanying a prompt so the builders can adapt the\n * role, schema, and timeline guidance accordingly.\n */\nexport type MediaContext =\n | { kind: \"image\" }\n | { kind: \"video\"; frameTimestamps: readonly number[]; durationSeconds: number };\n\nfunction buildVideoTimelineSection(\n frameTimestamps: readonly number[],\n durationSeconds: number,\n): string {\n const formatted = frameTimestamps.map((t, i) => ` ${i}: ${t.toFixed(2)}s`).join(\"\\n\");\n return `Video timeline:\n- Total duration: ${durationSeconds.toFixed(2)}s\n- ${frameTimestamps.length} frames sampled (in chronological order)\n- Frame index → timestamp:\n${formatted}\n\nTreat the attached images as a chronological timeline. The first image is the earliest frame, the last is the latest. Refer to frames by timestamp where helpful.`;\n}\n\nconst COMPARE_ROLE =\n \"You are performing a visual regression test. Compare the BEFORE image (baseline) to the AFTER image (current) and identify all visual differences. Flag changes that appear unintentional or problematic.\";\n\nconst COMPARE_EDGE_RULES: readonly string[] = [\n \"The BEFORE image is the baseline/expected state.\",\n \"Flag removals and layout changes as higher severity. Color and spacing changes are lower severity unless they break readability.\",\n \"If the images appear identical, report no changes and pass.\",\n];\n\nexport interface CheckPromptOptions {\n readonly role?: string;\n readonly instructions?: readonly string[];\n readonly media?: MediaContext;\n}\n\nexport interface AskPromptOptions {\n readonly instructions?: readonly string[];\n readonly media?: MediaContext;\n}\n\nexport interface ComparePromptOptions {\n readonly userPrompt?: string;\n readonly instructions?: readonly string[];\n}\n\nfunction buildInstructionsSection(instructions: readonly string[]): string {\n return (\n \"Additional instructions:\\n\" + instructions.map((instruction) => `- ${instruction}`).join(\"\\n\")\n );\n}\n\nexport function buildCheckPrompt(\n statements: string | string[],\n options?: CheckPromptOptions,\n): string {\n const stmts = Array.isArray(statements) ? statements : [statements];\n const statementsBlock = stmts.map((s, i) => `${i + 1}. \"${s}\"`).join(\"\\n\");\n const media = options?.media;\n const defaultRole = media?.kind === \"video\" ? DEFAULT_CHECK_ROLE_VIDEO : DEFAULT_CHECK_ROLE;\n\n const sections = [options?.role ?? defaultRole];\n\n if (media?.kind === \"video\") {\n sections.push(buildVideoTimelineSection(media.frameTimestamps, media.durationSeconds));\n }\n\n if (options?.instructions && options.instructions.length > 0) {\n sections.push(buildInstructionsSection(options.instructions));\n }\n\n sections.push(`Statements to evaluate:\\n${statementsBlock}`);\n sections.push(media?.kind === \"video\" ? CHECK_OUTPUT_SCHEMA_VIDEO : CHECK_OUTPUT_SCHEMA_IMAGE);\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function buildAskPrompt(userPrompt: string, options?: AskPromptOptions): string {\n const media = options?.media;\n const sections = [media?.kind === \"video\" ? DEFAULT_ASK_ROLE_VIDEO : DEFAULT_ASK_ROLE];\n\n if (media?.kind === \"video\") {\n sections.push(buildVideoTimelineSection(media.frameTimestamps, media.durationSeconds));\n }\n\n if (options?.instructions && options.instructions.length > 0) {\n sections.push(buildInstructionsSection(options.instructions));\n }\n\n sections.push(`User request: ${userPrompt}`);\n sections.push(media?.kind === \"video\" ? ASK_OUTPUT_SCHEMA_VIDEO : ASK_OUTPUT_SCHEMA_IMAGE);\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function buildAiDiffPrompt(): string {\n return `You are given two screenshots of the same page or component.\nGenerate a single annotated image that clearly highlights the visual differences between the first and second images.\n- Overlay semi-transparent red rectangles or outlines on areas that changed\n- Keep unchanged areas visible but slightly dimmed\n- The output image should match the dimensions of the input images\n- Focus on meaningful visual differences: layout shifts, missing elements, color changes, text differences`;\n}\n\nexport function buildAiDiffCodeExecutionPrompt(): string {\n return `${buildAiDiffPrompt()}\n\nWrite Python code using PIL/Pillow for image processing and matplotlib for rendering to accomplish the above.\nYour code MUST load both input images and display the result using matplotlib.`;\n}\n\nexport function buildComparePrompt(options?: ComparePromptOptions): string {\n const evaluation = options?.userPrompt\n ? `User request: ${options.userPrompt}`\n : \"Identify all visual differences between the baseline and current screenshot. Flag any changes that appear unintentional or problematic.\";\n\n const instructions = options?.instructions\n ? [...COMPARE_EDGE_RULES, ...options.instructions]\n : COMPARE_EDGE_RULES;\n\n const sections = [\n COMPARE_ROLE,\n buildInstructionsSection(instructions),\n evaluation,\n COMPARE_OUTPUT_SCHEMA,\n ];\n\n return sections.join(\"\\n\\n\");\n}\n","import type { ElementsVisibilityOptions } from \"../types.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ELEMENTS_VISIBLE_ROLE =\n \"Check whether specific UI elements are present and fully visible in this screenshot.\";\n\nconst ELEMENTS_HIDDEN_ROLE =\n \"Check whether specific UI elements are absent or hidden in this screenshot.\";\n\nconst ELEMENTS_VISIBLE_EDGE_RULES: readonly string[] = [\n \"If an element is partially visible (cut off by screenshot boundary), it is NOT considered fully visible — the check for that element should fail. Note the partial visibility in your reasoning.\",\n];\n\nconst ELEMENTS_HIDDEN_EDGE_RULES: readonly string[] = [\n \"If an element is partially visible (cut off by screenshot boundary), it is NOT considered hidden — the check for that element should fail. Note the partial visibility in your reasoning.\",\n];\n\nexport function buildElementsVisibilityPrompt(\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n): string {\n const statements = visible\n ? elements.map((el) => `The element \"${el}\" is fully visible on the page`)\n : elements.map((el) => `The element \"${el}\" is NOT visible on the page`);\n\n const defaultRules = visible ? ELEMENTS_VISIBLE_EDGE_RULES : ELEMENTS_HIDDEN_EDGE_RULES;\n const instructions = options?.instructions\n ? [...defaultRules, ...options.instructions]\n : defaultRules;\n\n return buildCheckPrompt(statements, {\n role: visible ? ELEMENTS_VISIBLE_ROLE : ELEMENTS_HIDDEN_ROLE,\n instructions,\n });\n}\n","import type { AccessibilityOptions } from \"../types.js\";\nimport { Accessibility, type AccessibilityCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: AccessibilityCheckName[] = Object.values(Accessibility);\n\nconst ACCESSIBILITY_ROLE =\n \"Evaluate this screenshot for visual accessibility. Focus on what you can actually perceive — apparent contrast levels, text legibility, and visual distinctiveness of interactive elements.\";\n\nconst ACCESSIBILITY_EDGE_RULES: readonly string[] = [\n \"Do not state specific contrast ratios. Describe contrast as 'appears sufficient' or 'appears low'.\",\n \"Dark mode and light mode themes are both valid. Do not flag a valid dark theme as a contrast issue.\",\n];\n\nconst CHECK_STATEMENTS: Record<AccessibilityCheckName, string> = {\n [Accessibility.CONTRAST]:\n \"All text and interactive elements appear to have sufficient color contrast — text is clearly readable against its background\",\n [Accessibility.READABILITY]:\n \"All text is readable — no text is cut off, overlapping, too small to read, or obscured by background images\",\n [Accessibility.INTERACTIVE_VISIBILITY]:\n \"All interactive elements (buttons, links, inputs) are clearly identifiable and visually distinct from non-interactive content\",\n};\n\nexport function buildAccessibilityPrompt(options?: AccessibilityOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n const instructions = options?.instructions\n ? [...ACCESSIBILITY_EDGE_RULES, ...options.instructions]\n : ACCESSIBILITY_EDGE_RULES;\n\n return buildCheckPrompt(statements, {\n role: ACCESSIBILITY_ROLE,\n instructions,\n });\n}\n","import type { LayoutOptions } from \"../types.js\";\nimport { Layout, type LayoutCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: LayoutCheckName[] = Object.values(Layout);\n\nconst LAYOUT_ROLE =\n \"Evaluate this screenshot for visual layout problems — overlapping elements, content that appears cut off or overflowing, and inconsistent alignment patterns.\";\n\nconst LAYOUT_EDGE_RULES: readonly string[] = [\n \"Intentional overlaps (stacked avatars, dropdown menus, overlapping cards) are acceptable. Flag only overlaps that obscure content or appear broken.\",\n \"Scrollable containers with partially visible content are not overflow issues.\",\n];\n\nconst CHECK_STATEMENTS: Record<LayoutCheckName, string> = {\n [Layout.OVERLAP]:\n \"No elements overlap each other unintentionally — all content is clearly separated and readable\",\n [Layout.OVERFLOW]:\n \"No content appears to be unintentionally cut off or extending beyond its container boundaries\",\n [Layout.ALIGNMENT]:\n \"Elements are properly aligned — text, images, and UI components follow a consistent grid or alignment pattern\",\n};\n\nexport function buildLayoutPrompt(options?: LayoutOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n const instructions = options?.instructions\n ? [...LAYOUT_EDGE_RULES, ...options.instructions]\n : LAYOUT_EDGE_RULES;\n\n return buildCheckPrompt(statements, { role: LAYOUT_ROLE, instructions });\n}\n","import type { PageLoadOptions } from \"../types.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst PAGE_LOAD_ROLE =\n \"Evaluate whether this page has finished loading. Look for loading indicators, empty content areas, and missing resources.\";\n\nexport function buildPageLoadPrompt(options?: PageLoadOptions): string {\n const expectLoaded = options?.expectLoaded ?? true;\n\n const statements = expectLoaded\n ? [\n \"The page content has finished loading — no spinning indicators, skeleton placeholders, or progress bars are visible\",\n \"The main content area has actual content displayed (not blank or empty)\",\n \"No broken image icons or missing resource indicators are visible\",\n ]\n : [\n \"The page shows a loading state — loading spinners, skeleton screens, or progress indicators are visible\",\n ];\n\n return buildCheckPrompt(statements, {\n role: PAGE_LOAD_ROLE,\n instructions: options?.instructions,\n });\n}\n","import type { ContentOptions } from \"../types.js\";\nimport { Content, type ContentCheckName } from \"../constants.js\";\nimport { buildCheckPrompt } from \"../core/prompt.js\";\n\nconst ALL_CHECKS: ContentCheckName[] = Object.values(Content);\n\nconst CONTENT_ROLE =\n \"Evaluate this screenshot for content quality problems — placeholder or dummy content, error states, and broken resources that should not appear in a production UI.\";\n\nconst CHECK_STATEMENTS: Record<ContentCheckName, string> = {\n [Content.PLACEHOLDER_TEXT]:\n \"No placeholder text like 'Lorem ipsum', 'TODO', 'TBD', 'placeholder', or similar dummy content is visible on the page\",\n [Content.ERROR_MESSAGES]:\n \"No error messages, error banners, stack traces, or error codes are visible on the page\",\n [Content.BROKEN_IMAGES]:\n \"No broken image icons, missing image placeholders, or failed-to-load image indicators are visible\",\n [Content.OVERLAPPING_ELEMENTS]:\n \"No UI elements are unintentionally overlapping each other, obscuring text, buttons, or other interactive content\",\n};\n\nexport function buildContentPrompt(options?: ContentOptions): string {\n const checks = options?.checks ?? [...ALL_CHECKS];\n const statements = checks.map((c) => CHECK_STATEMENTS[c]);\n\n return buildCheckPrompt(statements, {\n role: CONTENT_ROLE,\n instructions: options?.instructions,\n });\n}\n","import { VisualAIAuthError, VisualAIProviderError, VisualAIRateLimitError } from \"../errors.js\";\n\nexport function mapProviderError(err: unknown): Error {\n if (!(err instanceof Error)) {\n return new VisualAIProviderError(String(err));\n }\n\n const status = (err as { status?: number }).status;\n\n if (status === 401 || status === 403) {\n return new VisualAIAuthError(err.message);\n }\n if (status === 429) {\n const headers = (err as { headers?: Record<string, string> }).headers;\n const retryAfter = parseRetryAfter(headers?.[\"retry-after\"]);\n return new VisualAIRateLimitError(err.message, retryAfter);\n }\n if (status !== undefined) {\n return new VisualAIProviderError(err.message, status);\n }\n\n return new VisualAIProviderError(err.message);\n}\n\nfunction parseRetryAfter(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n","import { Model, type ReasoningEffortLevel } from \"../constants.js\";\nimport { VisualAIAuthError, VisualAIConfigError, VisualAITruncationError } from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\n// Opus 4.7 introduced a dedicated \"xhigh\" effort tier. Older Anthropic models\n// (Opus 4.6, Sonnet 4.6) reject \"xhigh\" but accept \"max\", which is why our\n// xhigh has historically mapped to \"max\" everywhere.\nfunction mapEffort(level: ReasoningEffortLevel, model: string): string {\n if (level !== \"xhigh\") return level;\n return model === Model.Anthropic.OPUS_4_7 ? \"xhigh\" : \"max\";\n}\n\n/** Minimal interface for the Anthropic SDK client used by this driver. */\ninterface AnthropicMessage {\n content: Array<{ type: string; text?: string }>;\n usage: { input_tokens: number; output_tokens: number };\n stop_reason?: string;\n}\n\ninterface AnthropicClient {\n messages: {\n create(params: Record<string, unknown>): Promise<AnthropicMessage>;\n };\n}\n\nexport class AnthropicDriver implements ProviderDriver {\n private client: AnthropicClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private async getClient(): Promise<AnthropicClient> {\n if (this.client) return this.client;\n\n let Anthropic: unknown;\n try {\n const mod: unknown = await import(\"@anthropic-ai/sdk\");\n Anthropic = (mod as { default: unknown }).default;\n } catch {\n throw new VisualAIConfigError(\n \"Anthropic SDK not installed. Run: npm install @anthropic-ai/sdk\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"Anthropic API key not found. Set ANTHROPIC_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (Anthropic as new (opts: { apiKey: string }) => AnthropicClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n const imageBlocks = images.map((img) => ({\n type: \"image\" as const,\n source: {\n type: \"base64\" as const,\n media_type: img.mimeType,\n data: img.base64,\n },\n }));\n\n try {\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_tokens: this.maxTokens,\n messages: [\n {\n role: \"user\",\n content: [...imageBlocks, { type: \"text\" as const, text: prompt }],\n },\n ],\n };\n\n if (this.reasoningEffort) {\n requestParams.thinking = { type: \"adaptive\" };\n requestParams.output_config = {\n effort: mapEffort(this.reasoningEffort, this.model),\n };\n }\n\n const message = await client.messages.create(requestParams);\n\n const textBlock = message.content.find((block) => block.type === \"text\");\n const text = textBlock?.text ?? \"\";\n\n if (message.stop_reason === \"max_tokens\") {\n throw new VisualAITruncationError(\n `Response truncated: Anthropic stopped due to max_tokens limit (${this.maxTokens} tokens). Increase maxTokens in your config or lower reasoningEffort.`,\n text,\n this.maxTokens,\n );\n }\n\n return {\n text,\n usage: {\n inputTokens: message.usage.input_tokens,\n outputTokens: message.usage.output_tokens,\n },\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import { buildAiDiffCodeExecutionPrompt } from \"../core/prompt.js\";\nimport {\n VisualAIAuthError,\n VisualAIConfigError,\n VisualAIProviderError,\n VisualAITruncationError,\n} from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type { ReasoningEffortLevel } from \"../constants.js\";\nimport type {\n ImageGenerationOptions,\n ImageGenerationResponse,\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst DEFAULT_IMAGE_GEN_MODEL = \"gemini-2.5-flash-image\";\n\n/** Gemini 3+ models use code execution for image generation instead of responseModalities. */\nexport function needsCodeExecution(model: string): boolean {\n const match = model.match(/^gemini-(\\d+)/);\n return match !== null && match[1] !== undefined && parseInt(match[1], 10) >= 3;\n}\n\n/** Response parts from Gemini API. Code execution responses include executableCode/codeExecutionResult\n * parts alongside inlineData; these fields are kept for type accuracy of the full response shape. */\ninterface GeminiImagePart {\n text?: string;\n inlineData?: {\n data: string;\n mimeType: string;\n };\n executableCode?: {\n code: string;\n language?: string;\n };\n codeExecutionResult?: {\n outcome?: string;\n output?: string;\n };\n}\n\n/** Minimal interface for the Google GenAI SDK client used by this driver. */\ninterface GoogleGenerateContentResponse {\n text?: string;\n candidates?: Array<{\n content?: {\n parts?: GeminiImagePart[];\n };\n finishReason?: string;\n }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n thoughtsTokenCount?: number;\n };\n}\n\ninterface GoogleClient {\n models: {\n generateContent(params: Record<string, unknown>): Promise<GoogleGenerateContentResponse>;\n };\n}\n\nconst GOOGLE_THINKING_LEVEL = {\n low: \"minimal\",\n medium: \"low\",\n high: \"medium\",\n xhigh: \"high\",\n} as const satisfies Record<ReasoningEffortLevel, string>;\n\nexport class GoogleDriver implements ProviderDriver {\n private client: GoogleClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private toGeminiParts(images: NormalizedImage[]) {\n return images.map((img) => ({\n inlineData: { data: img.base64, mimeType: img.mimeType },\n }));\n }\n\n private async getClient(): Promise<GoogleClient> {\n if (this.client) return this.client;\n\n let GoogleGenAI: unknown;\n try {\n const mod: unknown = await import(\"@google/genai\");\n GoogleGenAI = (mod as { GoogleGenAI: unknown }).GoogleGenAI;\n } catch {\n throw new VisualAIConfigError(\n \"Google GenAI SDK not installed. Run: npm install @google/genai\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.GOOGLE_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"Google API key not found. Set GOOGLE_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (GoogleGenAI as new (opts: { apiKey: string }) => GoogleClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n try {\n const response = await client.models.generateContent({\n model: this.model,\n contents: [...this.toGeminiParts(images), prompt],\n config: {\n responseMimeType: \"application/json\",\n maxOutputTokens: this.maxTokens,\n ...(this.reasoningEffort && {\n thinkingConfig: {\n thinkingLevel: GOOGLE_THINKING_LEVEL[this.reasoningEffort],\n },\n }),\n },\n });\n\n const finishReason = response.candidates?.[0]?.finishReason;\n if (finishReason === \"MAX_TOKENS\") {\n throw new VisualAITruncationError(\n `Response truncated: Google returned finishReason \"MAX_TOKENS\". The model exhausted the output token budget (${this.maxTokens} tokens). Increase maxTokens in your config or lower reasoningEffort.`,\n response.text ?? \"\",\n this.maxTokens,\n );\n }\n if (finishReason && finishReason !== \"STOP\") {\n throw new VisualAIProviderError(\n `Response blocked: Google returned finishReason \"${finishReason}\".`,\n );\n }\n\n const text = response.text ?? \"\";\n const thoughtsTokenCount = response.usageMetadata?.thoughtsTokenCount;\n\n return {\n text,\n usage: response.usageMetadata\n ? {\n inputTokens: response.usageMetadata.promptTokenCount ?? 0,\n outputTokens: response.usageMetadata.candidatesTokenCount ?? 0,\n ...(thoughtsTokenCount !== undefined && { reasoningTokens: thoughtsTokenCount }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n async generateImage(\n images: NormalizedImage[],\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const client = await this.getClient();\n const imageModel = options?.model ?? DEFAULT_IMAGE_GEN_MODEL;\n const resolvedPrompt =\n options?.promptKind === \"ai-diff\" && needsCodeExecution(imageModel)\n ? buildAiDiffCodeExecutionPrompt()\n : prompt;\n\n // Gemini 3+ models require code execution to generate images;\n // older models use native image generation via responseModalities.\n const config = needsCodeExecution(imageModel)\n ? { tools: [{ codeExecution: {} }] }\n : { responseModalities: [\"TEXT\", \"IMAGE\"] };\n\n try {\n const response = await client.models.generateContent({\n model: imageModel,\n contents: [...this.toGeminiParts(images), resolvedPrompt],\n config,\n });\n\n const parts = response.candidates?.[0]?.content?.parts;\n if (!parts) {\n throw new VisualAIProviderError(\"Gemini image generation returned no response parts\");\n }\n\n const imagePart = parts.find((p) => p.inlineData?.data);\n if (!imagePart?.inlineData) {\n throw new VisualAIProviderError(\n \"Gemini image generation returned no image data. Ensure the model supports image output.\",\n );\n }\n\n return {\n imageData: Buffer.from(imagePart.inlineData.data, \"base64\"),\n mimeType: imagePart.inlineData.mimeType,\n usage: response.usageMetadata\n ? {\n inputTokens: response.usageMetadata.promptTokenCount ?? 0,\n outputTokens: response.usageMetadata.candidatesTokenCount ?? 0,\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import { VisualAIAuthError, VisualAIConfigError, VisualAITruncationError } from \"../errors.js\";\nimport { mapProviderError } from \"./error-mapper.js\";\nimport type { NormalizedImage } from \"../types.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\n/** Minimal interface for the OpenAI SDK client used by this driver. */\ninterface OpenAIResponseResult {\n output_text?: string;\n usage?: {\n input_tokens: number;\n output_tokens: number;\n output_tokens_details?: { reasoning_tokens?: number };\n };\n status?: string;\n incomplete_details?: { reason?: string };\n}\n\ninterface OpenAIClient {\n responses: {\n create(params: Record<string, unknown>): Promise<OpenAIResponseResult>;\n };\n}\n\nexport class OpenAIDriver implements ProviderDriver {\n private client: OpenAIClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.maxTokens = config.maxTokens;\n this.client = null;\n this.apiKeyOrEnv = config.apiKey;\n this.reasoningEffort = config.reasoningEffort;\n }\n\n private async getClient(): Promise<OpenAIClient> {\n if (this.client) return this.client;\n\n let OpenAI: unknown;\n try {\n const mod: unknown = await import(\"openai\");\n OpenAI = (mod as { default: unknown }).default;\n } catch {\n throw new VisualAIConfigError(\"OpenAI SDK not installed. Run: npm install openai\");\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"OpenAI API key not found. Set OPENAI_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (OpenAI as new (opts: { apiKey: string }) => OpenAIClient)({ apiKey });\n return this.client;\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n\n const imageBlocks = images.map((img) => ({\n type: \"input_image\" as const,\n image_url: `data:${img.mimeType};base64,${img.base64}`,\n }));\n\n try {\n const format = options?.responseSchema\n ? {\n type: \"json_schema\" as const,\n name: \"visual_ai_response\",\n strict: true,\n schema: options.responseSchema,\n }\n : { type: \"json_object\" as const };\n\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_output_tokens: this.maxTokens,\n text: { format },\n input: [\n {\n role: \"user\",\n content: [...imageBlocks, { type: \"input_text\" as const, text: prompt }],\n },\n ],\n };\n\n if (this.reasoningEffort) {\n requestParams.reasoning = { effort: this.reasoningEffort };\n }\n\n const response = await client.responses.create(requestParams);\n\n if (response.status && response.status !== \"completed\") {\n const detail = response.incomplete_details?.reason\n ? ` (${response.incomplete_details.reason})`\n : \"\";\n throw new VisualAITruncationError(\n `Response truncated: OpenAI returned status \"${response.status}\"${detail}. The model exhausted the output token budget (${this.maxTokens} tokens). This commonly happens with higher reasoning effort levels. Increase maxTokens in your config (e.g., maxTokens: 16384) or lower reasoningEffort.`,\n response.output_text ?? \"\",\n this.maxTokens,\n );\n }\n\n const text = response.output_text ?? \"\";\n const reasoningTokens = response.usage?.output_tokens_details?.reasoning_tokens;\n\n return {\n text,\n usage: response.usage\n ? {\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n ...(reasoningTokens !== undefined && { reasoningTokens }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import {\n DEFAULT_MAX_TOKENS,\n DEFAULT_MODELS,\n MODEL_TO_PROVIDER,\n OPENAI_REASONING_MAX_TOKENS,\n} from \"../constants.js\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport type { ProviderName, VisualAIConfig } from \"../types.js\";\n\nexport interface ResolvedConfig {\n provider: ProviderName;\n apiKey: string | undefined;\n model: string;\n maxTokens: number;\n reasoningEffort: VisualAIConfig[\"reasoningEffort\"];\n debug: boolean;\n debugPrompt: boolean;\n debugResponse: boolean;\n trackUsage: boolean;\n}\n\nconst MODEL_PREFIX_TO_PROVIDER: [string, ProviderName][] = [\n [\"claude-\", \"anthropic\"],\n [\"gpt-\", \"openai\"],\n [\"o1-\", \"openai\"],\n [\"o3-\", \"openai\"],\n [\"o4-\", \"openai\"],\n [\"gemini-\", \"google\"],\n];\n\nfunction inferProviderFromModel(model: string): ProviderName | undefined {\n const known = MODEL_TO_PROVIDER.get(model);\n if (known) return known;\n\n const prefixMatch = MODEL_PREFIX_TO_PROVIDER.find(([prefix]) => model.startsWith(prefix));\n return prefixMatch?.[1];\n}\n\nfunction resolveProvider(config: VisualAIConfig): ProviderName {\n const model = config.model ?? process.env.VISUAL_AI_MODEL;\n if (model) {\n const inferred = inferProviderFromModel(model);\n if (inferred) return inferred;\n }\n\n const apiKeyProviderMap: [string, ProviderName][] = [\n [\"ANTHROPIC_API_KEY\", \"anthropic\"],\n [\"OPENAI_API_KEY\", \"openai\"],\n [\"GOOGLE_API_KEY\", \"google\"],\n ];\n const detected = apiKeyProviderMap.find(([key]) => process.env[key]);\n if (detected) return detected[1];\n\n throw new VisualAIConfigError(\n \"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).\",\n );\n}\n\nfunction parseBooleanEnv(envName: string, value: string | undefined): boolean | undefined {\n if (value === undefined || value === \"\") return undefined;\n const lower = value.toLowerCase();\n if (lower === \"true\" || lower === \"1\") return true;\n if (lower === \"false\" || lower === \"0\") return false;\n throw new VisualAIConfigError(\n `Invalid ${envName} value: \"${value}\". Use \"true\", \"1\", \"false\", or \"0\".`,\n );\n}\n\nlet debugDeprecationWarned = false;\n\n/** @internal Reset the deprecation warning guard. For testing only. */\nexport function resetDebugDeprecationWarning(): void {\n debugDeprecationWarned = false;\n}\n\nexport function resolveConfig(config: VisualAIConfig): ResolvedConfig {\n const provider = resolveProvider(config);\n const model = config.model ?? process.env.VISUAL_AI_MODEL ?? DEFAULT_MODELS[provider];\n const debug =\n config.debug ?? parseBooleanEnv(\"VISUAL_AI_DEBUG\", process.env.VISUAL_AI_DEBUG) ?? false;\n const debugPrompt =\n config.debugPrompt ??\n parseBooleanEnv(\"VISUAL_AI_DEBUG_PROMPT\", process.env.VISUAL_AI_DEBUG_PROMPT) ??\n false;\n const debugResponse =\n config.debugResponse ??\n parseBooleanEnv(\"VISUAL_AI_DEBUG_RESPONSE\", process.env.VISUAL_AI_DEBUG_RESPONSE) ??\n false;\n\n if (debug && !debugPrompt && !debugResponse && !debugDeprecationWarned) {\n debugDeprecationWarned = true;\n process.stderr.write(\n `[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.\\n`,\n );\n }\n\n const userSetMaxTokens = config.maxTokens !== undefined;\n let maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // OpenAI reasoning tokens share the output budget, so auto-increase for high/xhigh\n if (\n !userSetMaxTokens &&\n provider === \"openai\" &&\n (config.reasoningEffort === \"high\" || config.reasoningEffort === \"xhigh\")\n ) {\n maxTokens = OPENAI_REASONING_MAX_TOKENS;\n if (debug) {\n process.stderr.write(\n `[visual-ai-assertions] Auto-increased maxTokens from ${DEFAULT_MAX_TOKENS} to ${OPENAI_REASONING_MAX_TOKENS} for OpenAI with reasoningEffort \"${config.reasoningEffort}\".\\n`,\n );\n }\n }\n\n return {\n provider,\n apiKey: config.apiKey,\n model,\n maxTokens,\n reasoningEffort: config.reasoningEffort,\n debug,\n debugPrompt,\n debugResponse,\n trackUsage:\n config.trackUsage ??\n parseBooleanEnv(\"VISUAL_AI_TRACK_USAGE\", process.env.VISUAL_AI_TRACK_USAGE) ??\n false,\n };\n}\n","import { Model, Provider } from \"../constants.js\";\nimport type { ProviderName } from \"../types.js\";\n\ninterface ModelPricing {\n inputPricePerToken: number;\n outputPricePerToken: number;\n}\n\nconst PER_MILLION = 1_000_000;\n\nconst PRICING_TABLE: Record<string, ModelPricing> = {\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_4_7}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_4_6}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_4_6}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.HAIKU_4_5}`]: {\n inputPricePerToken: 1 / PER_MILLION,\n outputPricePerToken: 5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_5}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 30 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4}`]: {\n inputPricePerToken: 2.5 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_PRO}`]: {\n inputPricePerToken: 30 / PER_MILLION,\n outputPricePerToken: 180 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_2}`]: {\n inputPricePerToken: 1.75 / PER_MILLION,\n outputPricePerToken: 14 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_MINI}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 4.5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_4_NANO}`]: {\n inputPricePerToken: 0.2 / PER_MILLION,\n outputPricePerToken: 1.25 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_MINI}`]: {\n inputPricePerToken: 0.25 / PER_MILLION,\n outputPricePerToken: 2 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_1_PRO_PREVIEW}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 12 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_1_FLASH_LITE_PREVIEW}`]: {\n inputPricePerToken: 0.25 / PER_MILLION,\n outputPricePerToken: 1.5 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_FLASH_PREVIEW}`]: {\n inputPricePerToken: 0.5 / PER_MILLION,\n outputPricePerToken: 3 / PER_MILLION,\n },\n};\n\nexport function calculateCost(\n provider: ProviderName,\n model: string,\n inputTokens: number,\n outputTokens: number,\n): number | undefined {\n const key = `${provider}:${model}`;\n const pricing = PRICING_TABLE[key];\n if (!pricing) return undefined;\n\n return inputTokens * pricing.inputPricePerToken + outputTokens * pricing.outputPricePerToken;\n}\n","import { PROVIDER_DEFAULT_REASONING } from \"../constants.js\";\nimport { calculateCost } from \"./pricing.js\";\nimport type { ResolvedConfig } from \"./config.js\";\nimport type {\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport type { NormalizedImage, UsageInfo } from \"../types.js\";\nimport { VisualAIError, VisualAIResponseParseError, VisualAITruncationError } from \"../errors.js\";\n\nexport type DebugLogKind = \"prompt\" | \"response\" | \"error\";\n\nexport function debugLog(\n config: ResolvedConfig,\n label: string,\n data: string,\n kind: DebugLogKind = \"error\",\n): void {\n const enabled =\n kind === \"prompt\"\n ? config.debugPrompt\n : kind === \"response\"\n ? config.debugResponse\n : config.debug;\n\n if (enabled) {\n process.stderr.write(`[visual-ai-assertions] ${label}: ${data}\\n`);\n }\n}\n\nexport function usageLog(config: ResolvedConfig, method: string, usage: UsageInfo): void {\n if (!config.trackUsage) return;\n const costStr =\n usage.estimatedCost !== undefined ? `$${usage.estimatedCost.toFixed(6)}` : \"unknown\";\n const reasoningStr = config.reasoningEffort\n ? `reasoning: ${config.reasoningEffort}`\n : `reasoning: ${PROVIDER_DEFAULT_REASONING[config.provider]} (provider default)`;\n const reasoningTokenStr =\n usage.reasoningTokens !== undefined ? ` (${usage.reasoningTokens} reasoning)` : \"\";\n process.stderr.write(\n `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output${reasoningTokenStr} tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? \"0.000\"}s [${config.model}, ${reasoningStr}]\\n`,\n );\n}\n\nexport function processUsage(\n method: string,\n rawUsage: RawProviderResponse[\"usage\"],\n durationSeconds: number,\n config: ResolvedConfig,\n): UsageInfo {\n const inputTokens = rawUsage?.inputTokens ?? 0;\n const outputTokens = rawUsage?.outputTokens ?? 0;\n const usage: UsageInfo = {\n inputTokens,\n outputTokens,\n ...(rawUsage?.reasoningTokens !== undefined && { reasoningTokens: rawUsage.reasoningTokens }),\n estimatedCost: calculateCost(config.provider, config.model, inputTokens, outputTokens),\n durationSeconds,\n };\n usageLog(config, method, usage);\n return usage;\n}\n\nconst MAX_RAW_RESPONSE_PREVIEW = 500;\n\nexport function formatError(error: unknown): string {\n if (error instanceof VisualAITruncationError) {\n const preview =\n error.partialResponse.length > MAX_RAW_RESPONSE_PREVIEW\n ? error.partialResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + \"...\"\n : error.partialResponse;\n return `${error.name} (${error.code}): ${error.message}. Partial response: ${preview}`;\n }\n if (error instanceof VisualAIResponseParseError) {\n const truncated =\n error.rawResponse.length > MAX_RAW_RESPONSE_PREVIEW\n ? error.rawResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + \"...\"\n : error.rawResponse;\n return `${error.name} (${error.code}): ${error.message}. Raw (truncated): ${truncated}`;\n }\n if (error instanceof VisualAIError) {\n return `${error.name} (${error.code}): ${error.message}`;\n }\n if (error instanceof Error) {\n return `${error.name}: ${error.message}`;\n }\n return String(error);\n}\n\nexport async function withErrorDebug<T>(\n config: ResolvedConfig,\n method: string,\n fn: () => Promise<T>,\n): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n debugLog(config, `${method} error`, formatError(error), \"error\");\n throw error;\n }\n}\n\nexport async function timedSendMessage(\n driver: ProviderDriver,\n images: NormalizedImage[],\n prompt: string,\n options?: SendMessageOptions,\n): Promise<RawProviderResponse & { durationSeconds: number }> {\n const start = performance.now();\n const response = await driver.sendMessage(images, prompt, options);\n const durationSeconds = (performance.now() - start) / 1000;\n return { ...response, durationSeconds };\n}\n","import { Model } from \"../constants.js\";\nimport sharp from \"sharp\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport type { DiffImageResult, NormalizedImage } from \"../types.js\";\nimport { buildAiDiffPrompt } from \"./prompt.js\";\n\ninterface ImageGenerationDriver {\n generateImage?: (\n images: NormalizedImage[],\n prompt: string,\n options?: { model?: string; promptKind?: \"ai-diff\" },\n ) => Promise<{\n imageData: Buffer;\n mimeType: string;\n }>;\n}\n\nexport async function generateAiDiff(\n imgA: NormalizedImage,\n imgB: NormalizedImage,\n model: string,\n driver: ImageGenerationDriver,\n): Promise<DiffImageResult> {\n if (!driver.generateImage) {\n throw new VisualAIConfigError(\n \"AI-generated diff images require a provider that supports image generation. Currently only the Google (Gemini) provider supports this.\",\n );\n }\n\n if (model !== Model.Google.GEMINI_3_FLASH_PREVIEW) {\n throw new VisualAIConfigError(\n \"Annotated diff images are only supported when visualAI is configured with the Google model gemini-3-flash-preview.\",\n );\n }\n\n const response = await driver.generateImage([imgA, imgB], buildAiDiffPrompt(), {\n model,\n promptKind: \"ai-diff\",\n });\n\n const img = sharp(response.imageData);\n const meta = await img.metadata();\n const pngData = await img.png().toBuffer();\n\n return {\n data: pngData,\n width: meta.width ?? 0,\n height: meta.height ?? 0,\n mimeType: \"image/png\",\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\nimport sharp from \"sharp\";\nimport { VisualAIImageError } from \"../errors.js\";\nimport type { NormalizedImage, SupportedMimeType } from \"../types.js\";\nimport {\n decodeBase64,\n fetchToBuffer,\n isDataUrl,\n isFilePath,\n isUrl,\n looksLikeImageBase64,\n parseDataUrl,\n} from \"./input-detect.js\";\n\nconst SUPPORTED_FORMATS: ReadonlySet<SupportedMimeType> = new Set([\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"image/gif\",\n]);\n\nconst EXTENSION_TO_MIME: Record<string, SupportedMimeType> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".webp\": \"image/webp\",\n \".gif\": \"image/gif\",\n};\n\nconst MAX_DIMENSION = 1568;\nconst URL_FETCH_TIMEOUT_MS = 10_000;\n\nfunction isSupportedMimeType(value: string): value is SupportedMimeType {\n return SUPPORTED_FORMATS.has(value as SupportedMimeType);\n}\n\nfunction getMimeFromExtension(filePath: string): SupportedMimeType | undefined {\n const ext = extname(filePath).toLowerCase();\n return EXTENSION_TO_MIME[ext];\n}\n\nfunction detectMimeType(data: Buffer): SupportedMimeType {\n // Check magic bytes\n if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {\n return \"image/jpeg\";\n }\n if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) {\n return \"image/png\";\n }\n if (\n data[0] === 0x52 &&\n data[1] === 0x49 &&\n data[2] === 0x46 &&\n data[3] === 0x46 &&\n data[8] === 0x57 &&\n data[9] === 0x45 &&\n data[10] === 0x42 &&\n data[11] === 0x50\n ) {\n return \"image/webp\";\n }\n if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) {\n return \"image/gif\";\n }\n\n throw new VisualAIImageError(\"Unable to detect image format from file content\");\n}\n\nasync function resizeIfNeeded(data: Buffer, mimeType: SupportedMimeType): Promise<Buffer> {\n if (mimeType === \"image/gif\") {\n return data;\n }\n\n // Fast path for PNG: read dimensions directly from header bytes\n if (mimeType === \"image/png\" && data.length >= 24) {\n const width = data.readUInt32BE(16);\n const height = data.readUInt32BE(20);\n if (width <= MAX_DIMENSION && height <= MAX_DIMENSION) {\n return data;\n }\n }\n\n // Fall back to sharp for other formats or when resizing is needed\n const pipeline = sharp(data);\n const metadata = await pipeline.metadata();\n const width = metadata.width ?? 0;\n const height = metadata.height ?? 0;\n\n if (width <= MAX_DIMENSION && height <= MAX_DIMENSION) {\n return data;\n }\n\n return pipeline\n .resize({\n width: MAX_DIMENSION,\n height: MAX_DIMENSION,\n fit: \"inside\",\n withoutEnlargement: true,\n })\n .toBuffer();\n}\n\nasync function loadFromFilePath(\n filePath: string,\n): Promise<{ data: Buffer; mimeType: SupportedMimeType }> {\n let fileData: Buffer;\n try {\n fileData = await readFile(filePath);\n } catch (err) {\n throw new VisualAIImageError(\n `Failed to read image file: ${filePath} — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const mimeType = getMimeFromExtension(filePath) ?? detectMimeType(fileData);\n return { data: fileData, mimeType };\n}\n\nasync function loadFromUrl(url: string): Promise<{ data: Buffer; mimeType: SupportedMimeType }> {\n let result: { data: Buffer; contentType: string | null };\n try {\n result = await fetchToBuffer(url, URL_FETCH_TIMEOUT_MS);\n } catch (err) {\n throw new VisualAIImageError(\n `Failed to fetch image from URL: ${url} — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const { data, contentType } = result;\n const mimeType =\n contentType && isSupportedMimeType(contentType) ? contentType : detectMimeType(data);\n\n return { data, mimeType };\n}\n\nfunction loadFromBase64(input: string): { data: Buffer; mimeType: SupportedMimeType } {\n // Handle data URLs: data:image/png;base64,iVBOR...\n let base64Data = input;\n let mimeType: SupportedMimeType | undefined;\n\n if (isDataUrl(input)) {\n const parsed = parseDataUrl(input);\n if (!parsed) {\n throw new VisualAIImageError(\"Invalid data URL format\");\n }\n if (!isSupportedMimeType(parsed.mimeType)) {\n throw new VisualAIImageError(`Unsupported image format: ${parsed.mimeType}`);\n }\n mimeType = parsed.mimeType;\n base64Data = parsed.base64Payload;\n }\n\n let data: Buffer;\n try {\n data = decodeBase64(base64Data);\n } catch {\n throw new VisualAIImageError(\"Invalid base64 string\");\n }\n\n if (data.length === 0) {\n throw new VisualAIImageError(\"Empty image data after base64 decode\");\n }\n\n return { data, mimeType: mimeType ?? detectMimeType(data) };\n}\n\nexport async function normalizeImage(\n input: Buffer | Uint8Array | string,\n): Promise<NormalizedImage> {\n let data: Buffer;\n let mimeType: SupportedMimeType;\n\n if (Buffer.isBuffer(input)) {\n mimeType = detectMimeType(input);\n data = input;\n } else if (input instanceof Uint8Array) {\n const buf = Buffer.from(input);\n mimeType = detectMimeType(buf);\n data = buf;\n } else if (typeof input === \"string\") {\n if (isUrl(input)) {\n ({ data, mimeType } = await loadFromUrl(input));\n } else if (isDataUrl(input)) {\n ({ data, mimeType } = loadFromBase64(input));\n } else if (looksLikeImageBase64(input)) {\n ({ data, mimeType } = loadFromBase64(input));\n } else if (isFilePath(input)) {\n ({ data, mimeType } = await loadFromFilePath(input));\n } else {\n throw new VisualAIImageError(\n `Unrecognized image input: \"${input.slice(0, 80)}\". ` +\n `Expected a file path, URL, data URL, or base64-encoded image string.`,\n );\n }\n } else {\n throw new VisualAIImageError(\n \"Invalid image input: expected Buffer, Uint8Array, file path, URL, or base64 string\",\n );\n }\n\n data = await resizeIfNeeded(data, mimeType);\n\n let cachedBase64: string | undefined;\n return {\n data,\n mimeType,\n get base64(): string {\n if (cachedBase64 === undefined) {\n cachedBase64 = data.toString(\"base64\");\n }\n return cachedBase64;\n },\n };\n}\n","/**\n * Shared helpers for classifying string-shaped media inputs (paths, URLs,\n * data URLs, base64) and for the side-effecting loaders both `image.ts` and\n * `video.ts` rely on.\n */\n\nexport function isFilePath(input: string): boolean {\n return (\n input.startsWith(\"/\") ||\n input.startsWith(\"./\") ||\n input.startsWith(\"../\") ||\n input.includes(\"\\\\\")\n );\n}\n\nexport function isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\nexport function isDataUrl(input: string): boolean {\n return input.startsWith(\"data:\");\n}\n\n/**\n * Parses a base64 data URL of the form `data:<mime>;base64,<payload>`.\n * Returns `null` for any other shape so callers can dispatch.\n */\nexport function parseDataUrl(input: string): { mimeType: string; base64Payload: string } | null {\n const match = /^data:([^;]+);base64,(.+)$/.exec(input);\n if (!match?.[1] || !match[2]) return null;\n return { mimeType: match[1], base64Payload: match[2] };\n}\n\n/**\n * Validates and decodes a raw base64 payload. Throws on malformed input.\n * Callers are responsible for sniffing MIME from the resulting bytes.\n */\nexport function decodeBase64(payload: string): Buffer {\n if (!/^[A-Za-z0-9+/\\n\\r]+=*$/.test(payload)) {\n throw new Error(\"Invalid base64 string\");\n }\n return Buffer.from(payload, \"base64\");\n}\n\n/** Quick prefix check for the four image formats the library accepts. */\nexport function looksLikeImageBase64(input: string): boolean {\n return (\n input.startsWith(\"iVBOR\") || // PNG (0x89 0x50 0x4E 0x47)\n input.startsWith(\"/9j/\") || // JPEG (0xFF 0xD8 0xFF)\n input.startsWith(\"R0lGOD\") || // GIF (0x47 0x49 0x46)\n input.startsWith(\"UklGR\") // WebP (0x52 0x49 0x46 0x46)\n );\n}\n\n/**\n * Quick prefix check for video base64 payloads. Catches WebM/Matroska\n * (EBML header `1A 45 DF A3` → `GkXf`) and ISO BMFF MP4/MOV variants whose\n * first three bytes are `00 00 00` (size prefix) → `AAAA`.\n *\n * `AAAA` is permissive (any binary starting with three zero bytes matches),\n * so callers must follow up with a magic-byte sniff before treating the\n * payload as video.\n */\nexport function looksLikeVideoBase64(input: string): boolean {\n return input.startsWith(\"GkXf\") || input.startsWith(\"AAAA\");\n}\n\nexport async function fetchToBuffer(\n url: string,\n timeoutMs: number,\n): Promise<{ data: Buffer; contentType: string | null }> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n const data = Buffer.from(arrayBuffer);\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0]?.trim() ?? null;\n return { data, contentType };\n}\n","import { mkdtemp, readFile, readdir, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { extname, join } from \"node:path\";\nimport { VisualAIVideoError } from \"../errors.js\";\nimport type { Frame, SupportedVideoMimeType, VideoSamplingOptions } from \"../types.js\";\nimport { decodeBase64, isDataUrl, isFilePath, parseDataUrl } from \"./input-detect.js\";\n\nconst FRAME_MAX_DIMENSION = 1568;\nconst DEFAULT_FPS = 1;\nconst DEFAULT_MAX_FRAMES = 10;\nconst DEFAULT_MAX_DURATION_SECONDS = 10;\n/** Hard upper bound on caller-overridable maxFrames to keep memory bounded. */\nconst MAX_FRAMES_HARD_CAP = 60;\nconst FFPROBE_TIMEOUT_MS = 15_000;\nconst FFMPEG_RUN_TIMEOUT_MS = 60_000;\n\nconst VIDEO_EXTENSIONS: Record<string, SupportedVideoMimeType> = {\n \".mp4\": \"video/mp4\",\n \".m4v\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mov\": \"video/quicktime\",\n \".qt\": \"video/quicktime\",\n \".mkv\": \"video/x-matroska\",\n};\n\nconst VIDEO_MIME_TYPES: ReadonlySet<SupportedVideoMimeType> = new Set([\n \"video/mp4\",\n \"video/webm\",\n \"video/quicktime\",\n \"video/x-matroska\",\n]);\n\nexport function isSupportedVideoMimeType(value: string): value is SupportedVideoMimeType {\n return VIDEO_MIME_TYPES.has(value as SupportedVideoMimeType);\n}\n\nexport function getVideoMimeFromExtension(filePath: string): SupportedVideoMimeType | undefined {\n const ext = extname(filePath).toLowerCase();\n return VIDEO_EXTENSIONS[ext];\n}\n\n/**\n * Detects supported video MIME types from the leading bytes of a buffer.\n * Returns `null` if no signature matches.\n */\nexport function detectVideoMimeType(data: Buffer): SupportedVideoMimeType | null {\n if (data.length < 12) return null;\n\n // ISO BMFF: bytes 4..7 == \"ftyp\"\n if (data[4] === 0x66 && data[5] === 0x74 && data[6] === 0x79 && data[7] === 0x70) {\n // Brand \"qt \" distinguishes QuickTime from MP4 family.\n if (data[8] === 0x71 && data[9] === 0x74 && data[10] === 0x20 && data[11] === 0x20) {\n return \"video/quicktime\";\n }\n return \"video/mp4\";\n }\n\n // EBML header (Matroska / WebM): 0x1A 0x45 0xDF 0xA3\n if (data[0] === 0x1a && data[1] === 0x45 && data[2] === 0xdf && data[3] === 0xa3) {\n return \"video/webm\";\n }\n\n return null;\n}\n\n/**\n * Resolves a video `MediaInput` to an on-disk path that ffmpeg can read.\n * Accepts file paths, data URLs, base64 strings, Buffer, and Uint8Array.\n * URLs are not supported for video inputs — fetch the bytes yourself first.\n */\nexport async function resolveVideoToPath(\n input: Buffer | Uint8Array | string,\n): Promise<{ path: string; mimeType: SupportedVideoMimeType; cleanup: () => Promise<void> }> {\n if (Buffer.isBuffer(input) || input instanceof Uint8Array) {\n const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);\n const mimeType = detectVideoMimeType(buf);\n if (!mimeType) {\n throw new VisualAIVideoError(\"Unable to detect video format from buffer contents\");\n }\n return writeBufferToTemp(buf, mimeType);\n }\n\n if (typeof input !== \"string\") {\n throw new VisualAIVideoError(\n \"Invalid video input: expected Buffer, Uint8Array, file path, data URL, or base64 string\",\n );\n }\n\n if (isDataUrl(input)) {\n const parsed = parseDataUrl(input);\n if (!parsed) {\n throw new VisualAIVideoError(\"Invalid data URL format\");\n }\n if (!isSupportedVideoMimeType(parsed.mimeType)) {\n throw new VisualAIVideoError(`Unsupported video format: ${parsed.mimeType}`);\n }\n let buf: Buffer;\n try {\n buf = decodeBase64(parsed.base64Payload);\n } catch {\n throw new VisualAIVideoError(\"Invalid base64 payload in data URL\");\n }\n return writeBufferToTemp(buf, parsed.mimeType);\n }\n\n if (isFilePath(input)) {\n const mimeType = getVideoMimeFromExtension(input);\n if (!mimeType) {\n throw new VisualAIVideoError(\n `Unsupported video file extension: ${input}. Supported: .mp4, .webm, .mov, .mkv`,\n );\n }\n return { path: input, mimeType, cleanup: async () => {} };\n }\n\n // Treat as raw base64 of a video.\n let buf: Buffer;\n try {\n buf = decodeBase64(input);\n } catch {\n throw new VisualAIVideoError(\n `Unrecognized video input: \"${input.slice(0, 80)}\". ` +\n `Expected a file path, data URL, or base64-encoded video string.`,\n );\n }\n const mimeType = detectVideoMimeType(buf);\n if (!mimeType) {\n throw new VisualAIVideoError(\n `Unrecognized video input: \"${input.slice(0, 80)}\". ` +\n `Expected a file path, data URL, or base64-encoded video string.`,\n );\n }\n return writeBufferToTemp(buf, mimeType);\n}\n\nasync function writeBufferToTemp(\n data: Buffer,\n mimeType: SupportedVideoMimeType,\n): Promise<{ path: string; mimeType: SupportedVideoMimeType; cleanup: () => Promise<void> }> {\n const dir = await mkdtemp(join(tmpdir(), \"visual-ai-video-\"));\n try {\n const ext = extensionFor(mimeType);\n const path = join(dir, `input${ext}`);\n await writeFile(path, data);\n return {\n path,\n mimeType,\n cleanup: async () => {\n try {\n await rm(dir, { recursive: true, force: true });\n } catch {\n // Best-effort cleanup; never mask the caller's flow.\n }\n },\n };\n } catch (err) {\n try {\n await rm(dir, { recursive: true, force: true });\n } catch {\n // Best-effort cleanup of the partially-staged dir.\n }\n throw err;\n }\n}\n\nfunction extensionFor(mimeType: SupportedVideoMimeType): string {\n switch (mimeType) {\n case \"video/mp4\":\n return \".mp4\";\n case \"video/webm\":\n return \".webm\";\n case \"video/quicktime\":\n return \".mov\";\n case \"video/x-matroska\":\n return \".mkv\";\n }\n}\n\n// --- ffmpeg integration ---\n\ninterface FfprobeData {\n format?: { duration?: number | string };\n}\n\ninterface FfmpegCommand {\n outputOptions(options: string[]): FfmpegCommand;\n output(target: string): FfmpegCommand;\n on(event: \"end\", listener: () => void): FfmpegCommand;\n on(event: \"error\", listener: (err: Error) => void): FfmpegCommand;\n run(): void;\n kill(signal?: string): void;\n}\n\ninterface FfmpegFactory {\n (input: string): FfmpegCommand;\n setFfmpegPath(path: string): void;\n setFfprobePath(path: string): void;\n ffprobe(path: string, callback: (err: Error | null, data: FfprobeData) => void): void;\n}\n\nlet cachedFactoryPromise: Promise<FfmpegFactory> | undefined;\n\nasync function loadFfmpegFactory(): Promise<FfmpegFactory> {\n if (cachedFactoryPromise) return cachedFactoryPromise;\n\n cachedFactoryPromise = (async (): Promise<FfmpegFactory> => {\n let ffmpegModule: unknown;\n try {\n ffmpegModule = await import(\"fluent-ffmpeg\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") {\n throw new VisualAIVideoError(\n \"Video support requires fluent-ffmpeg. Install it with: \" +\n \"pnpm add -D fluent-ffmpeg @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe @types/fluent-ffmpeg\",\n );\n }\n throw new VisualAIVideoError(\n `Failed to load fluent-ffmpeg: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n const factory = ((ffmpegModule as { default?: unknown }).default ??\n ffmpegModule) as FfmpegFactory;\n\n try {\n const installer = (await import(\"@ffmpeg-installer/ffmpeg\")) as unknown;\n const path = (\n (installer as { default?: { path?: string } }).default ?? (installer as { path?: string })\n ).path;\n if (path) factory.setFfmpegPath(path);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ERR_MODULE_NOT_FOUND\" && code !== \"MODULE_NOT_FOUND\") {\n process.stderr.write(\n `[visual-ai-assertions] warning: @ffmpeg-installer/ffmpeg failed to load: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n\n try {\n const installer = (await import(\"@ffprobe-installer/ffprobe\")) as unknown;\n const path = (\n (installer as { default?: { path?: string } }).default ?? (installer as { path?: string })\n ).path;\n if (path) factory.setFfprobePath(path);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code !== \"ERR_MODULE_NOT_FOUND\" && code !== \"MODULE_NOT_FOUND\") {\n process.stderr.write(\n `[visual-ai-assertions] warning: @ffprobe-installer/ffprobe failed to load: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n\n return factory;\n })();\n\n try {\n return await cachedFactoryPromise;\n } catch (err) {\n cachedFactoryPromise = undefined;\n throw err;\n }\n}\n\n/**\n * Probes a video file's duration in seconds. Throws `VisualAIVideoError` if\n * ffprobe is unavailable, the output is unparseable, or the call exceeds the\n * 15-second wall-clock timeout.\n */\nexport async function probeDurationSeconds(videoPath: string): Promise<number> {\n const ffmpeg = await loadFfmpegFactory();\n return new Promise<number>((resolve, reject) => {\n let settled = false;\n const finish = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n const timer = setTimeout(() => {\n finish(() => {\n reject(\n new VisualAIVideoError(\n `ffprobe timed out after ${FFPROBE_TIMEOUT_MS}ms while probing ${videoPath}`,\n ),\n );\n });\n }, FFPROBE_TIMEOUT_MS);\n ffmpeg.ffprobe(videoPath, (err, data) => {\n if (err) {\n finish(() => {\n reject(\n new VisualAIVideoError(\n `Failed to probe video metadata: ${err.message}. ` +\n `Ensure ffprobe is installed (e.g. via @ffprobe-installer/ffprobe).`,\n ),\n );\n });\n return;\n }\n const raw = data.format?.duration;\n const duration = typeof raw === \"string\" ? Number(raw) : raw;\n if (!duration || !Number.isFinite(duration) || duration <= 0) {\n finish(() => {\n reject(new VisualAIVideoError(\"Video duration could not be determined\"));\n });\n return;\n }\n finish(() => {\n resolve(duration);\n });\n });\n });\n}\n\n/**\n * Samples frames from a video and returns them as `Frame` objects ready to\n * pass into a provider driver. Frames are extracted as JPEG, downscaled so\n * the longer edge fits within `FRAME_MAX_DIMENSION`, and time-stamped at\n * the centre of each sample window.\n */\nexport async function extractFrames(\n videoPath: string,\n options: VideoSamplingOptions = {},\n): Promise<{ frames: Frame[]; durationSeconds: number }> {\n const fps = options.fps ?? DEFAULT_FPS;\n const maxFrames = options.maxFrames ?? DEFAULT_MAX_FRAMES;\n const maxDurationSeconds = options.maxDurationSeconds ?? DEFAULT_MAX_DURATION_SECONDS;\n\n if (!Number.isFinite(fps) || fps <= 0) {\n throw new VisualAIVideoError(`Invalid fps: ${fps}. Must be a finite number > 0.`);\n }\n if (!Number.isFinite(maxFrames) || maxFrames <= 0) {\n throw new VisualAIVideoError(`Invalid maxFrames: ${maxFrames}. Must be a finite number > 0.`);\n }\n if (maxFrames > MAX_FRAMES_HARD_CAP) {\n throw new VisualAIVideoError(\n `maxFrames ${maxFrames} exceeds the hard cap of ${MAX_FRAMES_HARD_CAP}. ` +\n `Lower maxFrames or open an issue if you need a larger limit.`,\n );\n }\n if (!Number.isFinite(maxDurationSeconds) || maxDurationSeconds <= 0) {\n throw new VisualAIVideoError(\n `Invalid maxDurationSeconds: ${maxDurationSeconds}. Must be a finite number > 0.`,\n );\n }\n\n const ffmpeg = await loadFfmpegFactory();\n const durationSeconds = await probeDurationSeconds(videoPath);\n\n if (durationSeconds > maxDurationSeconds) {\n throw new VisualAIVideoError(\n `Video duration ${durationSeconds.toFixed(2)}s exceeds limit of ${maxDurationSeconds}s. ` +\n `Pass { maxDurationSeconds: N } to override, or trim the source video.`,\n );\n }\n\n const outputDir = await mkdtemp(join(tmpdir(), \"visual-ai-frames-\"));\n try {\n // Constrain the longer edge to FRAME_MAX_DIMENSION for both portrait and landscape inputs.\n const filter =\n `fps=${fps},` +\n `scale='if(gt(iw,ih),min(${FRAME_MAX_DIMENSION},iw),-2)':` +\n `'if(gt(iw,ih),-2,min(${FRAME_MAX_DIMENSION},ih))':flags=area`;\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const cmd = ffmpeg(videoPath);\n const finish = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n const timer = setTimeout(() => {\n try {\n cmd.kill(\"SIGKILL\");\n } catch {\n // Killing may fail if the child already exited; that's fine.\n }\n finish(() => {\n reject(\n new VisualAIVideoError(\n `ffmpeg frame extraction timed out after ${FFMPEG_RUN_TIMEOUT_MS}ms`,\n ),\n );\n });\n }, FFMPEG_RUN_TIMEOUT_MS);\n cmd\n .outputOptions([\"-vf\", filter, \"-vframes\", String(maxFrames), \"-q:v\", \"3\"])\n .output(join(outputDir, \"frame-%04d.jpg\"))\n .on(\"end\", () => {\n finish(() => {\n resolve();\n });\n })\n .on(\"error\", (err: Error) => {\n finish(() => {\n reject(new VisualAIVideoError(`ffmpeg frame extraction failed: ${err.message}`));\n });\n })\n .run();\n });\n\n const files = (await readdir(outputDir)).filter((name) => name.endsWith(\".jpg\")).sort();\n\n if (files.length === 0) {\n throw new VisualAIVideoError(\n \"No frames could be extracted from the video. The source may be corrupt or empty.\",\n );\n }\n\n const frames: Frame[] = await Promise.all(\n files.map(async (name, index): Promise<Frame> => {\n const data = await readFile(join(outputDir, name));\n const timestampSeconds = Math.min(durationSeconds, (index + 0.5) / fps);\n let cachedBase64: string | undefined;\n return {\n data,\n mimeType: \"image/jpeg\",\n get base64(): string {\n if (cachedBase64 === undefined) {\n cachedBase64 = data.toString(\"base64\");\n }\n return cachedBase64;\n },\n timestampSeconds,\n index,\n };\n }),\n );\n\n return { frames, durationSeconds };\n } finally {\n try {\n await rm(outputDir, { recursive: true, force: true });\n } catch {\n // Best-effort cleanup; never mask the original control flow.\n }\n }\n}\n","import type { Frame, MediaInput, NormalizedImage, VideoSamplingOptions } from \"../types.js\";\nimport { normalizeImage } from \"./image.js\";\nimport {\n decodeBase64,\n isDataUrl,\n isFilePath,\n looksLikeVideoBase64,\n parseDataUrl,\n} from \"./input-detect.js\";\nimport {\n detectVideoMimeType,\n extractFrames,\n getVideoMimeFromExtension,\n resolveVideoToPath,\n} from \"./video.js\";\n\nexport type NormalizedMedia =\n | { kind: \"image\"; image: NormalizedImage }\n | { kind: \"video\"; frames: Frame[]; durationSeconds: number };\n\n// Only the first 12 bytes (16 base64 chars) are needed to sniff a magic-byte signature.\nconst VIDEO_MAGIC_BYTE_PREFIX_LEN = 16;\n\n/**\n * Heuristically determines whether `input` is a video. Returns `true` only\n * when we have strong evidence: a recognized extension, a `data:video/*`\n * URL, or video magic bytes in the first 12 bytes of binary input. Anything\n * ambiguous returns `false` so the image pipeline gets a chance.\n *\n * URL inputs are not classified as video — the library does not fetch\n * remote videos.\n */\nexport function isVideoInput(input: MediaInput): boolean {\n if (Buffer.isBuffer(input) || input instanceof Uint8Array) {\n const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);\n return detectVideoMimeType(buf) !== null;\n }\n\n if (typeof input !== \"string\") return false;\n\n if (isDataUrl(input)) {\n const parsed = parseDataUrl(input);\n return parsed?.mimeType.startsWith(\"video/\") ?? false;\n }\n\n if (isFilePath(input)) {\n return getVideoMimeFromExtension(input) !== undefined;\n }\n\n // Bare base64 string — only treat as video when prefix + decoded magic\n // bytes both agree, to avoid stealing inputs from the image pipeline.\n if (looksLikeVideoBase64(input)) {\n try {\n const buf = decodeBase64(input.slice(0, VIDEO_MAGIC_BYTE_PREFIX_LEN));\n return detectVideoMimeType(buf) !== null;\n } catch {\n return false;\n }\n }\n\n return false;\n}\n\n/**\n * Single entry point used by the client. Auto-detects whether `input` is an\n * image or a video and returns a uniform `NormalizedMedia` envelope.\n */\nexport async function normalizeMedia(\n input: MediaInput,\n videoOptions?: VideoSamplingOptions,\n): Promise<NormalizedMedia> {\n if (isVideoInput(input)) {\n const { path, cleanup } = await resolveVideoToPath(input);\n try {\n const { frames, durationSeconds } = await extractFrames(path, videoOptions);\n return { kind: \"video\", frames, durationSeconds };\n } finally {\n try {\n await cleanup();\n } catch {\n // Best-effort cleanup; do not mask the original error path.\n }\n }\n }\n\n const image = await normalizeImage(input);\n return { kind: \"image\", image };\n}\n","import { z } from \"zod\";\nimport type {\n AccessibilityCheckName,\n ContentCheckName,\n LayoutCheckName,\n ReasoningEffortLevel,\n} from \"./constants.js\";\n\n// --- Issue types ---\n\n/** Zod schema for issue severity levels returned by checks and questions. */\nexport const IssuePrioritySchema = z.enum([\"critical\", \"major\", \"minor\"]);\n/** Severity level for a detected visual issue. */\nexport type IssuePriority = z.infer<typeof IssuePrioritySchema>;\n\n/** Zod schema for the categories assigned to detected visual issues. */\nexport const IssueCategorySchema = z.enum([\n \"accessibility\",\n \"missing-element\",\n \"layout\",\n \"content\",\n \"styling\",\n \"functionality\",\n \"performance\",\n \"other\",\n]);\n/** Category assigned to a detected visual issue. */\nexport type IssueCategory = z.infer<typeof IssueCategorySchema>;\n\n/** Zod schema for a structured issue reported by the model. */\nexport const IssueSchema = z.object({\n priority: IssuePrioritySchema,\n category: IssueCategorySchema,\n description: z.string(),\n suggestion: z.string(),\n});\n/** Structured issue reported by a visual check or question. */\nexport type Issue = z.infer<typeof IssueSchema>;\n\n// --- Per-statement result (for check) ---\n\n/** Zod schema for model confidence labels on statement-level results. */\nexport const ConfidenceSchema = z.enum([\"high\", \"medium\", \"low\"]);\n/** Confidence level attached to a statement result. */\nexport type Confidence = z.infer<typeof ConfidenceSchema>;\n\n/** Zod schema for an individual statement evaluation within `check()`. */\nexport const StatementResultSchema = z.object({\n statement: z.string(),\n pass: z.boolean(),\n reasoning: z.string(),\n confidence: ConfidenceSchema.optional(),\n /**\n * For video inputs, the approximate timestamp (in seconds, from the start of the clip)\n * of the frame that most clearly demonstrates the statement. `null` when the statement\n * fails or applies across the whole clip. Always omitted for image inputs.\n */\n timestampSeconds: z.number().nonnegative().nullable().optional(),\n});\n/** Outcome of a single statement evaluated by `check()`. */\nexport type StatementResult = z.infer<typeof StatementResultSchema>;\n\n// --- Usage info ---\n\n/** Zod schema for token and latency metadata attached to API calls. */\nexport const UsageInfoSchema = z.object({\n inputTokens: z.number(),\n outputTokens: z.number(),\n /** Reasoning/thinking tokens consumed by the model (informational, typically included within outputTokens). */\n reasoningTokens: z.number().optional(),\n estimatedCost: z.number().optional(),\n durationSeconds: z.number().nonnegative().optional(),\n});\n/** Token usage and optional cost/latency metadata for a provider call. */\nexport type UsageInfo = z.infer<typeof UsageInfoSchema>;\n\n// --- Base result (shared fields) ---\n\nconst BaseResultSchema = z.object({\n pass: z.boolean(),\n reasoning: z.string(),\n usage: UsageInfoSchema.optional(),\n});\n\n// --- check() / template result ---\n\n/**\n * Zod schema for results returned by `check()` and template helpers.\n *\n * Note: the runtime `CheckResult` TypeScript type extends this schema with\n * an optional `frames` field that is populated client-side for video inputs.\n * Parsing a stored `CheckResult` through this schema will silently drop\n * `frames` because the schema only describes what the model returns.\n */\nexport const CheckResultSchema = BaseResultSchema.extend({\n issues: z.array(IssueSchema),\n statements: z.array(StatementResultSchema),\n});\n/**\n * Metadata describing the sampled-frame timeline used when the input was a video.\n * Populated client-side; not part of the model's response.\n */\nexport interface VideoFramesMetadata {\n /** Total number of frames sampled from the video. */\n count: number;\n /** Timestamp (seconds, from the start of the clip) of each sampled frame, in order. */\n timestampsSeconds: number[];\n /** Total duration of the source video in seconds. */\n durationSeconds: number;\n}\n/** Result returned by `check()` and the template convenience methods. */\nexport type CheckResult = z.infer<typeof CheckResultSchema> & {\n /** Present only when the input was a video. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n};\n\n// --- compare() result ---\n\n/** Zod schema for an individual visual change reported by `compare()`. */\nexport const ChangeEntrySchema = z.object({\n description: z.string(),\n severity: IssuePrioritySchema,\n});\n/** Single visual change reported by `compare()`. */\nexport type ChangeEntry = z.infer<typeof ChangeEntrySchema>;\n\n/** Zod schema for the parsed model response returned by `compare()`. */\nexport const CompareResultSchema = BaseResultSchema.extend({\n changes: z.array(ChangeEntrySchema).max(50),\n});\n// diffImage is appended client-side after the AI response is parsed,\n// so it intentionally does not appear in CompareResultSchema.\n/** Result returned by `compare()`, optionally including an AI-generated diff image. */\nexport type CompareResult = z.infer<typeof CompareResultSchema> & {\n diffImage?: DiffImageResult;\n};\n\n// --- ask() result ---\n\n/**\n * Zod schema for results returned by `ask()`.\n *\n * Note: the runtime `AskResult` TypeScript type extends this schema with an\n * optional `frames` field that is populated client-side for video inputs.\n * Parsing a stored `AskResult` through this schema will silently drop\n * `frames` because the schema only describes what the model returns.\n */\nexport const AskResultSchema = z.object({\n summary: z.string(),\n issues: z.array(IssueSchema),\n /**\n * For video inputs, the indices of frames the model relied on to answer.\n * Indices are 0-based and refer to entries in `frames.timestampsSeconds`.\n */\n frameReferences: z.array(z.number().int().nonnegative()).optional(),\n usage: UsageInfoSchema.optional(),\n});\n/** Result returned by `ask()`. */\nexport type AskResult = z.infer<typeof AskResultSchema> & {\n /** Present only when the input was a video. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n};\n\n// --- Image / media input ---\n\n/** Supported input shapes for image arguments accepted by the client. */\nexport type ImageInput = Buffer | Uint8Array | string;\n\n/**\n * Supported input shapes for media arguments accepted by the client.\n * Identical to `ImageInput` today — the client auto-detects whether the bytes are\n * an image or a video.\n */\nexport type MediaInput = ImageInput;\n\n/** Supported image MIME types accepted by all providers. */\nexport type SupportedMimeType = \"image/jpeg\" | \"image/png\" | \"image/webp\" | \"image/gif\";\n\n/** Supported video MIME types the client can accept and sample frames from. */\nexport type SupportedVideoMimeType =\n | \"video/mp4\"\n | \"video/webm\"\n | \"video/quicktime\"\n | \"video/x-matroska\";\n\n// --- Provider names ---\n\n/** Supported provider identifiers. */\nexport type ProviderName = \"anthropic\" | \"openai\" | \"google\";\n\n// --- VisualAI config ---\n\n/**\n * Configuration for creating a visual AI client.\n *\n * @example\n * ```ts\n * const client = visualAI({\n * model: \"gpt-5-mini\",\n * apiKey: process.env.OPENAI_API_KEY,\n * });\n * ```\n */\nexport interface VisualAIConfig {\n apiKey?: string;\n model?: string;\n /** Enable error diagnostic logging to stderr. Does not enable prompt/response logging — use `debugPrompt` and `debugResponse` for that. */\n debug?: boolean;\n /** Log prompts to stderr. */\n debugPrompt?: boolean;\n /** Log responses to stderr. */\n debugResponse?: boolean;\n maxTokens?: number;\n reasoningEffort?: ReasoningEffortLevel;\n trackUsage?: boolean;\n}\n\n// --- Template option types ---\n\n/** Optional instructions for `check()`. */\nexport interface CheckOptions {\n instructions?: readonly string[];\n /**\n * Frame-sampling configuration applied when the input is a video.\n * Ignored for image inputs. See `VideoSamplingOptions` for defaults.\n */\n video?: VideoSamplingOptions;\n}\n\n/** Optional instructions for `ask()`. */\nexport interface AskOptions {\n instructions?: readonly string[];\n /**\n * Frame-sampling configuration applied when the input is a video.\n * Ignored for image inputs. See `VideoSamplingOptions` for defaults.\n */\n video?: VideoSamplingOptions;\n}\n\n// --- Diff image types ---\n\n/** Metadata and binary content for an AI-generated diff image. */\nexport interface DiffImageResult {\n data: Buffer;\n width: number;\n height: number;\n mimeType: \"image/png\";\n}\n\n/** Optional prompt, instructions, and diff configuration for `compare()`. */\nexport interface CompareOptions {\n prompt?: string;\n instructions?: readonly string[];\n diffImage?: boolean;\n}\n\n/** Optional instructions for `elementsVisible()` and `elementsHidden()`. */\nexport interface ElementsVisibilityOptions {\n instructions?: readonly string[];\n}\n\n/** Options for the built-in accessibility template. */\nexport interface AccessibilityOptions {\n checks?: AccessibilityCheckName[];\n instructions?: readonly string[];\n}\n\n/** Options for the built-in layout template. */\nexport interface LayoutOptions {\n checks?: LayoutCheckName[];\n instructions?: readonly string[];\n}\n\n/** Options for the built-in page-load template. */\nexport interface PageLoadOptions {\n expectLoaded?: boolean;\n instructions?: readonly string[];\n}\n\n/** Options for the built-in content template. */\nexport interface ContentOptions {\n checks?: ContentCheckName[];\n instructions?: readonly string[];\n}\n\n// --- Normalized image (internal) ---\n\n/** Internal normalized image representation passed to provider drivers. */\nexport interface NormalizedImage {\n readonly data: Buffer;\n readonly mimeType: SupportedMimeType;\n readonly base64: string;\n}\n\n// --- Video sampling ---\n\n/**\n * Options for sampling frames from a video input. Defaults match the v1\n * sampling strategy: 1 fps, capped at 10 frames, max duration 10 s.\n */\nexport interface VideoSamplingOptions {\n /** Sampling rate in frames per second. Default `1`. */\n fps?: number;\n /**\n * Maximum number of frames extracted regardless of duration. Default `10`.\n * Hard-capped at `60` to keep memory bounded; values above the cap throw\n * `VisualAIVideoError`.\n */\n maxFrames?: number;\n /**\n * Maximum video duration accepted, in seconds. Videos longer than this\n * cause `VisualAIVideoError` to be thrown before any provider call.\n * Default `10`.\n */\n maxDurationSeconds?: number;\n}\n\n/**\n * A single frame extracted from a video input. Identical in shape to\n * `NormalizedImage` so it can be passed transparently to provider drivers.\n */\nexport interface Frame extends NormalizedImage {\n /** 0-based timestamp (seconds, from the start of the clip) of this frame. */\n readonly timestampSeconds: number;\n /** 0-based index of this frame within the sampled sequence. */\n readonly index: number;\n}\n","import { z } from \"zod\";\nimport { VisualAIResponseParseError } from \"../errors.js\";\nimport { AskResultSchema, CheckResultSchema, CompareResultSchema } from \"../types.js\";\nimport type { AskResult, CheckResult, CompareResult } from \"../types.js\";\n\nfunction stripCodeFences(text: string): string {\n const trimmed = text.trim();\n const match = /^```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/s.exec(trimmed);\n return match?.[1] ?? trimmed;\n}\n\nexport const CheckResponseSchema = CheckResultSchema.omit({ usage: true });\nexport const AskResponseSchema = AskResultSchema.omit({ usage: true });\nexport const CompareResponseSchema = CompareResultSchema.omit({ usage: true });\n\nfunction parseResponse<T>(raw: string, schema: z.ZodType<T>): T {\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripCodeFences(raw));\n } catch {\n throw new VisualAIResponseParseError(\n `Failed to parse AI response as JSON: ${raw.slice(0, 200)}`,\n raw,\n );\n }\n\n const result = schema.safeParse(parsed);\n if (!result.success) {\n throw new VisualAIResponseParseError(\n `AI response does not match expected schema: ${result.error.message}`,\n raw,\n );\n }\n\n return result.data;\n}\n\nfunction reconcileCheckResult(result: Omit<CheckResult, \"usage\">): Omit<CheckResult, \"usage\"> {\n if (result.statements.length === 0) {\n return result;\n }\n\n const passCount = result.statements.filter((s) => s.pass).length;\n const total = result.statements.length;\n const computedPass = passCount === total;\n const countPrefix = `${passCount} of ${total} checks passed`;\n const reasoning = `${countPrefix}. ${result.reasoning}`;\n\n return {\n ...result,\n pass: computedPass,\n reasoning,\n };\n}\n\nexport function parseCheckResponse(raw: string): Omit<CheckResult, \"usage\"> {\n const result = parseResponse(raw, CheckResponseSchema);\n return reconcileCheckResult(result);\n}\n\nexport function parseAskResponse(raw: string): Omit<AskResult, \"usage\"> {\n return parseResponse(raw, AskResponseSchema);\n}\n\nexport function parseCompareResponse(raw: string): Omit<CompareResult, \"usage\"> {\n return parseResponse(raw, CompareResponseSchema);\n}\n","import { Model } from \"../constants.js\";\nimport { VisualAIConfigError } from \"../errors.js\";\nimport {\n buildAccessibilityPrompt,\n buildContentPrompt,\n buildElementsVisibilityPrompt,\n buildLayoutPrompt,\n buildPageLoadPrompt,\n} from \"../templates/index.js\";\nimport type {\n AccessibilityOptions,\n AskOptions,\n AskResult,\n CheckOptions,\n CheckResult,\n CompareOptions,\n CompareResult,\n ContentOptions,\n ElementsVisibilityOptions,\n ImageInput,\n LayoutOptions,\n MediaInput,\n NormalizedImage,\n PageLoadOptions,\n ProviderName,\n VideoFramesMetadata,\n VisualAIConfig,\n} from \"../types.js\";\nimport { AnthropicDriver } from \"../providers/anthropic.js\";\nimport { GoogleDriver } from \"../providers/google.js\";\nimport { OpenAIDriver } from \"../providers/openai.js\";\nimport type { ProviderConfig, ProviderDriver, SendMessageOptions } from \"../providers/types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { debugLog, processUsage, timedSendMessage, withErrorDebug } from \"./debug.js\";\nimport { generateAiDiff } from \"./diff.js\";\nimport { normalizeImage } from \"./image.js\";\nimport { normalizeMedia, type NormalizedMedia } from \"./media.js\";\nimport {\n buildAskPrompt,\n buildCheckPrompt,\n buildComparePrompt,\n type MediaContext,\n} from \"./prompt.js\";\nimport {\n AskResponseSchema,\n CheckResponseSchema,\n CompareResponseSchema,\n parseAskResponse,\n parseCheckResponse,\n parseCompareResponse,\n} from \"./response.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport type { z } from \"zod\";\n\nfunction toSchemaOptions(schema: z.ZodType): SendMessageOptions {\n return {\n responseSchema: zodToJsonSchema(schema, { target: \"openAi\" }) as Record<string, unknown>,\n };\n}\n\n/**\n * High-level client for running visual checks against screenshots or other images.\n *\n * @example\n * ```ts\n * const client = visualAI({ model: \"gpt-5-mini\" });\n * const result = await client.check(\"./tests/fixtures/small.png\", \"The button is visible\");\n * ```\n */\nexport interface VisualAIClient {\n /**\n * Verifies one or more statements against a single image or video.\n *\n * Pass an image (PNG/JPEG/WebP/GIF) for a single-frame check. Pass a video\n * (MP4/WebM/MOV/MKV file path, URL, base64, Buffer) and the client samples\n * frames automatically; statements pass if they are true at any sampled\n * frame, and each statement result includes the timestamp where it\n * matched. The `frames` metadata on the result reports which timestamps\n * the model saw.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string.\n * @param statements One or more statements to validate against the input.\n * @param options Optional additional instructions and video sampling overrides.\n * @returns A structured result describing pass/fail, issues, and statement reasoning.\n * @throws {VisualAIConfigError} When no statements are provided.\n * @throws {VisualAIImageError} When an image input cannot be loaded or decoded.\n * @throws {VisualAIVideoError} When a video input cannot be loaded, exceeds the duration cap, or ffmpeg is missing.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.check(screenshot, [\n * \"The primary CTA is visible\",\n * \"There is no error banner\",\n * ]);\n * ```\n * @example\n * ```ts\n * const result = await client.check(\"./recording.webm\", [\n * 'A success toast with text \"Saved\" briefly appears',\n * ]);\n * console.log(result.statements[0].timestampSeconds); // e.g. 3.5\n * ```\n */\n check(\n input: MediaInput,\n statements: string | string[],\n options?: CheckOptions,\n ): Promise<CheckResult>;\n /**\n * Asks an open-ended question about an image or video and returns a structured summary.\n *\n * Video inputs are sampled into frames and analyzed as a chronological\n * timeline. The result's `frameReferences` array surfaces which frames the\n * model relied on for its answer.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string.\n * @param prompt Prompt describing what to inspect in the input.\n * @param options Optional additional instructions and video sampling overrides.\n * @returns A summary with any detected issues.\n * @throws {VisualAIImageError} When an image input cannot be loaded or decoded.\n * @throws {VisualAIVideoError} When a video input cannot be loaded, exceeds the duration cap, or ffmpeg is missing.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.ask(screenshot, \"What looks visually broken on this page?\");\n * ```\n */\n ask(input: MediaInput, prompt: string, options?: AskOptions): Promise<AskResult>;\n /**\n * Compares two images and reports meaningful visual differences.\n *\n * @param imageA Baseline image source.\n * @param imageB Candidate image source.\n * @param options Optional comparison prompt, instructions, and diff-image settings.\n * `gemini-3-flash-preview` generates an annotated diff image by default;\n * pass `{ diffImage: false }` to opt out.\n * @returns A structured comparison result with optional diff image metadata.\n * @throws {VisualAIImageError} When either image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * const result = await client.compare(beforeScreenshot, afterScreenshot, {\n * diffImage: true,\n * });\n * ```\n */\n compare(imageA: ImageInput, imageB: ImageInput, options?: CompareOptions): Promise<CompareResult>;\n /**\n * Checks that the listed elements are visible in an image. Image input only —\n * template helpers do not accept video input; use `check()` for video.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param elements Element descriptions that should be present and visible.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A structured pass/fail result for the requested elements.\n * @throws {VisualAIConfigError} When `elements` is empty.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.elementsVisible(screenshot, [\"Save button\", \"Profile avatar\"]);\n * ```\n */\n elementsVisible(\n image: ImageInput,\n elements: string[],\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult>;\n /**\n * Checks that the listed elements are not visible in an image. Image input\n * only — template helpers do not accept video input; use `check()` for video.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param elements Element descriptions that should be absent or hidden.\n * @param options Optional additional instructions appended to the prompt.\n * @returns A structured pass/fail result for the requested elements.\n * @throws {VisualAIConfigError} When `elements` is empty.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.elementsHidden(screenshot, [\"Cookie banner\"]);\n * ```\n */\n elementsHidden(\n image: ImageInput,\n elements: string[],\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult>;\n /**\n * Runs the built-in accessibility template against an image. Image input\n * only — template helpers do not accept video input.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional checks and extra instructions for the accessibility prompt.\n * @returns A structured accessibility-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.accessibility(screenshot, { checks: [\"contrast\"] });\n * ```\n */\n accessibility(image: ImageInput, options?: AccessibilityOptions): Promise<CheckResult>;\n /**\n * Runs the built-in layout template against an image. Image input only —\n * template helpers do not accept video input.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional checks and extra instructions for the layout prompt.\n * @returns A structured layout-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.layout(screenshot, { checks: [\"overflow\", \"alignment\"] });\n * ```\n */\n layout(image: ImageInput, options?: LayoutOptions): Promise<CheckResult>;\n /**\n * Runs the built-in page-load template against an image. Image input only —\n * template helpers do not accept video input.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional page-load expectations and extra instructions.\n * @returns A structured page-load check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.pageLoad(screenshot, { expectLoaded: true });\n * ```\n */\n pageLoad(image: ImageInput, options?: PageLoadOptions): Promise<CheckResult>;\n /**\n * Runs the built-in content template against an image. Image input only —\n * template helpers do not accept video input.\n *\n * @param image Image source as a buffer, URL, file path, or base64 string.\n * @param options Optional content checks and extra instructions.\n * @returns A structured content-focused check result.\n * @throws {VisualAIImageError} When the image cannot be loaded or decoded.\n * @throws {VisualAIError} When the provider rejects the request or returns invalid output.\n * @example\n * ```ts\n * await client.content(screenshot, { checks: [\"placeholder-text\"] });\n * ```\n */\n content(image: ImageInput, options?: ContentOptions): Promise<CheckResult>;\n}\n\ntype ProviderFactory = (config: ProviderConfig) => ProviderDriver;\n\nconst PROVIDER_REGISTRY = {\n anthropic: (config) => new AnthropicDriver(config),\n openai: (config) => new OpenAIDriver(config),\n google: (config) => new GoogleDriver(config),\n} as const satisfies Record<ProviderName, ProviderFactory>;\n\nfunction createDriver(provider: ProviderName, config: ProviderConfig): ProviderDriver {\n return PROVIDER_REGISTRY[provider](config);\n}\n\nconst checkSchemaOptions = toSchemaOptions(CheckResponseSchema);\nconst askSchemaOptions = toSchemaOptions(AskResponseSchema);\nconst compareSchemaOptions = toSchemaOptions(CompareResponseSchema);\n\nfunction mediaToProviderInputs(media: NormalizedMedia): {\n images: NormalizedImage[];\n mediaContext: MediaContext;\n framesMetadata: VideoFramesMetadata | undefined;\n} {\n if (media.kind === \"image\") {\n return {\n images: [media.image],\n mediaContext: { kind: \"image\" },\n framesMetadata: undefined,\n };\n }\n const timestamps = media.frames.map((f) => f.timestampSeconds);\n return {\n images: media.frames,\n mediaContext: {\n kind: \"video\",\n frameTimestamps: timestamps,\n durationSeconds: media.durationSeconds,\n },\n framesMetadata: {\n count: media.frames.length,\n timestampsSeconds: timestamps,\n durationSeconds: media.durationSeconds,\n },\n };\n}\n\n/**\n * Creates a configured visual AI client.\n *\n * @param config Model selection and runtime options for subsequent requests.\n * @returns A `VisualAIClient` instance with check, compare, ask, and template helpers.\n * @throws {VisualAIConfigError} When the provider or model configuration is invalid.\n * @throws {VisualAIAuthError} When required API credentials are missing.\n * @example\n * ```ts\n * import { expect, test } from \"@playwright/test\";\n * import { visualAI } from \"visual-ai-assertions\";\n *\n * test(\"hero loads correctly\", async ({ page }) => {\n * const client = visualAI({\n * model: \"gpt-5-mini\",\n * apiKey: process.env.OPENAI_API_KEY,\n * });\n *\n * await page.goto(\"https://example.com\");\n * const screenshot = await page.screenshot();\n * const result = await client.check(screenshot, [\n * \"The hero heading is visible\",\n * \"There is no loading spinner\",\n * ]);\n *\n * expect(result.pass).toBe(true);\n * });\n * ```\n */\nexport function visualAI(config: VisualAIConfig = {}): VisualAIClient {\n const resolvedConfig = resolveConfig(config);\n const driverConfig: ProviderConfig = {\n apiKey: resolvedConfig.apiKey,\n model: resolvedConfig.model,\n maxTokens: resolvedConfig.maxTokens,\n reasoningEffort: resolvedConfig.reasoningEffort,\n };\n const driver = createDriver(resolvedConfig.provider, driverConfig);\n\n async function checkElementsVisibility(\n image: ImageInput,\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n ): Promise<CheckResult> {\n const methodName = visible ? \"elementsVisible\" : \"elementsHidden\";\n if (elements.length === 0) {\n throw new VisualAIConfigError(`At least one element is required for ${methodName}()`);\n }\n\n return withErrorDebug(resolvedConfig, methodName, async () => {\n const img = await normalizeImage(image);\n const prompt = buildElementsVisibilityPrompt(elements, visible, options);\n debugLog(resolvedConfig, `${methodName} prompt`, prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, `${methodName} response`, response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(methodName, response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n }\n\n return {\n async check(input, statements, options) {\n const stmts = Array.isArray(statements) ? statements : [statements];\n if (stmts.length === 0) {\n throw new VisualAIConfigError(\"At least one statement is required for check()\");\n }\n\n return withErrorDebug(resolvedConfig, \"check\", async () => {\n const media = await normalizeMedia(input, options?.video);\n const { images, mediaContext, framesMetadata } = mediaToProviderInputs(media);\n const prompt = buildCheckPrompt(stmts, {\n instructions: options?.instructions,\n media: mediaContext,\n });\n debugLog(resolvedConfig, \"check prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, images, prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"check response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n ...(framesMetadata ? { frames: framesMetadata } : {}),\n usage: processUsage(\"check\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async ask(input, userPrompt, options) {\n return withErrorDebug(resolvedConfig, \"ask\", async () => {\n const media = await normalizeMedia(input, options?.video);\n const { images, mediaContext, framesMetadata } = mediaToProviderInputs(media);\n const prompt = buildAskPrompt(userPrompt, {\n instructions: options?.instructions,\n media: mediaContext,\n });\n debugLog(resolvedConfig, \"ask prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, images, prompt, askSchemaOptions);\n debugLog(resolvedConfig, \"ask response\", response.text, \"response\");\n\n const result = parseAskResponse(response.text);\n return {\n ...result,\n ...(framesMetadata ? { frames: framesMetadata } : {}),\n usage: processUsage(\"ask\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async compare(imageA, imageB, options) {\n return withErrorDebug(resolvedConfig, \"compare\", async () => {\n const [imgA, imgB] = await Promise.all([normalizeImage(imageA), normalizeImage(imageB)]);\n const prompt = buildComparePrompt({\n userPrompt: options?.prompt,\n instructions: options?.instructions,\n });\n debugLog(resolvedConfig, \"compare prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [imgA, imgB], prompt, compareSchemaOptions);\n debugLog(resolvedConfig, \"compare response\", response.text, \"response\");\n\n const supportsAnnotatedDiff =\n resolvedConfig.provider === \"google\" &&\n resolvedConfig.model === Model.Google.GEMINI_3_FLASH_PREVIEW;\n const effectiveDiffImage = options?.diffImage ?? (supportsAnnotatedDiff ? true : false);\n\n let diffImage;\n if (effectiveDiffImage) {\n try {\n diffImage = await generateAiDiff(imgA, imgB, resolvedConfig.model, driver);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[visual-ai-assertions] warning: diff generation failed: ${msg}\\n`,\n );\n }\n }\n\n const result = parseCompareResponse(response.text);\n return {\n ...result,\n ...(diffImage ? { diffImage } : {}),\n usage: processUsage(\"compare\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n elementsVisible(image, elements, options) {\n return checkElementsVisibility(image, elements, true, options);\n },\n\n elementsHidden(image, elements, options) {\n return checkElementsVisibility(image, elements, false, options);\n },\n\n async accessibility(image, options) {\n return withErrorDebug(resolvedConfig, \"accessibility\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildAccessibilityPrompt(options);\n debugLog(resolvedConfig, \"accessibility prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"accessibility response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\n \"accessibility\",\n response.usage,\n response.durationSeconds,\n resolvedConfig,\n ),\n };\n });\n },\n\n async layout(image, options) {\n return withErrorDebug(resolvedConfig, \"layout\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildLayoutPrompt(options);\n debugLog(resolvedConfig, \"layout prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"layout response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"layout\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async pageLoad(image, options) {\n return withErrorDebug(resolvedConfig, \"pageLoad\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildPageLoadPrompt(options);\n debugLog(resolvedConfig, \"pageLoad prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"pageLoad response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"pageLoad\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n\n async content(image, options) {\n return withErrorDebug(resolvedConfig, \"content\", async () => {\n const img = await normalizeImage(image);\n const prompt = buildContentPrompt(options);\n debugLog(resolvedConfig, \"content prompt\", prompt, \"prompt\");\n\n const response = await timedSendMessage(driver, [img], prompt, checkSchemaOptions);\n debugLog(resolvedConfig, \"content response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n usage: processUsage(\"content\", response.usage, response.durationSeconds, resolvedConfig),\n };\n });\n },\n };\n}\n","import type { CheckResult, CompareResult } from \"./types.js\";\nimport { VisualAIAssertionError } from \"./errors.js\";\n\n/**\n * Formats a check result into a readable multiline string for logs or test failures.\n *\n * @param result Structured result returned by `check()` or a template helper.\n * @param label Optional label appended to the output header.\n * @returns A human-readable summary of the check result.\n * @example\n * ```ts\n * console.log(formatCheckResult(result, \"Checkout page\"));\n * ```\n */\nexport function formatCheckResult(result: CheckResult, label?: string): string {\n if (result.pass) {\n const header = label ? `Visual AI Check Passed (${label})` : \"Visual AI Check Passed\";\n return `${header}\\n${\"=\".repeat(header.length)}\\n${result.reasoning}`;\n }\n\n const header = label ? `Visual AI Check Failed (${label})` : \"Visual AI Check Failed\";\n const lines: string[] = [header, \"=\".repeat(header.length), result.reasoning];\n\n if (result.statements.length > 0) {\n lines.push(\"\", \"Statements:\");\n for (const s of result.statements) {\n const status = s.pass ? \"PASS\" : \"FAIL\";\n const confidence = s.confidence ? ` (${s.confidence})` : \"\";\n lines.push(` ${status} \"${s.statement}\"`);\n lines.push(` ${s.reasoning}${confidence}`);\n }\n }\n\n if (result.issues.length > 0) {\n lines.push(\"\", \"Issues:\");\n for (const issue of result.issues) {\n lines.push(` [${issue.priority}/${issue.category}] ${issue.description}`);\n lines.push(` → ${issue.suggestion}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Formats a compare result into a readable multiline string for logs or test failures.\n *\n * @param result Structured result returned by `compare()`.\n * @param label Optional label appended to the output header.\n * @returns A human-readable summary of the compare result.\n * @example\n * ```ts\n * console.log(formatCompareResult(result, \"Before/after deploy\"));\n * ```\n */\nexport function formatCompareResult(result: CompareResult, label?: string): string {\n const status = result.pass ? \"Passed\" : \"Failed\";\n const header = label ? `Visual AI Compare ${status} (${label})` : `Visual AI Compare ${status}`;\n const lines: string[] = [header, \"=\".repeat(header.length), result.reasoning];\n\n if (result.changes.length > 0) {\n lines.push(\"\", \"Changes:\");\n for (const change of result.changes) {\n lines.push(` [${change.severity}] ${change.description}`);\n }\n }\n\n if (result.diffImage) {\n const { width, height } = result.diffImage;\n lines.push(\"\", `Diff image: ${width}x${height} (AI-generated)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Throws a `VisualAIAssertionError` when a check result did not pass.\n *\n * @param result Structured result returned by `check()` or a template helper.\n * @param label Optional label appended to the assertion message.\n * @returns Nothing when the result passes.\n * @throws {VisualAIAssertionError} When `result.pass` is `false`.\n * @example\n * ```ts\n * assertVisualResult(result, \"Homepage\");\n * ```\n */\nexport function assertVisualResult(result: CheckResult, label?: string): void {\n if (!result.pass) {\n throw new VisualAIAssertionError(formatCheckResult(result, label), result);\n }\n}\n\n/**\n * Throws a `VisualAIAssertionError` when a compare result did not pass.\n *\n * @param result Structured result returned by `compare()`.\n * @param label Optional label appended to the assertion message.\n * @returns Nothing when the result passes.\n * @throws {VisualAIAssertionError} When `result.pass` is `false`.\n * @example\n * ```ts\n * assertVisualCompareResult(result, \"Login flow\");\n * ```\n */\nexport function assertVisualCompareResult(result: CompareResult, label?: string): void {\n if (!result.pass) {\n throw new VisualAIAssertionError(formatCompareResult(result, label), result);\n }\n}\n"],"mappings":"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","names":["ALL_CHECKS","CHECK_STATEMENTS","ALL_CHECKS","CHECK_STATEMENTS","sharp","width","height","sharp","readFile","extname","extname","buf","mimeType","readFile","header"]}
|