visual-ai-assertions 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/core/prompt.ts","../src/templates/elements-visibility.ts","../src/constants.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/providers/openrouter.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/media.ts","../src/core/debug-frames.ts","../src/core/frame-dedupe.ts","../src/core/video.ts","../src/types.ts","../src/core/response.ts","../src/core/client.ts","../src/format.ts"],"sourcesContent":["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\n/**\n * Video check schema. The wording differs only in what the model is looking\n * at: discrete sampled frames, or a continuous clip it samples itself.\n */\nfunction buildCheckOutputSchemaVideo(unit: \"frame\" | \"moment\"): string {\n const anyPoint = unit === \"frame\" ? \"ANY frame of the timeline\" : \"ANY moment of the video\";\n const bestPoint =\n unit === \"frame\"\n ? \"the timestamp of the frame that most clearly demonstrates it\"\n : \"the timestamp of the moment that most clearly demonstrates it\";\n const citing = unit === \"frame\" ? \"citing frame timestamps\" : \"citing timestamps\";\n const exampleWhere = unit === \"frame\" ? \"at the 3.5s frame\" : \"at 3.5s\";\n return `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 ${anyPoint}, unless the wording explicitly says otherwise (e.g. \"throughout\", \"at all times\")\n3. For each statement that passes, set \"timestampSeconds\" to ${bestPoint} (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} 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 ${exampleWhere}\", \"confidence\": \"high\", \"timestampSeconds\": 3.5 }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n}\n\nconst CHECK_OUTPUT_SCHEMA_VIDEO = buildCheckOutputSchemaVideo(\"frame\");\nconst CHECK_OUTPUT_SCHEMA_NATIVE_VIDEO = buildCheckOutputSchemaVideo(\"moment\");\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 ASK_OUTPUT_SCHEMA_NATIVE_VIDEO = `Respond with a JSON object matching this exact structure:\n{\n \"summary\": string, // high-level summary of what happens across the video\n \"issues\": [...], // list of issues/findings, can be empty\n \"timestampReferences\": number[] // seconds from the start of the clip of the moments the answer relies on (in order)\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nPrioritize issues by severity (critical / major / minor) as for image input.\nCite timestamps in \"timestampReferences\" 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\nconst DEFAULT_CHECK_ROLE_NATIVE_VIDEO =\n \"You are a visual QA assistant. Evaluate the provided video recording precisely and objectively, treating it as a chronological timeline.\";\n\nconst DEFAULT_ASK_ROLE_NATIVE_VIDEO =\n \"You are a visual QA assistant. Analyze the provided video recording 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 | {\n kind: \"video\";\n frameTimestamps: readonly number[];\n durationSeconds: number;\n /** Sampled frames omitted because they matched the preceding kept frame. */\n droppedUnchanged?: number;\n }\n | {\n /** The video itself was attached; the provider samples it. */\n kind: \"native-video\";\n durationSeconds: number;\n };\n\nfunction buildNativeVideoSection(durationSeconds: number): string {\n return `Video recording:\n- Total duration: ${durationSeconds.toFixed(2)}s\n\nThe attached file is the complete video recording. Treat it as a chronological timeline and refer to moments by timestamp (seconds from the start of the clip) where helpful.`;\n}\n\nfunction buildVideoTimelineSection(\n frameTimestamps: readonly number[],\n durationSeconds: number,\n droppedUnchanged = 0,\n): string {\n const formatted = frameTimestamps.map((t, i) => ` ${i}: ${t.toFixed(2)}s`).join(\"\\n\");\n const attached = frameTimestamps.length;\n const sampledLine =\n droppedUnchanged > 0\n ? `- ${attached + droppedUnchanged} frames sampled (in chronological order); ${droppedUnchanged} ` +\n `${droppedUnchanged === 1 ? \"was\" : \"were\"} dropped because ` +\n `${droppedUnchanged === 1 ? \"it\" : \"they\"} did not visibly change from the preceding kept frame, ` +\n `so ${attached} ${attached === 1 ? \"image is\" : \"images are\"} attached`\n : `- ${attached} frames sampled (in chronological order)`;\n const droppedGuidance =\n droppedUnchanged > 0\n ? ` Frames sampled between two consecutive listed timestamps looked the same as the earlier listed frame, and frames sampled after the last listed timestamp looked the same as the last attached image until the clip ended at ${durationSeconds.toFixed(2)}s.`\n : \"\";\n return `Video timeline:\n- Total duration: ${durationSeconds.toFixed(2)}s\n${sampledLine}\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.${droppedGuidance}`;\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 =\n media?.kind === \"video\"\n ? DEFAULT_CHECK_ROLE_VIDEO\n : media?.kind === \"native-video\"\n ? DEFAULT_CHECK_ROLE_NATIVE_VIDEO\n : DEFAULT_CHECK_ROLE;\n\n const sections = [options?.role ?? defaultRole];\n\n if (media?.kind === \"video\") {\n sections.push(\n buildVideoTimelineSection(\n media.frameTimestamps,\n media.durationSeconds,\n media.droppedUnchanged,\n ),\n );\n } else if (media?.kind === \"native-video\") {\n sections.push(buildNativeVideoSection(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(\n media?.kind === \"video\"\n ? CHECK_OUTPUT_SCHEMA_VIDEO\n : media?.kind === \"native-video\"\n ? CHECK_OUTPUT_SCHEMA_NATIVE_VIDEO\n : CHECK_OUTPUT_SCHEMA_IMAGE,\n );\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 = [\n media?.kind === \"video\"\n ? DEFAULT_ASK_ROLE_VIDEO\n : media?.kind === \"native-video\"\n ? DEFAULT_ASK_ROLE_NATIVE_VIDEO\n : DEFAULT_ASK_ROLE,\n ];\n\n if (media?.kind === \"video\") {\n sections.push(\n buildVideoTimelineSection(\n media.frameTimestamps,\n media.durationSeconds,\n media.droppedUnchanged,\n ),\n );\n } else if (media?.kind === \"native-video\") {\n sections.push(buildNativeVideoSection(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(\n media?.kind === \"video\"\n ? ASK_OUTPUT_SCHEMA_VIDEO\n : media?.kind === \"native-video\"\n ? ASK_OUTPUT_SCHEMA_NATIVE_VIDEO\n : ASK_OUTPUT_SCHEMA_IMAGE,\n );\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_HIDDEN_ROLE =\n \"Check whether specific UI elements are absent or hidden in this screenshot.\";\n\n/** \"a and b\" for two clauses, \"a, b, and c\" for more. */\nfunction joinClauses(clauses: readonly string[]): string {\n if (clauses.length <= 1) return clauses[0] ?? \"\";\n if (clauses.length === 2) return `${clauses[0]} and ${clauses[1]}`;\n return `${clauses.slice(0, -1).join(\", \")}, and ${clauses[clauses.length - 1]}`;\n}\n\n/**\n * The role names exactly what the enabled rules judge, so the headline never\n * promises more or less than the instructions below it.\n */\nfunction visibleRole(finalState: boolean, requireCorrectRendering: boolean): string {\n const clauses = [\"present\", \"properly visible\"];\n if (requireCorrectRendering) clauses.push(\"correctly rendered\");\n if (finalState) clauses.push(\"in their finished state\");\n return `Check whether specific UI elements are ${joinClauses(clauses)} in this screenshot.`;\n}\n\n/**\n * Being cut off at an edge means two opposite things, and the difference is the\n * judgement a human tester makes without thinking about it.\n *\n * A card peeking past the end of a carousel is not a defect: the peek is how\n * the interface advertises that the row scrolls, and the user reaches the card\n * normally. Failing that check would report a bug that does not exist, which is\n * what a naive \"partial means fail\" rule does to every scrollable row.\n *\n * A control sliced by the status bar or the home indicator is a defect: no\n * amount of scrolling brings it into view.\n *\n * A single screenshot does not record whether a surface scrolls, so the model\n * has to infer it from layout convention, the same way a person does.\n *\n * Something drawn on top of an element splits the same way, and the test is not\n * how much it covers. A price pill or a favourite icon coexists with finished\n * content, and on a typical card those cover more of the image than a spinner\n * does. A spinner covering less of it still means something different: it\n * asserts the element is not ready. So the question is what the overlay says\n * about the element's state, not how much of it you can still see.\n */\nconst ELEMENTS_VISIBLE_CLIPPING_RULES: readonly string[] = [\n \"When an element is partly rendered but cut off at an edge, decide whether ordinary scrolling would bring it fully into view. For example, a card peeking past the end of a horizontal carousel, a filter chip in a row that continues past the screen edge, or a list item partly below the bottom of a scrolling feed is reachable that way, so the check for that element PASSES. Say in your reasoning that it is reached by scrolling.\",\n \"An element that scrolling cannot bring into view is NOT properly visible: one sliced by the screen edge itself, or cut off or overlapped by fixed chrome such as the status bar, a notch, a home indicator, a sticky header, or a fixed bottom navigation bar. That is a layout fault, so the check for that element FAILS. Describe the clipping in your reasoning.\",\n \"The scrolling allowance above applies only to elements that are at least partly rendered. If no part of an element is on screen, the check for that element FAILS: do not infer that it exists below the fold. Judge only what this screenshot actually shows.\",\n];\n\n/**\n * Dropped when the caller passes `finalState: false`, because a screenshot\n * captured mid-load is expected to carry loading chrome. Everything else still\n * applies: whether an element is present, and whether it is clipped or covered,\n * does not depend on the interface having settled.\n */\nconst ELEMENTS_VISIBLE_FINAL_STATE_RULE =\n \"Judge each element in its finished, presented state. Things a design draws on top of an element — a badge, a favourite icon, a duration or price pill, a gradient scrim — coexist with finished content and leave it visible. An overlay that says the element is NOT ready — a loading spinner, a skeleton placeholder, a shimmer, a progress bar, an error or retry overlay — means the element is not properly visible even when you can still make out what sits underneath, so the check for that element FAILS. Name which of the two you are seeing in your reasoning.\";\n\n/**\n * Opt-in. Measured on the assertion bench with the rule on and off, five reps\n * each: it caught the one bullet naming a present-but-overlapping element\n * (5/5 on both models, against 3/5 and 0/5 without it) and nothing else, while\n * tripling Gemini's flakiness on unrelated presence questions. A presence\n * assertion should not pay that by default; pass `requireCorrectRendering: true`\n * for the assertions where a rendering defect is the thing being checked.\n *\n * Open-ended defect hunting makes models over-report, which is why the closing\n * sentence draws the line at defects worth arguing about.\n *\n * Misalignment is named even though the models measured so far never report it;\n * a model that can see it should have the instruction available.\n */\nconst ELEMENTS_VISIBLE_CORRECT_RENDERING_RULE =\n \"An element that is present but clearly defective in how it is rendered is NOT properly visible: text at contrast too low to read, elements overlapping or colliding with one another, an element visibly out of alignment with the siblings it should line up with, or text cut off mid-word inside its own container. The check for that element FAILS. In your reasoning, say that the element is present and then name the defect. Only clear, unambiguous defects count: do not fail an element for tight spacing, stylistic choices, or anything you would have to argue for.\";\n\n/** A blocking overlay hides an element whether or not the interface has settled. */\nconst ELEMENTS_VISIBLE_OCCLUSION_RULE =\n \"An element a user could not read or use because a modal, dialog, cookie banner, toast or similar overlay covers it is NOT visible: the check for that element FAILS.\";\n\nfunction visibleRules(finalState: boolean, requireCorrectRendering: boolean): readonly string[] {\n return [\n ...ELEMENTS_VISIBLE_CLIPPING_RULES,\n ...(finalState ? [ELEMENTS_VISIBLE_FINAL_STATE_RULE] : []),\n ...(requireCorrectRendering ? [ELEMENTS_VISIBLE_CORRECT_RENDERING_RULE] : []),\n ELEMENTS_VISIBLE_OCCLUSION_RULE,\n ];\n}\n\nconst ELEMENTS_HIDDEN_BASE_RULES: readonly string[] = [\n \"An element that is rendered at all, even partly, is not hidden, so the check for that element FAILS. This includes one peeking past the edge of a scrollable row or feed, which the user reaches by scrolling normally. Note the partial visibility in your reasoning.\",\n \"An element that appears nowhere in this screenshot counts as hidden, even if the page might reveal it after scrolling. Judge only what this screenshot actually shows.\",\n];\n\n/** Mirrors the visible side: dropped when the caller says the screen is mid-load. */\nconst ELEMENTS_HIDDEN_FINAL_STATE_RULE =\n \"An element sitting under a loading spinner, skeleton, progress bar or error overlay is still rendered, so it is not hidden and the check for that element FAILS. It is not properly visible either; that is what the visible check is for.\";\n\nfunction hiddenRules(finalState: boolean): readonly string[] {\n return finalState\n ? [...ELEMENTS_HIDDEN_BASE_RULES, ELEMENTS_HIDDEN_FINAL_STATE_RULE]\n : ELEMENTS_HIDDEN_BASE_RULES;\n}\n\nexport function buildElementsVisibilityPrompt(\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n): string {\n // \"visible\" rather than \"fully visible\": an element reached by scrolling\n // counts, and a headline that said otherwise would contradict the edge rules.\n const statements = visible\n ? elements.map((el) => `The element \"${el}\" is visible on the page`)\n : elements.map((el) => `The element \"${el}\" is NOT visible on the page`);\n\n // Default: the screenshot shows a settled interface, which is what a test\n // asserting on a finished screen means. `finalState: false` drops the rules\n // that treat loading chrome as a defect.\n const finalState = options?.finalState ?? true;\n // Presence-only by default. `elementsHidden` asks about absence, so a\n // rendering defect cannot change its answer and the option is ignored there.\n const correctRendering = visible && (options?.requireCorrectRendering ?? false);\n const defaultRules = visible\n ? visibleRules(finalState, correctRendering)\n : hiddenRules(finalState);\n const instructions = options?.instructions\n ? [...defaultRules, ...options.instructions]\n : defaultRules;\n\n const role = visible ? visibleRole(finalState, correctRendering) : ELEMENTS_HIDDEN_ROLE;\n\n return buildCheckPrompt(statements, { role, instructions });\n}\n","import type { ProviderName } from \"./types.js\";\n\n// --- Reasoning effort constants ---\n\n/** Supported reasoning effort levels. */\n/**\n * Abstract reasoning-effort hint. Each provider driver maps it to its native\n * mechanism; levels a provider lacks clamp to its nearest tier.\n *\n * `minimal` is **not universally accepted**. OpenAI rejects it per-model with\n * HTTP 400 (\"Unsupported value: 'minimal' is not supported with the\n * '<model>' model\"), observed on `gpt-6-astra` and `gpt-6.1-sol`, whose\n * supported set is low/medium/high/xhigh/max, and on `gpt-6-sol` and\n * `gpt-6-luna`, which accept none/low/medium/high/xhigh/max. Gemini defines\n * the tier but some models (e.g. Gemini 3.1 Pro) reject it, so Google and\n * OpenRouter clamp it to `low` rather than send it. Use `minimal` only\n * against an OpenAI model known to accept it; `low` is the portable floor.\n */\nexport const ReasoningEffort = {\n MINIMAL: \"minimal\",\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// --- Image fidelity constants ---\n\n/**\n * Abstract image-detail hint. Each provider driver maps it to its native\n * mechanism (OpenAI/OpenRouter `detail`, Google `mediaResolution`); Anthropic\n * has no equivalent (Claude auto-downscales to ~1568px / 1.15MP regardless).\n * `\"auto\"` sends no detail field, preserving each provider's default.\n */\nexport const ImageDetail = {\n AUTO: \"auto\",\n LOW: \"low\",\n HIGH: \"high\",\n} as const;\n\n/** Union of valid image-detail values, derived from the ImageDetail constant. */\nexport type ImageDetailLevel = (typeof ImageDetail)[keyof typeof ImageDetail];\n\n/** Default image-detail hint: `\"auto\"` leaves each provider's own default in place. */\nexport const DEFAULT_IMAGE_DETAIL: ImageDetailLevel = ImageDetail.AUTO;\n\n/**\n * Longest-edge pixel cap applied when normalizing images before they reach a\n * provider. 1568 matches Anthropic's recommended max edge (images within a\n * 1568px box stay under Claude's ~1.15MP limit). Raising it only affects\n * providers that also accept a higher-resolution request (e.g. OpenAI at\n * `detail:\"high\"`, which scales into a 2048px box); Anthropic re-downscales.\n */\nexport const DEFAULT_MAX_IMAGE_DIMENSION = 1568;\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 OPENROUTER: \"openrouter\",\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 FABLE_5_1: \"claude-fable-5-1\",\n FABLE_5: \"claude-fable-5\",\n OPUS_5_5: \"claude-opus-5-5\",\n OPUS_5: \"claude-opus-5\",\n OPUS_4_8: \"claude-opus-4-8\",\n OPUS_4_7: \"claude-opus-4-7\",\n OPUS_4_6: \"claude-opus-4-6\",\n SONNET_5_5: \"claude-sonnet-5-5\",\n SONNET_5: \"claude-sonnet-5\",\n SONNET_4_6: \"claude-sonnet-4-6\",\n HAIKU_4_5: \"claude-haiku-4-5\",\n },\n OpenAI: {\n GPT_6_ASTRA: \"gpt-6-astra\",\n GPT_6_1_SOL: \"gpt-6.1-sol\",\n GPT_6_SOL: \"gpt-6-sol\",\n GPT_6_LUNA: \"gpt-6-luna\",\n GPT_5_6_SOL: \"gpt-5.6-sol\",\n GPT_5_6_TERRA: \"gpt-5.6-terra\",\n GPT_5_6_LUNA: \"gpt-5.6-luna\",\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_8_FLASH: \"gemini-3.8-flash\",\n GEMINI_3_7_FLASH: \"gemini-3.7-flash\",\n GEMINI_3_6_FLASH: \"gemini-3.6-flash\",\n GEMINI_3_5_FLASH: \"gemini-3.5-flash\",\n GEMINI_3_5_FLASH_LITE: \"gemini-3.5-flash-lite\",\n GEMINI_3_1_PRO_PREVIEW: \"gemini-3.1-pro-preview\",\n GEMINI_3_1_FLASH_LITE: \"gemini-3.1-flash-lite\",\n GEMINI_3_FLASH_PREVIEW: \"gemini-3-flash-preview\",\n },\n /**\n * Models routed through OpenRouter (https://openrouter.ai). Slugs always\n * carry a vendor prefix (`vendor/model`), which is how provider inference\n * recognizes them. All listed models accept image input.\n */\n OpenRouter: {\n MUSE_SPARK_1_3: \"meta/muse-spark-1.3\",\n GROK_4_7: \"x-ai/grok-4.7\",\n GROK_4_6: \"x-ai/grok-4.6\",\n GROK_4_5: \"x-ai/grok-4.5\",\n KIMI_K3: \"moonshotai/kimi-k3\",\n KIMI_K2_7_CODE: \"moonshotai/kimi-k2.7-code\",\n QWEN_3_8_MAX: \"qwen/qwen3.8-max\",\n QWEN_3_7_PLUS: \"qwen/qwen3.7-plus\",\n QWEN_3_6_FLASH: \"qwen/qwen3.6-flash\",\n GLM_5_3_FLASH: \"z-ai/glm-5.3-flash\",\n MIMO_V2_6_PRO: \"xiaomi/mimo-v2.6-pro\",\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 | (typeof Model.OpenRouter)[keyof typeof Model.OpenRouter];\n\n// --- Default model per provider ---\n\n/**\n * Default model selection used when a caller omits `config.model`.\n *\n * Two of these carry constraints the older defaults did not:\n * - `gpt-6.1-sol` rejects `reasoningEffort: \"minimal\"` (and `none`) with HTTP\n * 400; `low` is the floor. See the `ReasoningEffort` note above.\n * - `meta/muse-spark-1.3` is age-gated on OpenRouter and returns HTTP 403 until\n * the account completes the 18+ confirmation at\n * openrouter.ai/settings/preferences. It also reasons by default (~370-814\n * reasoning tokens per call) even with no effort configured, so cost per call\n * runs above what its headline rate suggests.\n */\nexport const DEFAULT_MODELS = {\n [Provider.ANTHROPIC]: Model.Anthropic.SONNET_5_5,\n [Provider.OPENAI]: Model.OpenAI.GPT_6_1_SOL,\n [Provider.GOOGLE]: Model.Google.GEMINI_3_8_FLASH,\n [Provider.OPENROUTER]: Model.OpenRouter.MUSE_SPARK_1_3,\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/**\n * Budget for `MODELS_REQUIRING_LARGE_OUTPUT_BUDGET`. OpenAI's reasoning guide\n * recommends reserving \"at least 25,000 tokens for reasoning and outputs\" when\n * starting out; this leaves headroom above that while still capping cost. The\n * listed models' longest observed calls stayed under 9000 output tokens, and at\n * their output prices a call that used the whole budget would cost at most\n * about $0.20 (qwen3.8-max, $6/MTok).\n */\nexport const OPENAI_HEAVY_REASONING_MAX_TOKENS = 32768;\n\n/**\n * Models that exhaust `DEFAULT_MAX_TOKENS` on reasoning at *every* effort\n * level, not just high/xhigh, and so return status \"incomplete\" with no\n * visible answer. They get `OPENAI_HEAVY_REASONING_MAX_TOKENS` by default\n * regardless of `reasoningEffort`; an explicit `maxTokens` still wins.\n *\n * `gpt-6-astra` was listed here after image `ask()` calls truncated at 4096\n * and even 16384. That turned out to be the image response schema requiring\n * the video-only `frameReferences` (see `AskImageResponseSchema`), not\n * reasoning: the model printed whitespace until the budget ran out. With that\n * fixed, Astra completed `ask()` and `check()` at 4096 and never exceeded 548\n * output tokens on the discovery bench, so it was removed.\n *\n * The Qwen entries were verified live at the 4096 default with that fix in\n * place. Unlike Astra they genuinely reason past it (4500-5400 reasoning tokens\n * on long calls, under 600 visible): at `medium`, qwen3.8-max truncated 5/8\n * `ask()` and 3/8 `check()` calls and qwen3.7-plus 5/8 `ask()`; with this\n * budget both completed 16/16. grok-4.7 reasons as long but never truncated,\n * since its upstream does not count reasoning against `max_tokens`.\n *\n * kimi-k2.7-code is deliberately absent. It truncates ~8% of calls at 4096,\n * but those are calls where it writes prose instead of JSON: at 32768 they\n * finish and fail to parse instead (~12%), so a larger budget only makes each\n * failure cost more.\n */\nexport const MODELS_REQUIRING_LARGE_OUTPUT_BUDGET: ReadonlySet<string> = new Set<string>([\n Model.OpenRouter.QWEN_3_8_MAX,\n Model.OpenRouter.QWEN_3_7_PLUS,\n]);\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 ...Object.values(Model.OpenRouter).map((m) => [m, Provider.OPENROUTER] 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 // Varies by upstream model; the driver sends no reasoning field unless configured.\n openrouter: \"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 /** Detects color choices likely to be indistinguishable to viewers with common color vision deficiencies */\n COLOR_BLINDNESS: \"color-blindness\",\n /** Detects information conveyed by color alone, without a non-color cue (icon, text, pattern, position) */\n COLOR_ALONE: \"color-alone\",\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 { 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 \"Purely decorative color (branding, backgrounds, gradients) is not a color-blindness or color-alone issue — only flag color usage that carries meaning a user must perceive to use the interface.\",\n \"Only evaluate color cues that are visible in the screenshot. Do not assume hover/focus state colors exist if they are not shown.\",\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 [Accessibility.COLOR_BLINDNESS]:\n \"Information conveyed by color remains distinguishable to viewers with common color vision deficiencies — status indicators, chart series, and other meaningful color pairings do not rely on hue combinations that are commonly confused (e.g., red/green, blue/purple)\",\n [Accessibility.COLOR_ALONE]:\n \"Information conveyed by color is also conveyed through at least one non-color cue — text labels, icons, shapes, patterns, underlines, or position accompany any color-based meaning (e.g., required fields, error states, chart legends, link styling)\",\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 { DEFAULT_MAX_TOKENS, 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, carried forward by\n// Fable 5.1, Fable 5, Opus 4.8, Opus 5, Opus 5.5, Sonnet 5, and Sonnet 5.5.\n// Older Anthropic models (Opus 4.6, Sonnet 4.6) reject \"xhigh\" but accept\n// \"max\", which is why our xhigh maps to \"max\" on those models only.\nconst XHIGH_CAPABLE_MODELS: ReadonlySet<string> = new Set([\n Model.Anthropic.FABLE_5_1,\n Model.Anthropic.FABLE_5,\n Model.Anthropic.OPUS_5_5,\n Model.Anthropic.OPUS_5,\n Model.Anthropic.OPUS_4_8,\n Model.Anthropic.OPUS_4_7,\n Model.Anthropic.SONNET_5_5,\n Model.Anthropic.SONNET_5,\n]);\n\nfunction mapEffort(level: ReasoningEffortLevel, model: string): string {\n // Anthropic's adaptive thinking has no \"minimal\" tier; clamp to its floor.\n if (level === \"minimal\") return \"low\";\n if (level !== \"xhigh\") return level;\n return XHIGH_CAPABLE_MODELS.has(model) ? \"xhigh\" : \"max\";\n}\n\n// Haiku 4.5 rejects `thinking: {type: \"adaptive\"}` — it only supports the older\n// budget-based extended thinking API, so effort maps to a token budget instead.\nconst BUDGET_THINKING_MODELS: ReadonlySet<string> = new Set([Model.Anthropic.HAIKU_4_5]);\n\nconst EFFORT_TO_BUDGET_TOKENS: Readonly<Record<ReasoningEffortLevel, number>> = {\n // 1024 is Anthropic's minimum thinking budget, so minimal and low coincide.\n minimal: 1024,\n low: 1024,\n medium: 4096,\n high: 8192,\n xhigh: 16384,\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: {\n input_tokens: number;\n output_tokens: number;\n /**\n * Prompt tokens served from Anthropic's cache. Reported in its own bucket\n * *alongside* `input_tokens` rather than inside it, unlike OpenAI/Google.\n */\n cache_read_input_tokens?: number;\n };\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 private timeout: ProviderConfig[\"timeout\"];\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 this.timeout = config.timeout;\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: {\n apiKey: string;\n timeout?: number;\n }) => AnthropicClient)({\n apiKey,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // Note: `imageDetail` (ProviderConfig) is intentionally not used here. Claude\n // has no detail/resolution parameter and auto-downscales images to ~1568px /\n // 1.15MP, so a higher-fidelity request would give no additional performance.\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 if (BUDGET_THINKING_MODELS.has(this.model)) {\n const budgetTokens = EFFORT_TO_BUDGET_TOKENS[this.reasoningEffort];\n requestParams.thinking = { type: \"enabled\", budget_tokens: budgetTokens };\n // Thinking tokens share the max_tokens budget and the API requires\n // budget_tokens < max_tokens, so leave room for the actual response.\n requestParams.max_tokens = Math.max(this.maxTokens, budgetTokens + DEFAULT_MAX_TOKENS);\n } else {\n requestParams.thinking = { type: \"adaptive\" };\n requestParams.output_config = {\n effort: mapEffort(this.reasoningEffort, this.model),\n };\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 ...(message.usage.cache_read_input_tokens !== undefined && {\n cachedInputTokens: message.usage.cache_read_input_tokens,\n }),\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, NormalizedVideo } from \"../types.js\";\nimport type { ImageDetailLevel, ReasoningEffortLevel } from \"../constants.js\";\nimport type {\n ImageGenerationOptions,\n ImageGenerationResponse,\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n RawVideoProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst DEFAULT_IMAGE_GEN_MODEL = \"gemini-2.5-flash-image\";\n\n/**\n * Gemini caps a whole inline request at 20 MB; videos up to this size go\n * inline as base64 and anything larger is uploaded through the Files API.\n */\nexport const GEMINI_INLINE_VIDEO_LIMIT_BYTES = 19 * 1024 * 1024;\nconst GEMINI_FILE_POLL_INTERVAL_MS = 2_000;\nconst GEMINI_FILE_POLL_TIMEOUT_MS = 5 * 60_000;\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 /** Cached subset of `promptTokenCount` (implicit or explicit context caching). */\n cachedContentTokenCount?: number;\n };\n}\n\n/** Minimal shape of a Gemini Files API record. */\ninterface GeminiFile {\n name?: string;\n uri?: string;\n mimeType?: string;\n state?: string;\n error?: { message?: string };\n}\n\ninterface GoogleClient {\n models: {\n generateContent(params: Record<string, unknown>): Promise<GoogleGenerateContentResponse>;\n };\n files: {\n upload(params: { file: Blob; config?: { mimeType?: string } }): Promise<GeminiFile>;\n get(params: { name: string }): Promise<GeminiFile>;\n delete(params: { name: string }): Promise<unknown>;\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 1:1 mapping so \"medium\" means native medium, matching the other providers.\n * (Until v0.17 this was shifted one level down — medium -> \"low\" — which left\n * Gemini models thinking far less than peers at the same configured effort.)\n * Google has no level above \"high\", so xhigh clamps; \"minimal\" is unused,\n * which also sidesteps models that reject it (e.g. Gemini 3.1 Pro).\n */\nconst GOOGLE_THINKING_LEVEL = {\n // Gemini does define a \"minimal\" thinking level, but some models reject it\n // (e.g. Gemini 3.1 Pro), so \"minimal\" clamps to \"low\" here as well.\n minimal: \"low\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"high\",\n} as const satisfies Record<ReasoningEffortLevel, string>;\n\n/**\n * Maps the abstract image-detail hint to Gemini's `mediaResolution` token-budget\n * tier (LOW 280 → HIGH 1120 tokens/image). \"auto\" is omitted so Gemini's own\n * default resolution applies. Gemini 3 also offers MEDIA_RESOLUTION_ULTRA_HIGH\n * (2240 tokens) if an even higher tier is ever wanted.\n */\nconst GOOGLE_MEDIA_RESOLUTION: Partial<Record<ImageDetailLevel, string>> = {\n low: \"MEDIA_RESOLUTION_LOW\",\n high: \"MEDIA_RESOLUTION_HIGH\",\n};\n\ninterface GeminiUsageMetadata {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n thoughtsTokenCount?: number;\n /** Cached subset of `promptTokenCount` (implicit or explicit context caching). */\n cachedContentTokenCount?: number;\n}\n\n/**\n * Normalize Gemini usage. Gemini reports the visible answer\n * (`candidatesTokenCount`) and the thinking trace (`thoughtsTokenCount`)\n * separately, but bills thinking at the output-token rate. We fold thinking into\n * `outputTokens` so the count matches what's billed (and matches OpenAI/OpenRouter,\n * whose output counts already include reasoning), and expose `reasoningTokens` as\n * the breakdown. Without this, cost estimates omit Gemini's thinking entirely.\n */\nfunction toGeminiUsage(um: GeminiUsageMetadata | undefined) {\n if (!um) return undefined;\n const thoughts = um.thoughtsTokenCount ?? 0;\n return {\n inputTokens: um.promptTokenCount ?? 0,\n outputTokens: (um.candidatesTokenCount ?? 0) + thoughts,\n ...(um.thoughtsTokenCount !== undefined && { reasoningTokens: um.thoughtsTokenCount }),\n ...(um.cachedContentTokenCount !== undefined && {\n cachedInputTokens: um.cachedContentTokenCount,\n }),\n };\n}\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 private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\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: {\n apiKey: string;\n httpOptions?: { timeout: number };\n }) => GoogleClient)({\n apiKey,\n ...(this.timeout !== undefined && { httpOptions: { timeout: this.timeout } }),\n });\n return this.client;\n }\n\n /** Request config shared by image and video messages. */\n private generationConfig(): Record<string, unknown> {\n return {\n responseMimeType: \"application/json\",\n maxOutputTokens: this.maxTokens,\n ...(this.reasoningEffort && {\n thinkingConfig: {\n thinkingLevel: GOOGLE_THINKING_LEVEL[this.reasoningEffort],\n },\n }),\n ...(this.imageDetail &&\n GOOGLE_MEDIA_RESOLUTION[this.imageDetail] && {\n mediaResolution: GOOGLE_MEDIA_RESOLUTION[this.imageDetail],\n }),\n };\n }\n\n /** Runs one generateContent call and normalizes finish reasons, text, and usage. */\n private async generate(client: GoogleClient, contents: unknown[]): Promise<RawProviderResponse> {\n try {\n const response = await client.models.generateContent({\n model: this.model,\n contents,\n config: this.generationConfig(),\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 return {\n text: response.text ?? \"\",\n usage: toGeminiUsage(response.usageMetadata),\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n return this.generate(client, [...this.toGeminiParts(images), prompt]);\n }\n\n /**\n * Uploads a video through the Files API and waits until Gemini has finished\n * processing it. Returns the ACTIVE file record.\n */\n private async uploadVideo(client: GoogleClient, video: NormalizedVideo): Promise<GeminiFile> {\n try {\n const uploaded = await client.files.upload({\n file: new Blob([new Uint8Array(video.data)], { type: video.mimeType }),\n config: { mimeType: video.mimeType },\n });\n const name = uploaded.name;\n if (!name) {\n throw new VisualAIProviderError(\"Gemini Files API returned a file without a name.\");\n }\n const deadline = Date.now() + GEMINI_FILE_POLL_TIMEOUT_MS;\n let current = uploaded;\n while (current.state === \"PROCESSING\") {\n if (Date.now() > deadline) {\n throw new VisualAIProviderError(\n `Gemini file ${name} was still processing after ${GEMINI_FILE_POLL_TIMEOUT_MS}ms.`,\n );\n }\n await sleep(GEMINI_FILE_POLL_INTERVAL_MS);\n current = await client.files.get({ name });\n }\n if (current.state !== \"ACTIVE\") {\n throw new VisualAIProviderError(\n `Gemini file ${name} ended in state ${current.state ?? \"unknown\"}: ${current.error?.message ?? \"no error message\"}`,\n );\n }\n if (!current.uri) {\n throw new VisualAIProviderError(\"Gemini Files API returned an ACTIVE file without a URI.\");\n }\n return current;\n } catch (err) {\n if (err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n /**\n * Sends the video bytes themselves. Gemini samples the clip server-side at\n * `video.fps` and, unlike sampled frames, also hears the audio track. Small\n * videos go inline; larger ones are uploaded via the Files API and deleted\n * again afterwards (they would expire on their own after 48 h).\n */\n async sendVideoMessage(\n video: NormalizedVideo,\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawVideoProviderResponse> {\n const client = await this.getClient();\n const videoMetadata = { fps: video.fps };\n\n if (video.data.byteLength <= GEMINI_INLINE_VIDEO_LIMIT_BYTES) {\n const part = {\n inlineData: { data: video.data.toString(\"base64\"), mimeType: video.mimeType },\n videoMetadata,\n };\n const response = await this.generate(client, [part, prompt]);\n return { ...response, delivery: \"inline\" };\n }\n\n const file = await this.uploadVideo(client, video);\n try {\n const part = {\n fileData: { fileUri: file.uri, mimeType: file.mimeType ?? video.mimeType },\n videoMetadata,\n };\n const response = await this.generate(client, [part, prompt]);\n return { ...response, delivery: \"file\" };\n } finally {\n if (file.name) {\n // Best-effort tidiness; never mask the original control flow.\n await client.files.delete({ name: file.name }).catch(() => undefined);\n }\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: toGeminiUsage(response.usageMetadata),\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 /** OpenAI caches prompt prefixes automatically above ~1024 tokens; this is\n * the cached subset of `input_tokens`, already discounted in billing. */\n input_tokens_details?: { cached_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 private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\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; timeout?: number }) => OpenAIClient)({\n apiKey,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // OpenAI `detail` scales high→2048px box (shortest side 768), low→512px.\n // Omitted for \"auto\" so the default request shape is unchanged.\n const detail = this.imageDetail && this.imageDetail !== \"auto\" ? this.imageDetail : undefined;\n const imageBlocks = images.map((img) => ({\n type: \"input_image\" as const,\n image_url: `data:${img.mimeType};base64,${img.base64}`,\n ...(detail ? { detail } : {}),\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 const cachedInputTokens = response.usage?.input_tokens_details?.cached_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 ...(cachedInputTokens !== undefined && { cachedInputTokens }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import {\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 ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst OPENROUTER_BASE_URL = \"https://openrouter.ai/api/v1\";\n\n/**\n * OpenRouter normalizes reasoning across upstream vendors to low/medium/high;\n * there is no xhigh, so it clamps to high.\n */\nconst OPENROUTER_REASONING_EFFORT: Record<ReasoningEffortLevel, \"low\" | \"medium\" | \"high\"> = {\n // OpenRouter normalizes upstream vendors to low/medium/high only, so\n // \"minimal\" has no native equivalent and clamps to the floor.\n minimal: \"low\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"high\",\n};\n\n/** Minimal interface for the OpenAI SDK chat-completions surface used by this driver. */\ninterface OpenRouterCompletionResult {\n choices?: {\n message?: { content?: string | null };\n finish_reason?: string | null;\n }[];\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n /** Cached subset of `prompt_tokens`, when the upstream provider reports one. */\n prompt_tokens_details?: { cached_tokens?: number };\n /** Actual cost in USD, returned because the request sets `usage: { include: true }`. */\n cost?: number;\n };\n}\n\ninterface OpenRouterClient {\n chat: {\n completions: {\n create(params: Record<string, unknown>): Promise<OpenRouterCompletionResult>;\n };\n };\n}\n\n/**\n * Driver for models routed through OpenRouter (xAI, Moonshot, Qwen, ...).\n * Speaks the OpenAI chat-completions protocol via the `openai` SDK pointed at\n * the OpenRouter base URL, so no additional SDK dependency is required.\n */\nexport class OpenRouterDriver implements ProviderDriver {\n private client: OpenRouterClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\n }\n\n private async getClient(): Promise<OpenRouterClient> {\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(\n \"OpenAI SDK not installed (required for the OpenRouter provider). Run: npm install openai\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.OPENROUTER_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"OpenRouter API key not found. Set OPENROUTER_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (OpenAI as new (opts: {\n apiKey: string;\n baseURL: string;\n timeout?: number;\n }) => OpenRouterClient)({\n apiKey,\n baseURL: OPENROUTER_BASE_URL,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // OpenAI-chat-completions `detail`, passed through to the upstream model.\n // Omitted for \"auto\" so the default request shape is unchanged.\n const detail = this.imageDetail && this.imageDetail !== \"auto\" ? this.imageDetail : undefined;\n const imageParts = images.map((img) => ({\n type: \"image_url\" as const,\n image_url: {\n url: `data:${img.mimeType};base64,${img.base64}`,\n ...(detail ? { detail } : {}),\n },\n }));\n\n try {\n const responseFormat = options?.responseSchema\n ? {\n type: \"json_schema\" as const,\n json_schema: {\n name: \"visual_ai_response\",\n strict: true,\n schema: options.responseSchema,\n },\n }\n : { type: \"json_object\" as const };\n\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_tokens: this.maxTokens,\n response_format: responseFormat,\n messages: [\n {\n role: \"user\",\n content: [...imageParts, { type: \"text\" as const, text: prompt }],\n },\n ],\n // OpenRouter-specific: include token accounting in the response.\n usage: { include: true },\n };\n\n if (this.reasoningEffort) {\n requestParams.reasoning = { effort: OPENROUTER_REASONING_EFFORT[this.reasoningEffort] };\n }\n\n const response = await client.chat.completions.create(requestParams);\n\n const choice = response.choices?.[0];\n if (!choice?.message) {\n throw new VisualAIProviderError(\"OpenRouter returned an empty response (no choices).\");\n }\n\n const text = choice.message.content ?? \"\";\n\n if (choice.finish_reason === \"length\") {\n throw new VisualAITruncationError(\n `Response truncated: OpenRouter returned finish_reason \"length\". 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 text,\n this.maxTokens,\n );\n }\n\n const reasoningTokens = response.usage?.completion_tokens_details?.reasoning_tokens;\n const cachedInputTokens = response.usage?.prompt_tokens_details?.cached_tokens;\n const cost = response.usage?.cost;\n\n return {\n text,\n usage: response.usage\n ? {\n inputTokens: response.usage.prompt_tokens,\n outputTokens: response.usage.completion_tokens,\n ...(reasoningTokens !== undefined && { reasoningTokens }),\n ...(cachedInputTokens !== undefined && { cachedInputTokens }),\n ...(cost !== undefined && { cost }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) {\n throw err;\n }\n throw mapProviderError(err);\n }\n }\n}\n","import {\n DEFAULT_IMAGE_DETAIL,\n DEFAULT_MAX_IMAGE_DIMENSION,\n DEFAULT_MAX_TOKENS,\n DEFAULT_MODELS,\n type ImageDetailLevel,\n MODEL_TO_PROVIDER,\n MODELS_REQUIRING_LARGE_OUTPUT_BUDGET,\n OPENAI_HEAVY_REASONING_MAX_TOKENS,\n OPENAI_REASONING_MAX_TOKENS,\n ReasoningEffort,\n type ReasoningEffortLevel,\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 maxImageDimension: number;\n imageDetail: ImageDetailLevel;\n /** Per-request timeout in ms; undefined leaves the provider SDK default in place. */\n timeout: number | undefined;\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 // OpenRouter slugs are always vendor-prefixed (\"x-ai/grok-4.5\"); no\n // first-party model name contains a slash.\n if (model.includes(\"/\")) return \"openrouter\";\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 [\"OPENROUTER_API_KEY\", \"openrouter\"],\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, OPENROUTER_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\n/**\n * `reasoningEffort` is a closed enum, so an unrecognised value is a typo rather\n * than a preference: throw like `parseBooleanEnv` instead of silently running at\n * the provider's default. The accepted list is derived from `ReasoningEffort` so\n * it cannot drift from the type.\n */\nfunction parseReasoningEffortEnv(\n envName: string,\n value: string | undefined,\n): ReasoningEffortLevel | undefined {\n if (value === undefined || value === \"\") return undefined;\n const levels: readonly string[] = Object.values(ReasoningEffort);\n const lower = value.toLowerCase();\n if (levels.includes(lower)) return lower as ReasoningEffortLevel;\n throw new VisualAIConfigError(\n `Invalid ${envName} value: \"${value}\". Use one of: ${levels.join(\", \")}.`,\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 // Resolved before the maxTokens rules below, which branch on the effort: an\n // env-set effort has to raise the OpenAI budget exactly as a param-set one does.\n const reasoningEffort =\n config.reasoningEffort ??\n parseReasoningEffortEnv(\"VISUAL_AI_REASONING_EFFORT\", process.env.VISUAL_AI_REASONING_EFFORT);\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 if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {\n throw new VisualAIConfigError(\n `Invalid timeout: ${config.timeout}. Must be a positive number of milliseconds.`,\n );\n }\n\n const userSetMaxTokens = config.maxTokens !== undefined;\n let maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // OpenAI and OpenRouter reasoning tokens share the output budget, so the\n // 4096 default can be consumed entirely by reasoning. Auto-increase either\n // when the effort is high/xhigh, or when the model reasons heavily enough to\n // truncate at every effort level (see MODELS_REQUIRING_LARGE_OUTPUT_BUDGET).\n const effortNeedsLargeBudget = reasoningEffort === \"high\" || reasoningEffort === \"xhigh\";\n const modelNeedsLargeBudget = MODELS_REQUIRING_LARGE_OUTPUT_BUDGET.has(model);\n if (\n !userSetMaxTokens &&\n (provider === \"openai\" || provider === \"openrouter\") &&\n (effortNeedsLargeBudget || modelNeedsLargeBudget)\n ) {\n maxTokens = modelNeedsLargeBudget\n ? OPENAI_HEAVY_REASONING_MAX_TOKENS\n : OPENAI_REASONING_MAX_TOKENS;\n if (debug) {\n const reason = modelNeedsLargeBudget\n ? `model \"${model}\", which exhausts smaller budgets on reasoning at any effort`\n : `provider \"${provider}\" with reasoningEffort \"${reasoningEffort}\"`;\n process.stderr.write(\n `[visual-ai-assertions] Auto-increased maxTokens from ${DEFAULT_MAX_TOKENS} to ${maxTokens} for ${reason}.\\n`,\n );\n }\n }\n\n return {\n provider,\n apiKey: config.apiKey,\n model,\n maxTokens,\n reasoningEffort,\n maxImageDimension: config.maxImageDimension ?? DEFAULT_MAX_IMAGE_DIMENSION,\n imageDetail: config.imageDetail ?? DEFAULT_IMAGE_DETAIL,\n timeout: config.timeout,\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 // Fable 5.1 is priced identically to Fable 5, the tier it succeeds.\n [`${Provider.ANTHROPIC}:${Model.Anthropic.FABLE_5_1}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.FABLE_5}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_5_5}`]: {\n inputPricePerToken: 4 / PER_MILLION,\n outputPricePerToken: 20 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_5}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_4_8}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_5_5}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_5}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\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 // Cached input is $1/MTok and cache writes $12.50/MTok; neither is modelled\n // here, since `calculateCost` applies no cache discount on any provider.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_ASTRA}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n // Cached input is $0.10/MTok (not modelled), half GPT-6 Sol's cached rate.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_1_SOL}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n // Cached input is $0.20/MTok (not modelled). Prompts above 272K input tokens\n // bill at 2x input / 1.5x output, which is far beyond screenshot-sized calls.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_SOL}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n // Cached input is $0.01/MTok and cache writes $0.125/MTok; neither is modelled.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_LUNA}`]: {\n inputPricePerToken: 0.1 / PER_MILLION,\n outputPricePerToken: 0.5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_SOL}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 30 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_TERRA}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 12 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_LUNA}`]: {\n inputPricePerToken: 0.2 / PER_MILLION,\n outputPricePerToken: 1.2 / 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 // Introductory pricing through 2026-12-31; reverts to $1.50/$7.50 per MTok\n // on 2027-01-01 (https://blog.google/.../3-8-flash-and-3-8-flash-cyber/).\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_8_FLASH}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n // Introductory pricing through 2026-12-31; reverts to $1.50/$7.50 per MTok\n // on 2027-01-01 (https://blog.google/.../introducing-gemini-3-7-flash/).\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_7_FLASH}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_6_FLASH}`]: {\n inputPricePerToken: 1.5 / PER_MILLION,\n outputPricePerToken: 7.5 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_5_FLASH}`]: {\n inputPricePerToken: 1.5 / PER_MILLION,\n outputPricePerToken: 9 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_5_FLASH_LITE}`]: {\n inputPricePerToken: 0.3 / PER_MILLION,\n outputPricePerToken: 2.5 / 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}`]: {\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 // OpenRouter passes through upstream per-model pricing (verified 2026-09-03\n // against https://openrouter.ai/api/v1/models).\n // Meta's own listed rates ($1.25 / $4.25, cached input $0.15) match\n // OpenRouter's pass-through exactly. Cached input is not modelled here:\n // `calculateCost` applies no cache discount on any provider.\n [`${Provider.OPENROUTER}:${Model.OpenRouter.MUSE_SPARK_1_3}`]: {\n inputPricePerToken: 1.25 / PER_MILLION,\n outputPricePerToken: 4.25 / PER_MILLION,\n },\n // Muse Spark 1.3's data-sharing tier: same model, ~12x cheaper, because\n // Meta trains on everything submitted through it. Deliberately keyed by the\n // literal slug rather than a `Model.OpenRouter` entry — it must never be\n // reachable via autocomplete or default selection. Pass the string yourself\n // (`model: \"meta/muse-spark-1.3-contributor\"`) to opt in; cost is still\n // tracked correctly once you do. Cached input is $0.002/MTok, not modelled\n // (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:meta/muse-spark-1.3-contributor`]: {\n inputPricePerToken: 0.1 / PER_MILLION,\n outputPricePerToken: 0.2 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_7}`]: {\n inputPricePerToken: 1.6 / PER_MILLION,\n outputPricePerToken: 4.8 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_6}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_5}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.KIMI_K3}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.KIMI_K2_7_CODE}`]: {\n inputPricePerToken: 0.82 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_8_MAX}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_7_PLUS}`]: {\n inputPricePerToken: 0.32 / PER_MILLION,\n outputPricePerToken: 1.28 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_6_FLASH}`]: {\n inputPricePerToken: 0.1875 / PER_MILLION,\n outputPricePerToken: 1.125 / PER_MILLION,\n },\n // Verified 2026-09-10 against https://openrouter.ai/api/v1/models. Cached\n // input is $0.03/MTok, not modelled (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GLM_5_3_FLASH}`]: {\n inputPricePerToken: 0.15 / PER_MILLION,\n outputPricePerToken: 0.5 / PER_MILLION,\n },\n // Verified 2026-09-23 against https://openrouter.ai/api/v1/models; both\n // upstream endpoints (Xiaomi, DeepInfra) charge the same rate. Cached input\n // is $0.0036/MTok, not modelled (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:${Model.OpenRouter.MIMO_V2_6_PRO}`]: {\n inputPricePerToken: 0.435 / PER_MILLION,\n outputPricePerToken: 0.87 / 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 RawVideoProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport type { NormalizedImage, NormalizedVideo, 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 const cachedTokenStr =\n usage.cachedInputTokens !== undefined ? ` (${usage.cachedInputTokens} cached)` : \"\";\n process.stderr.write(\n `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input${cachedTokenStr} + ${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 ...(rawUsage?.cachedInputTokens !== undefined && {\n cachedInputTokens: rawUsage.cachedInputTokens,\n }),\n estimatedCost: calculateCost(config.provider, config.model, inputTokens, outputTokens),\n ...(rawUsage?.cost !== undefined && { reportedCost: rawUsage.cost }),\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\n/**\n * Native-video counterpart of `timedSendMessage`. The caller has already\n * checked that the driver implements `sendVideoMessage`.\n */\nexport async function timedSendVideoMessage(\n driver: ProviderDriver,\n video: NormalizedVideo,\n prompt: string,\n options?: SendMessageOptions,\n): Promise<RawVideoProviderResponse & { durationSeconds: number }> {\n if (!driver.sendVideoMessage) {\n throw new VisualAIError(\"Provider driver does not support native video delivery\");\n }\n const start = performance.now();\n const response = await driver.sendVideoMessage(video, 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\n/**\n * Models that return annotated diff images via Gemini code execution: the whole\n * Gemini flash tier. `compare()` auto-enables a diff image on every model in\n * this set, and `generateAiDiff` refuses any model outside it.\n *\n * The Flash-Lite models (`gemini-3.5-flash-lite`, `gemini-3.1-flash-lite`) and\n * the Pro tier (`gemini-3.1-pro-preview`) are deliberately absent: they do not\n * drive code execution reliably enough to annotate an image.\n */\nexport const DIFF_ALLOWED_MODELS: ReadonlySet<string> = new Set([\n Model.Google.GEMINI_3_FLASH_PREVIEW,\n Model.Google.GEMINI_3_5_FLASH,\n Model.Google.GEMINI_3_6_FLASH,\n Model.Google.GEMINI_3_7_FLASH,\n Model.Google.GEMINI_3_8_FLASH,\n]);\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 (!DIFF_ALLOWED_MODELS.has(model)) {\n throw new VisualAIConfigError(\n `Annotated diff images are only supported with these Google models: ${[...DIFF_ALLOWED_MODELS].join(\", \")}.`,\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 { DEFAULT_MAX_IMAGE_DIMENSION } from \"../constants.js\";\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 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(\n data: Buffer,\n mimeType: SupportedMimeType,\n maxDimension: number,\n): 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 <= maxDimension && height <= maxDimension) {\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 <= maxDimension && height <= maxDimension) {\n return data;\n }\n\n return pipeline\n .resize({\n width: maxDimension,\n height: maxDimension,\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 maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\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, maxDimension);\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 { DEFAULT_MAX_IMAGE_DIMENSION } from \"../constants.js\";\nimport { VisualAIVideoError } from \"../errors.js\";\nimport { readFile } from \"node:fs/promises\";\nimport type {\n Frame,\n FramesInput,\n ImageInput,\n MediaInput,\n NormalizedImage,\n NormalizedVideo,\n TimestampedFrameInput,\n VideoSamplingOptions,\n} from \"../types.js\";\nimport { saveDebugFrames } from \"./debug-frames.js\";\nimport { dedupeFrames, resolveDedupeOptions } from \"./frame-dedupe.js\";\nimport { normalizeImage } from \"./image.js\";\nimport {\n decodeBase64,\n isDataUrl,\n isFilePath,\n looksLikeVideoBase64,\n parseDataUrl,\n} from \"./input-detect.js\";\nimport {\n DEFAULT_FPS,\n MAX_FRAMES_HARD_CAP,\n detectVideoMimeType,\n assertDurationWithinLimit,\n extractFrames,\n getVideoMimeFromExtension,\n probeDurationSeconds,\n resolveVideoSamplingOptions,\n resolveVideoToPath,\n} from \"./video.js\";\n\nexport type NormalizedMedia =\n | { kind: \"image\"; image: NormalizedImage }\n | {\n kind: \"video\";\n /** Frames to send to the provider, after unchanged frames were dropped. */\n frames: Frame[];\n durationSeconds: number;\n /** Sampled frames dropped because they matched the preceding kept frame. */\n droppedUnchanged: number;\n }\n | {\n kind: \"native-video\";\n /** The video itself, to be sent to a provider that accepts video input. */\n video: NormalizedVideo;\n };\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 * Narrows an input to the pre-sampled `FramesInput` shape. Buffers, typed\n * arrays, and strings (all valid `MediaInput`s) are excluded so only a plain\n * `{ frames: [...] }` object matches.\n */\nexport function isFramesInput(input: unknown): input is FramesInput {\n return (\n typeof input === \"object\" &&\n input !== null &&\n !Buffer.isBuffer(input) &&\n !(input instanceof Uint8Array) &&\n Array.isArray((input as { frames?: unknown }).frames)\n );\n}\n\nfunction isTimestampedFrameInput(\n frame: ImageInput | TimestampedFrameInput,\n): frame is TimestampedFrameInput {\n return (\n typeof frame === \"object\" &&\n !Buffer.isBuffer(frame) &&\n !(frame instanceof Uint8Array) &&\n \"image\" in frame\n );\n}\n\n/**\n * Normalizes pre-sampled frames into the same `{ kind: \"video\", frames,\n * durationSeconds }` envelope the video path produces, so everything\n * downstream (provider call, timeline prompt, frame metadata, timestamps,\n * frame references) works unchanged. Never loads ffmpeg.\n */\nexport async function normalizeFrames(\n input: FramesInput,\n maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\n): Promise<NormalizedMedia> {\n const rawFrames = input.frames;\n const fps = input.fps ?? DEFAULT_FPS;\n\n // Validate before decoding any image so a bad option fails fast.\n resolveDedupeOptions(input.dedupe);\n\n if (rawFrames.length === 0) {\n throw new VisualAIVideoError(\"frames must be a non-empty array of image inputs\");\n }\n if (rawFrames.length > MAX_FRAMES_HARD_CAP) {\n throw new VisualAIVideoError(\n `frames length ${rawFrames.length} exceeds the hard cap of ${MAX_FRAMES_HARD_CAP}. ` +\n `Pass fewer frames or open an issue if you need a larger limit.`,\n );\n }\n if (!Number.isFinite(fps) || fps <= 0) {\n throw new VisualAIVideoError(`Invalid fps: ${fps}. Must be a finite number > 0.`);\n }\n\n const sampled: Frame[] = await Promise.all(\n rawFrames.map(async (raw, index): Promise<Frame> => {\n const timestamped = isTimestampedFrameInput(raw);\n const imageInput = timestamped ? raw.image : raw;\n const explicit = timestamped ? raw.timestampSeconds : undefined;\n const timestampSeconds = explicit ?? index / fps;\n if (!Number.isFinite(timestampSeconds) || timestampSeconds < 0) {\n throw new VisualAIVideoError(\n `Invalid timestampSeconds for frame ${index}: ${String(timestampSeconds)}. ` +\n `Must be a finite number >= 0.`,\n );\n }\n const image = await normalizeImage(imageInput, maxDimension);\n return {\n data: image.data,\n mimeType: image.mimeType,\n get base64(): string {\n return image.base64;\n },\n timestampSeconds,\n index,\n };\n }),\n );\n\n // Duration isn't known for a bare frame sequence; use the latest timestamp so\n // the timeline prompt and frame metadata report a sensible span. Computed\n // over every supplied frame so dropping a static tail does not shorten it.\n const durationSeconds = sampled.reduce((max, f) => Math.max(max, f.timestampSeconds), 0);\n\n const { frames, dropped } = await dedupeFrames(sampled, input.dedupe);\n await saveDebugFrames(frames);\n return { kind: \"video\", frames, durationSeconds, droppedUnchanged: dropped };\n}\n\n/**\n * Prepares a video for native delivery: resolves it to bytes, probes the\n * duration, and enforces `maxDurationSeconds` before any provider call, exactly\n * as frame sampling does. No frames are extracted.\n */\nexport async function normalizeNativeVideo(\n input: MediaInput,\n videoOptions?: VideoSamplingOptions,\n): Promise<NormalizedMedia> {\n const { fps, maxDurationSeconds } = resolveVideoSamplingOptions(videoOptions);\n const { path, mimeType, cleanup } = await resolveVideoToPath(input);\n try {\n const durationSeconds = await probeDurationSeconds(path);\n assertDurationWithinLimit(durationSeconds, maxDurationSeconds);\n const data = await readFile(path);\n return { kind: \"native-video\", video: { data, mimeType, durationSeconds, fps } };\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/**\n * Single entry point used by the client. Accepts pre-sampled frames or auto-detects\n * whether `input` is an image or a video, returning a uniform `NormalizedMedia`\n * envelope. With `nativeVideo` set, a video input is prepared for native\n * delivery instead of being sampled into frames; images and pre-sampled frames\n * are unaffected.\n */\nexport async function normalizeMedia(\n input: MediaInput | FramesInput,\n videoOptions?: VideoSamplingOptions,\n maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\n nativeVideo = false,\n): Promise<NormalizedMedia> {\n if (isFramesInput(input)) {\n return normalizeFrames(input, maxDimension);\n }\n\n if (isVideoInput(input)) {\n if (nativeVideo) {\n return normalizeNativeVideo(input, videoOptions);\n }\n // Validate before touching the filesystem or ffmpeg so a bad option fails fast.\n resolveDedupeOptions(videoOptions?.dedupe);\n const { path, cleanup } = await resolveVideoToPath(input);\n try {\n const { frames: sampled, durationSeconds } = await extractFrames(\n path,\n videoOptions,\n maxDimension,\n );\n const { frames, dropped } = await dedupeFrames(sampled, videoOptions?.dedupe);\n // Debug output shows exactly the frames the model receives.\n await saveDebugFrames(frames);\n return { kind: \"video\", frames, durationSeconds, droppedUnchanged: dropped };\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, maxDimension);\n return { kind: \"image\", image };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { Frame } from \"../types.js\";\n\nexport const DEBUG_FRAMES_ENV = \"VISUAL_AI_DEBUG_FRAMES\";\nexport const DEBUG_FRAMES_DIR_ENV = \"VISUAL_AI_DEBUG_FRAMES_DIR\";\nconst DEFAULT_DIR_NAME = \"visual-ai-debug-frames\";\n\nfunction isEnabled(env: NodeJS.ProcessEnv): boolean {\n const raw = env[DEBUG_FRAMES_ENV];\n if (raw === undefined || raw === \"\") return false;\n const lower = raw.toLowerCase();\n return lower === \"true\" || lower === \"1\";\n}\n\nfunction timestampSlug(date: Date): string {\n return date.toISOString().replace(/[:.]/g, \"-\");\n}\n\nfunction paddedIndex(value: number, total: number): string {\n const width = Math.max(2, String(total - 1).length);\n return String(value).padStart(width, \"0\");\n}\n\nfunction extensionFromMimeType(mimeType: string): string {\n if (mimeType === \"image/png\") return \".png\";\n if (mimeType === \"image/webp\") return \".webp\";\n return \".jpg\";\n}\n\n/**\n * When `VISUAL_AI_DEBUG_FRAMES=true|1`, persist sampled video frames to disk\n * for offline inspection. Frames are written to a per-call subdirectory under\n * `VISUAL_AI_DEBUG_FRAMES_DIR` (or `./visual-ai-debug-frames` if unset).\n *\n * Best-effort: any failure is logged to stderr and swallowed so debug-only\n * disk problems never break the actual provider call.\n */\nexport async function saveDebugFrames(\n frames: readonly Frame[],\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string | undefined> {\n if (!isEnabled(env)) return undefined;\n if (frames.length === 0) return undefined;\n\n const baseDir = env[DEBUG_FRAMES_DIR_ENV]?.trim() || DEFAULT_DIR_NAME;\n const runDir = resolve(baseDir, `${timestampSlug(new Date())}-${randomBytes(3).toString(\"hex\")}`);\n\n try {\n await mkdir(runDir, { recursive: true });\n await Promise.all(\n frames.map((frame) => {\n const idx = paddedIndex(frame.index, frames.length);\n const ts = frame.timestampSeconds.toFixed(2);\n const ext = extensionFromMimeType(frame.mimeType);\n const filename = `frame-${idx}-t${ts}s${ext}`;\n return writeFile(join(runDir, filename), frame.data);\n }),\n );\n } catch (err) {\n process.stderr.write(\n `[visual-ai-assertions] warning: failed to save debug frames to ${runDir}: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n return undefined;\n }\n\n process.stderr.write(\n `[visual-ai-assertions] Saved ${frames.length} debug frame(s) to ${runDir}\\n`,\n );\n return runDir;\n}\n","import sharp from \"sharp\";\nimport { VisualAIVideoError } from \"../errors.js\";\nimport type { Frame, FrameDedupeOptions } from \"../types.js\";\n\n/**\n * Longer edge of the grayscale thumbnail frames are compared on. 1568x880\n * becomes 256x144 (36,864 px): a 300x60 toast still covers ~490 px, while a\n * 2x16 text caret shrinks to ~3 px.\n */\nconst DEDUPE_THUMBNAIL_EDGE = 256;\n\n/**\n * Per-pixel gray-level delta (out of 255) a thumbnail pixel must exceed to\n * count as changed. Sits above JPEG `-q:v 3` and codec re-quantisation noise,\n * which stays under ~10 levels once averaged down to thumbnail size.\n */\nconst DEDUPE_PIXEL_TOLERANCE = 24;\n\n/**\n * Default fraction of thumbnail pixels that must change for a frame to be kept.\n * 0.1% is ~37 px of a 256x144 thumbnail: a 37x37 px region on a 1568x880 frame.\n */\nexport const DEFAULT_DEDUPE_THRESHOLD = 0.001;\n\nexport interface ResolvedDedupeOptions {\n readonly enabled: boolean;\n readonly threshold: number;\n}\n\n/** Grayscale thumbnail used to compare two frames. */\nexport interface FrameSignature {\n readonly width: number;\n readonly height: number;\n /** One byte per pixel, row-major. */\n readonly pixels: Buffer;\n}\n\n/**\n * Normalizes a `dedupe` option into an explicit `{ enabled, threshold }` pair,\n * throwing `VisualAIVideoError` for a threshold outside `(0, 1]`. Call this\n * before any expensive work so an invalid option fails fast.\n */\nexport function resolveDedupeOptions(raw: FrameDedupeOptions | undefined): ResolvedDedupeOptions {\n if (raw === undefined || raw === true) {\n return { enabled: true, threshold: DEFAULT_DEDUPE_THRESHOLD };\n }\n if (raw === false) {\n return { enabled: false, threshold: DEFAULT_DEDUPE_THRESHOLD };\n }\n const threshold = raw.threshold ?? DEFAULT_DEDUPE_THRESHOLD;\n if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {\n throw new VisualAIVideoError(\n `Invalid dedupe threshold: ${String(threshold)}. Must be a finite number in (0, 1].`,\n );\n }\n return { enabled: true, threshold };\n}\n\n/**\n * Decodes a frame into a small single-channel grayscale thumbnail. Alpha is\n * flattened onto white first so RGBA inputs produce one channel, not two.\n */\nexport async function frameSignature(frame: Frame): Promise<FrameSignature> {\n try {\n const { data, info } = await sharp(frame.data)\n .flatten({ background: { r: 255, g: 255, b: 255 } })\n .greyscale()\n .resize(DEDUPE_THUMBNAIL_EDGE, DEDUPE_THUMBNAIL_EDGE, {\n fit: \"inside\",\n withoutEnlargement: true,\n })\n .raw()\n .toBuffer({ resolveWithObject: true });\n return { width: info.width, height: info.height, pixels: data };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new VisualAIVideoError(\n `Failed to decode frame ${frame.index} (${frame.timestampSeconds.toFixed(2)}s) ` +\n `for change detection: ${reason}`,\n );\n }\n}\n\n/** Fraction of pixels whose gray level differs by more than the tolerance. */\nfunction changedFraction(a: FrameSignature, b: FrameSignature): number {\n if (a.width !== b.width || a.height !== b.height) {\n return 1;\n }\n let changed = 0;\n for (let i = 0; i < a.pixels.length; i++) {\n if (Math.abs((a.pixels[i] ?? 0) - (b.pixels[i] ?? 0)) > DEDUPE_PIXEL_TOLERANCE) {\n changed++;\n }\n }\n return changed / a.pixels.length;\n}\n\n/**\n * Returns `frame` with the given index. A new object is built field by field\n * so the source's lazy `base64` getter is forwarded rather than evaluated, as\n * object spread would do.\n */\nfunction reindex(frame: Frame, index: number): Frame {\n if (frame.index === index) {\n return frame;\n }\n return {\n data: frame.data,\n mimeType: frame.mimeType,\n get base64(): string {\n return frame.base64;\n },\n timestampSeconds: frame.timestampSeconds,\n index,\n };\n}\n\n/**\n * Drops frames that did not visibly change from the most recently kept frame.\n * The first frame is always kept; the last is not treated specially. Surviving\n * frames are re-indexed contiguously so `frameReferences` and the timeline\n * prompt line up with `frames.timestampsSeconds`.\n *\n * @throws {VisualAIVideoError} For an invalid threshold or an undecodable frame.\n */\nexport async function dedupeFrames(\n frames: readonly Frame[],\n options: FrameDedupeOptions | undefined,\n): Promise<{ frames: Frame[]; dropped: number }> {\n const { enabled, threshold } = resolveDedupeOptions(options);\n if (!enabled || frames.length < 2) {\n return { frames: [...frames], dropped: 0 };\n }\n\n const signed = await Promise.all(\n frames.map(async (frame) => ({ frame, signature: await frameSignature(frame) })),\n );\n\n const [first, ...rest] = signed;\n if (first === undefined) {\n return { frames: [], dropped: 0 };\n }\n\n const kept: Frame[] = [first.frame];\n let lastKept = first.signature;\n for (const { frame, signature } of rest) {\n if (changedFraction(lastKept, signature) >= threshold) {\n kept.push(frame);\n lastKept = signature;\n }\n }\n\n return {\n frames: kept.map((frame, index) => reindex(frame, index)),\n dropped: frames.length - kept.length,\n };\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;\n/** Default frame-sampling rate, shared by video sampling and pre-sampled frame inputs. */\nexport const DEFAULT_FPS = 1;\nconst DEFAULT_MAX_FRAMES = 10;\nconst DEFAULT_MAX_DURATION_SECONDS = 10;\n/** Hard upper bound on caller-supplied frame counts to keep memory bounded. */\nexport const 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 \"Could not load fluent-ffmpeg. It ships as a dependency of visual-ai-assertions, \" +\n \"so this usually means the install was pruned or the platform-specific binary is unavailable. \" +\n \"Reinstall the package or run: pnpm add fluent-ffmpeg @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe\",\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 interface ResolvedVideoSamplingOptions {\n readonly fps: number;\n readonly maxFrames: number;\n readonly maxDurationSeconds: number;\n}\n\n/**\n * Applies defaults to the numeric sampling options and validates them, throwing\n * `VisualAIVideoError` for anything out of range. Shared by frame extraction\n * and native delivery so both paths reject the same inputs before any I/O.\n */\nexport function resolveVideoSamplingOptions(\n options: VideoSamplingOptions = {},\n): ResolvedVideoSamplingOptions {\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 return { fps, maxFrames, maxDurationSeconds };\n}\n\n/** Throws `VisualAIVideoError` when a probed duration exceeds the configured limit. */\nexport function assertDurationWithinLimit(\n durationSeconds: number,\n maxDurationSeconds: number,\n): void {\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\nexport async function extractFrames(\n videoPath: string,\n options: VideoSamplingOptions = {},\n maxDimension: number = FRAME_MAX_DIMENSION,\n): Promise<{ frames: Frame[]; durationSeconds: number }> {\n const { fps, maxFrames, maxDurationSeconds } = resolveVideoSamplingOptions(options);\n\n const ffmpeg = await loadFfmpegFactory();\n const durationSeconds = await probeDurationSeconds(videoPath);\n assertDurationWithinLimit(durationSeconds, maxDurationSeconds);\n\n const outputDir = await mkdtemp(join(tmpdir(), \"visual-ai-frames-\"));\n try {\n // Constrain the longer edge to maxDimension for both portrait and landscape inputs.\n const filter =\n `fps=${fps},` +\n `scale='if(gt(iw,ih),min(${maxDimension},iw),-2)':` +\n `'if(gt(iw,ih),-2,min(${maxDimension},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 { z } from \"zod\";\nimport type {\n AccessibilityCheckName,\n ContentCheckName,\n ImageDetailLevel,\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 /**\n * Prompt tokens served from the provider's cache, when reported. Informational\n * only — `estimatedCost` does not apply a cache discount, because providers\n * differ on whether these are counted inside `inputTokens` (OpenAI, OpenRouter,\n * Google) or billed as a separate bucket alongside it (Anthropic).\n */\n cachedInputTokens: z.number().optional(),\n /** Cost in USD from the library's local pricing table (inputTokens/outputTokens × per-model rates). */\n estimatedCost: z.number().optional(),\n /** Actual cost in USD reported by the provider itself, when available (OpenRouter). Authoritative over `estimatedCost`. */\n reportedCost: 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 /** Number of frames actually sent to the model (after unchanged frames were dropped). */\n count: number;\n /** Timestamp (seconds, from the start of the clip) of each sent frame, in order. */\n timestampsSeconds: number[];\n /** Total duration of the source video in seconds. */\n durationSeconds: number;\n /**\n * Number of sampled frames dropped because they did not visibly change from\n * the preceding kept frame. `0` when dedupe is disabled or nothing was dropped.\n * See `FrameDedupeOptions`.\n */\n droppedUnchanged: number;\n}\n/**\n * Metadata describing a video that was delivered to the model natively (as the\n * video itself rather than sampled frames). Populated client-side.\n */\nexport interface NativeVideoMetadata {\n /** Total duration of the source video in seconds. */\n durationSeconds: number;\n /** Sampling rate requested from the provider, in frames per second. */\n fps: number;\n /** MIME type the video was sent as. */\n mimeType: SupportedVideoMimeType;\n /** Whether the bytes went inline in the request or through the provider's file upload API. */\n delivery: \"inline\" | \"file\";\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 sampled into frames. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n /** Present only when the input was a video delivered natively to the provider. */\n video?: NativeVideoMetadata;\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 * Nullable because providers with strict structured-output schemas (e.g. OpenAI)\n * must mark every field required and represent \"no value\" as `null` rather than\n * omitting the key, even for image inputs that were never asked to populate it.\n */\n frameReferences: z.array(z.number().int().nonnegative()).nullable().optional(),\n /**\n * For natively delivered video, the timestamps (seconds from the start of\n * the clip) the model relied on to answer. The native counterpart of\n * `frameReferences`. Nullable for the same strict-schema reason.\n */\n timestampReferences: z.array(z.number().nonnegative()).nullable().optional(),\n usage: UsageInfoSchema.optional(),\n});\n/** Result returned by `ask()`. */\nexport type AskResult = Omit<\n z.infer<typeof AskResultSchema>,\n \"frameReferences\" | \"timestampReferences\"\n> & {\n /** Present only when the input was a video sampled into frames. Indices into `frames.timestampsSeconds`. */\n frameReferences?: number[];\n /** Present only when the input was a video delivered natively. Seconds from the start of the clip. */\n timestampReferences?: number[];\n /** Present only when the input was a video sampled into frames. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n /** Present only when the input was a video delivered natively to the provider. */\n video?: NativeVideoMetadata;\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/**\n * A single pre-sampled frame with an explicit timestamp. Use this shape when\n * you want to control the timestamp the model sees; otherwise pass a bare\n * `ImageInput` and the timestamp is derived from `fps` and the frame's position.\n */\nexport interface TimestampedFrameInput {\n /** Image source for the frame (buffer, data URL, base64, file path, or URL). */\n image: ImageInput;\n /** Timestamp in seconds, from the start of the sequence. Derived from `fps` when omitted. */\n timestampSeconds?: number;\n}\n\n/**\n * Pre-sampled frames supplied directly instead of a video file. Useful when you\n * already have an array of screenshots and cannot (or would rather not) pass a\n * video — this path never loads ffmpeg. Passed to `check()` / `ask()` in place\n * of a `MediaInput`; downstream handling is identical to a sampled video.\n */\nexport interface FramesInput {\n /**\n * Ordered frames, earliest first. Each entry is an image source or a\n * `{ image, timestampSeconds }` pair. Must be non-empty and is capped at the\n * same per-request frame limit as video sampling (60).\n */\n frames: ReadonlyArray<ImageInput | TimestampedFrameInput>;\n /**\n * Frame rate used to derive timestamps for frames that don't carry their own\n * `timestampSeconds` (frame `i` maps to `i / fps` seconds). Default `1`.\n */\n fps?: number;\n /**\n * Drop frames that did not visibly change from the preceding kept frame\n * before sending to the provider. Default `true`. See `FrameDedupeOptions`.\n */\n dedupe?: FrameDedupeOptions;\n}\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\" | \"openrouter\";\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 /**\n * Longest-edge pixel cap for image inputs before they are sent to the\n * provider. Defaults to 1568. Raising it only helps providers that accept\n * higher-resolution requests (OpenAI with `imageDetail: \"high\"` scales into a\n * 2048px box); Anthropic re-downscales to ~1568px regardless.\n */\n maxImageDimension?: number;\n /**\n * Image-detail hint mapped per provider: OpenAI/OpenRouter `detail`, Google\n * `mediaResolution`. `\"auto\"` (default) sends no detail field. No effect on\n * Anthropic (Claude auto-downscales images).\n */\n imageDetail?: ImageDetailLevel;\n /**\n * Per-request timeout in milliseconds, forwarded to the provider SDK.\n * Omitted by default, so each SDK's own default applies (OpenAI and\n * OpenRouter 10 minutes, Google 1 minute, Anthropic per its own rules).\n *\n * Worth setting for heavy reasoning models: they can spend many minutes on a\n * single call, and the SDK default lets a request hang far longer than most\n * test suites should tolerate. Note that provider SDKs retry timed-out\n * requests, so total wall time can exceed this value by a multiple.\n */\n timeout?: number;\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 * Whether the screenshot shows the interface in its finished state. Defaults\n * to `true`, which is what a test asserting on a settled screen wants: an\n * element under a loading spinner, skeleton or error overlay is reported as\n * not properly visible, because the overlay says the content is not ready.\n *\n * Set to `false` when the screenshot was deliberately captured mid-load, so\n * loading chrome is expected rather than a defect. The finished-state rules\n * are then left out of the prompt entirely, and only presence and clipping\n * are judged. Note this omits the guidance rather than inverting it — to have\n * the model actively disregard loading indicators, say so in `instructions`.\n */\n finalState?: boolean;\n /**\n * Whether an element that is present but clearly badly rendered should fail.\n * Defaults to `false`: `elementsVisible()` is a presence check, and an element\n * counts as visible if it is there at all, whatever it looks like.\n *\n * Set to `true` to judge presentation as well. Text at contrast too low to\n * read, elements overlapping, an element out of alignment with its siblings,\n * or text cut off mid-word then fail, with the model told to say the element\n * is present before naming the defect. Opt in per assertion where a rendering\n * defect is the thing being checked: measured with the rule on and off, it\n * caught exactly the bullet naming a present-but-overlapping element and\n * nothing else, while making the better model noticeably flakier on plain\n * presence questions.\n *\n * Ignored by `elementsHidden()`, where the question is absence and a rendering\n * defect cannot change the answer.\n */\n requireCorrectRendering?: boolean;\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 * Drop sampled frames that did not visibly change from the preceding kept\n * frame before sending to the provider. Default `true`. See `FrameDedupeOptions`.\n * Only applies when frames are sampled; ignored for native delivery.\n */\n dedupe?: FrameDedupeOptions;\n /**\n * How the video reaches the model. Default `\"auto\"`. See `VideoDeliveryMode`.\n */\n mode?: VideoDeliveryMode;\n}\n\n/**\n * How a video input is delivered to the model.\n *\n * - `\"auto\"` (default): send the video itself when the provider accepts video\n * natively (Google models), otherwise sample frames with ffmpeg.\n * - `\"native\"`: always send the video itself. Throws `VisualAIConfigError`\n * when the provider has no native video support.\n * - `\"frames\"`: always sample frames with ffmpeg, whatever the provider.\n *\n * Native delivery still probes the duration and enforces `maxDurationSeconds`\n * before any provider call, and passes `fps` on as the provider's sampling\n * rate. `maxFrames` and `dedupe` apply to frame sampling only. Pre-sampled\n * `FramesInput` is always sent as frames.\n */\nexport type VideoDeliveryMode = \"auto\" | \"native\" | \"frames\";\n\n/**\n * Controls dropping of frames that did not visibly change from the previous\n * kept frame, so a static screen does not cost input tokens for every sample.\n *\n * - `true` (default): drop unchanged frames using the default threshold.\n * - `false`: send every sampled frame.\n * - `{ threshold }`: the fraction `(0, 1]` of a frame's pixels that must differ\n * from the last kept frame for it to count as changed. Default `0.001` (0.1%,\n * roughly a 37x37 px region on a 1568x880 frame). Lower it to keep smaller\n * changes; raise it to ignore more.\n *\n * Each frame is compared against the most recently *kept* frame, so gradual\n * drift accumulates and is eventually kept. The first frame is always kept.\n * Pixel-level compression noise and tiny flickers such as a blinking text\n * caret fall below the default threshold.\n */\nexport type FrameDedupeOptions = boolean | { threshold?: number };\n\n/** Internal normalized video passed to a provider driver's `sendVideoMessage`. */\nexport interface NormalizedVideo {\n readonly data: Buffer;\n readonly mimeType: SupportedVideoMimeType;\n /** Probed duration in seconds; already checked against `maxDurationSeconds`. */\n readonly durationSeconds: number;\n /** Sampling rate to request from the provider, in frames per second. */\n readonly fps: 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 });\n/**\n * What `ask()` requests from strict structured-output providers, one schema per\n * media kind so the schema never requires a field its prompt does not mention.\n * The image prompt describes only `summary` and `issues`; requiring the\n * video-only `frameReferences` as well made gpt-6.1-sol and gpt-6-astra stall\n * in endless whitespace after `issues` until `max_output_tokens` ran out (7 of\n * 20 and 3 of 12 calls, against 0 of 60 and 0 of 12 without it). Sampled-frame\n * video prompts ask for `frameReferences` and native video prompts for\n * `timestampReferences`, so each keeps only its own. Parsing still uses\n * `AskResponseSchema`, where both fields are optional.\n */\nexport const AskImageResponseSchema = AskResponseSchema.omit({\n frameReferences: true,\n timestampReferences: true,\n});\nexport const AskFramesResponseSchema = AskResponseSchema.omit({ timestampReferences: true });\nexport const AskNativeVideoResponseSchema = AskResponseSchema.omit({ frameReferences: true });\nexport const CompareResponseSchema = CompareResultSchema.omit({ usage: true });\n\n/**\n * Control characters other than tab, newline and carriage return. Nothing a\n * model says about a screenshot legitimately contains them, but two routes have\n * been observed emitting them anyway:\n *\n * - Kimi via OpenRouter writes them raw inside string literals, which strict\n * JSON.parse rejects outright, failing an otherwise good response.\n * - Muse Spark via OpenRouter mangles non-ASCII characters into escaped control\n * characters: \"−\" arrives as \\u0002 followed by the digits, \"é\" as \\u0000\n * followed by \"e9\". Those parse cleanly and then reach the caller as garbage\n * inside `statements[].statement`, where Zod cannot catch them because any\n * string validates.\n *\n * The original character cannot be recovered, so it is dropped. That leaves\n * \"20%\" rather than \"−20%\", which is wrong but printable, searchable, and safe\n * to log; the control byte was none of those.\n */\n// eslint-disable-next-line no-control-regex\nconst STRAY_CONTROL_CHARS = /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/g;\n\n/**\n * Parse JSON, retrying once with raw control characters replaced by spaces.\n * The retry is safe: between tokens a control character is whitespace, and\n * inside a string it stands in for text that was already unreadable.\n */\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown;\n } catch (first) {\n try {\n // eslint-disable-next-line no-control-regex\n return JSON.parse(text.replace(/[\\u0000-\\u001f]/g, \" \")) as unknown;\n } catch {\n throw first;\n }\n }\n}\n\n/** Drop stray control characters from every string in a parsed JSON value. */\nexport function stripControlCharacters<T>(value: T): T {\n if (typeof value === \"string\") return value.replace(STRAY_CONTROL_CHARS, \"\") as T;\n if (Array.isArray(value)) return value.map((item: unknown) => stripControlCharacters(item)) as T;\n if (value !== null && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n out[key] = stripControlCharacters(item);\n }\n return out as T;\n }\n return value;\n}\n\nfunction parseResponse<T>(raw: string, schema: z.ZodType<T>): T {\n let parsed: unknown;\n try {\n parsed = stripControlCharacters(parseJson(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 const result = parseResponse(raw, AskResponseSchema);\n return {\n ...result,\n frameReferences: result.frameReferences ?? undefined,\n timestampReferences: result.timestampReferences ?? undefined,\n };\n}\n\nexport function parseCompareResponse(raw: string): Omit<CompareResult, \"usage\"> {\n return parseResponse(raw, CompareResponseSchema);\n}\n","import { 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 FramesInput,\n ImageInput,\n LayoutOptions,\n MediaInput,\n NativeVideoMetadata,\n NormalizedImage,\n NormalizedVideo,\n PageLoadOptions,\n ProviderName,\n VideoFramesMetadata,\n VideoSamplingOptions,\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 { OpenRouterDriver } from \"../providers/openrouter.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport {\n debugLog,\n processUsage,\n timedSendMessage,\n timedSendVideoMessage,\n withErrorDebug,\n} from \"./debug.js\";\nimport { DIFF_ALLOWED_MODELS, generateAiDiff } from \"./diff.js\";\nimport { normalizeImage } from \"./image.js\";\nimport { isFramesInput, isVideoInput, normalizeMedia, type NormalizedMedia } from \"./media.js\";\nimport {\n buildAskPrompt,\n buildCheckPrompt,\n buildComparePrompt,\n type MediaContext,\n} from \"./prompt.js\";\nimport {\n AskFramesResponseSchema,\n AskImageResponseSchema,\n AskNativeVideoResponseSchema,\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, base64, Buffer) and statements pass if they\n * are true at any point, with each statement result carrying the timestamp\n * where it matched. On providers that accept video natively (Google models)\n * the video itself is sent and the result's `video` metadata describes the\n * delivery; elsewhere the client samples frames with ffmpeg and the `frames`\n * metadata reports which timestamps the model saw. Control this with\n * `video.mode`. Pass a `FramesInput` (`{ frames, fps? }`) to supply\n * pre-sampled frames directly — handled identically to a sampled timeline but\n * without loading ffmpeg.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string, or a `FramesInput` of pre-sampled frames.\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 | FramesInput,\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 analyzed as a chronological timeline. On providers that\n * accept video natively (Google models) the video itself is sent and the\n * result's `timestampReferences` array surfaces the moments the model relied\n * on; elsewhere frames are sampled with ffmpeg and `frameReferences` indexes\n * into `frames.timestampsSeconds`. Control this with `video.mode`. Pass a\n * `FramesInput` (`{ frames, fps? }`) to supply pre-sampled frames directly —\n * handled identically to a sampled timeline but without loading ffmpeg.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string, or a `FramesInput` of pre-sampled frames.\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 | FramesInput, 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 flash models (`DIFF_ALLOWED_MODELS`) generate an annotated diff image\n * by default; pass `{ diffImage: false }` to opt out. Flash-Lite and Pro tiers\n * produce none even when asked.\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 openrouter: (config) => new OpenRouterDriver(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 askSchemaOptionsByMedia = {\n image: toSchemaOptions(AskImageResponseSchema),\n video: toSchemaOptions(AskFramesResponseSchema),\n \"native-video\": toSchemaOptions(AskNativeVideoResponseSchema),\n} as const satisfies Record<MediaContext[\"kind\"], SendMessageOptions>;\nconst compareSchemaOptions = toSchemaOptions(CompareResponseSchema);\n\n/** Media-derived fields spread onto a `check()` / `ask()` result. */\ninterface MediaResultMetadata {\n frames?: VideoFramesMetadata;\n video?: NativeVideoMetadata;\n}\n\ntype MediaDispatch =\n | {\n kind: \"images\";\n images: NormalizedImage[];\n mediaContext: MediaContext;\n frames: VideoFramesMetadata | undefined;\n }\n | { kind: \"native-video\"; video: NormalizedVideo; mediaContext: MediaContext };\n\nfunction mediaToProviderInputs(media: NormalizedMedia): MediaDispatch {\n if (media.kind === \"image\") {\n return {\n kind: \"images\",\n images: [media.image],\n mediaContext: { kind: \"image\" },\n frames: undefined,\n };\n }\n if (media.kind === \"native-video\") {\n return {\n kind: \"native-video\",\n video: media.video,\n mediaContext: { kind: \"native-video\", durationSeconds: media.video.durationSeconds },\n };\n }\n const timestamps = media.frames.map((f) => f.timestampSeconds);\n return {\n kind: \"images\",\n images: media.frames,\n mediaContext: {\n kind: \"video\",\n frameTimestamps: timestamps,\n durationSeconds: media.durationSeconds,\n droppedUnchanged: media.droppedUnchanged,\n },\n frames: {\n count: media.frames.length,\n timestampsSeconds: timestamps,\n durationSeconds: media.durationSeconds,\n droppedUnchanged: media.droppedUnchanged,\n },\n };\n}\n\n/** Sends the dispatched media through the matching driver method and returns the metadata for the result. */\nasync function sendMedia(\n driver: ProviderDriver,\n dispatch: MediaDispatch,\n prompt: string,\n options: SendMessageOptions,\n): Promise<{\n response: RawProviderResponse & { durationSeconds: number };\n metadata: MediaResultMetadata;\n}> {\n if (dispatch.kind === \"native-video\") {\n const response = await timedSendVideoMessage(driver, dispatch.video, prompt, options);\n const { durationSeconds, fps, mimeType } = dispatch.video;\n return {\n response,\n metadata: { video: { durationSeconds, fps, mimeType, delivery: response.delivery } },\n };\n }\n const response = await timedSendMessage(driver, dispatch.images, prompt, options);\n return { response, metadata: dispatch.frames ? { frames: dispatch.frames } : {} };\n}\n\n/**\n * Decides whether a video input should be delivered natively, honouring\n * `video.mode`. `\"native\"` on a provider without support throws, but only\n * when the input really is a video, so images and pre-sampled frames pass\n * through untouched whatever the mode says.\n */\nfunction resolveNativeVideo(\n input: MediaInput | FramesInput,\n videoOptions: VideoSamplingOptions | undefined,\n driver: ProviderDriver,\n provider: ProviderName,\n): boolean {\n // Widened to string so a value outside the union still gets a clear error at runtime.\n const mode: string = videoOptions?.mode ?? \"auto\";\n if (mode !== \"auto\" && mode !== \"native\" && mode !== \"frames\") {\n throw new VisualAIConfigError(\n `Invalid video mode: ${mode}. Expected \"auto\", \"native\", or \"frames\".`,\n );\n }\n const supported = typeof driver.sendVideoMessage === \"function\";\n if (mode === \"frames\") return false;\n if (mode === \"auto\") return supported;\n if (!supported && !isFramesInput(input) && isVideoInput(input)) {\n throw new VisualAIConfigError(\n `Native video delivery is not supported by the \"${provider}\" provider. ` +\n `Use a Google model, or set video: { mode: \"frames\" } to sample frames instead.`,\n );\n }\n return true;\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 imageDetail: resolvedConfig.imageDetail,\n timeout: resolvedConfig.timeout,\n };\n const driver = createDriver(resolvedConfig.provider, driverConfig);\n // Longest-edge pixel cap applied to every image/frame before it reaches a driver.\n const maxImageDimension = resolvedConfig.maxImageDimension;\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, maxImageDimension);\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 nativeVideo = resolveNativeVideo(\n input,\n options?.video,\n driver,\n resolvedConfig.provider,\n );\n const media = await normalizeMedia(input, options?.video, maxImageDimension, nativeVideo);\n const dispatch = mediaToProviderInputs(media);\n const prompt = buildCheckPrompt(stmts, {\n instructions: options?.instructions,\n media: dispatch.mediaContext,\n });\n debugLog(resolvedConfig, \"check prompt\", prompt, \"prompt\");\n\n const { response, metadata } = await sendMedia(\n driver,\n dispatch,\n prompt,\n checkSchemaOptions,\n );\n debugLog(resolvedConfig, \"check response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n ...metadata,\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 nativeVideo = resolveNativeVideo(\n input,\n options?.video,\n driver,\n resolvedConfig.provider,\n );\n const media = await normalizeMedia(input, options?.video, maxImageDimension, nativeVideo);\n const dispatch = mediaToProviderInputs(media);\n const prompt = buildAskPrompt(userPrompt, {\n instructions: options?.instructions,\n media: dispatch.mediaContext,\n });\n debugLog(resolvedConfig, \"ask prompt\", prompt, \"prompt\");\n\n const { response, metadata } = await sendMedia(\n driver,\n dispatch,\n prompt,\n askSchemaOptionsByMedia[dispatch.mediaContext.kind],\n );\n debugLog(resolvedConfig, \"ask response\", response.text, \"response\");\n\n const result = parseAskResponse(response.text);\n return {\n ...result,\n ...metadata,\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([\n normalizeImage(imageA, maxImageDimension),\n normalizeImage(imageB, maxImageDimension),\n ]);\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 // Every Gemini flash model returns annotated diffs via code execution, so\n // the auto-trigger follows the allowlist rather than naming one model.\n // Flash-Lite and Pro tiers are absent from it and so never auto-enable.\n const supportsAnnotatedDiff =\n resolvedConfig.provider === \"google\" && DIFF_ALLOWED_MODELS.has(resolvedConfig.model);\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, maxImageDimension);\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, maxImageDimension);\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, maxImageDimension);\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, maxImageDimension);\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","resolve","detail","sharp","width","height","sharp","readFile","sharp","sharp","readFile","writeFile","extname","join","extname","buf","mimeType","join","writeFile","resolve","readFile","readFile","response","header"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/core/prompt.ts","../src/templates/elements-visibility.ts","../src/constants.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/providers/openrouter.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/media.ts","../src/core/debug-frames.ts","../src/core/frame-dedupe.ts","../src/core/video.ts","../src/types.ts","../src/core/response.ts","../src/core/client.ts","../src/format.ts"],"sourcesContent":["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\n/**\n * Video check schema. The wording differs only in what the model is looking\n * at: discrete sampled frames, or a continuous clip it samples itself.\n */\nfunction buildCheckOutputSchemaVideo(unit: \"frame\" | \"moment\"): string {\n const anyPoint = unit === \"frame\" ? \"ANY frame of the timeline\" : \"ANY moment of the video\";\n const bestPoint =\n unit === \"frame\"\n ? \"the timestamp of the frame that most clearly demonstrates it\"\n : \"the timestamp of the moment that most clearly demonstrates it\";\n const citing = unit === \"frame\" ? \"citing frame timestamps\" : \"citing timestamps\";\n const exampleWhere = unit === \"frame\" ? \"at the 3.5s frame\" : \"at 3.5s\";\n return `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 ${anyPoint}, unless the wording explicitly says otherwise (e.g. \"throughout\", \"at all times\")\n3. For each statement that passes, set \"timestampSeconds\" to ${bestPoint} (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} 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 ${exampleWhere}\", \"confidence\": \"high\", \"timestampSeconds\": 3.5 }\n ]\n}\n${JSON_INSTRUCTIONS}`;\n}\n\nconst CHECK_OUTPUT_SCHEMA_VIDEO = buildCheckOutputSchemaVideo(\"frame\");\nconst CHECK_OUTPUT_SCHEMA_NATIVE_VIDEO = buildCheckOutputSchemaVideo(\"moment\");\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 ASK_OUTPUT_SCHEMA_NATIVE_VIDEO = `Respond with a JSON object matching this exact structure:\n{\n \"summary\": string, // high-level summary of what happens across the video\n \"issues\": [...], // list of issues/findings, can be empty\n \"timestampReferences\": number[] // seconds from the start of the clip of the moments the answer relies on (in order)\n}\n${ISSUE_SCHEMA_INSTRUCTIONS}\n\nPrioritize issues by severity (critical / major / minor) as for image input.\nCite timestamps in \"timestampReferences\" 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\nconst DEFAULT_CHECK_ROLE_NATIVE_VIDEO =\n \"You are a visual QA assistant. Evaluate the provided video recording precisely and objectively, treating it as a chronological timeline.\";\n\nconst DEFAULT_ASK_ROLE_NATIVE_VIDEO =\n \"You are a visual QA assistant. Analyze the provided video recording 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 | {\n kind: \"video\";\n frameTimestamps: readonly number[];\n durationSeconds: number;\n /** Sampled frames omitted because they matched the preceding kept frame. */\n droppedUnchanged?: number;\n }\n | {\n /** The video itself was attached; the provider samples it. */\n kind: \"native-video\";\n durationSeconds: number;\n };\n\nfunction buildNativeVideoSection(durationSeconds: number): string {\n return `Video recording:\n- Total duration: ${durationSeconds.toFixed(2)}s\n\nThe attached file is the complete video recording. Treat it as a chronological timeline and refer to moments by timestamp (seconds from the start of the clip) where helpful.`;\n}\n\nfunction buildVideoTimelineSection(\n frameTimestamps: readonly number[],\n durationSeconds: number,\n droppedUnchanged = 0,\n): string {\n const formatted = frameTimestamps.map((t, i) => ` ${i}: ${t.toFixed(2)}s`).join(\"\\n\");\n const attached = frameTimestamps.length;\n const sampledLine =\n droppedUnchanged > 0\n ? `- ${attached + droppedUnchanged} frames sampled (in chronological order); ${droppedUnchanged} ` +\n `${droppedUnchanged === 1 ? \"was\" : \"were\"} dropped because ` +\n `${droppedUnchanged === 1 ? \"it\" : \"they\"} did not visibly change from the preceding kept frame, ` +\n `so ${attached} ${attached === 1 ? \"image is\" : \"images are\"} attached`\n : `- ${attached} frames sampled (in chronological order)`;\n const droppedGuidance =\n droppedUnchanged > 0\n ? ` Frames sampled between two consecutive listed timestamps looked the same as the earlier listed frame, and frames sampled after the last listed timestamp looked the same as the last attached image until the clip ended at ${durationSeconds.toFixed(2)}s.`\n : \"\";\n return `Video timeline:\n- Total duration: ${durationSeconds.toFixed(2)}s\n${sampledLine}\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.${droppedGuidance}`;\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 =\n media?.kind === \"video\"\n ? DEFAULT_CHECK_ROLE_VIDEO\n : media?.kind === \"native-video\"\n ? DEFAULT_CHECK_ROLE_NATIVE_VIDEO\n : DEFAULT_CHECK_ROLE;\n\n const sections = [options?.role ?? defaultRole];\n\n if (media?.kind === \"video\") {\n sections.push(\n buildVideoTimelineSection(\n media.frameTimestamps,\n media.durationSeconds,\n media.droppedUnchanged,\n ),\n );\n } else if (media?.kind === \"native-video\") {\n sections.push(buildNativeVideoSection(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(\n media?.kind === \"video\"\n ? CHECK_OUTPUT_SCHEMA_VIDEO\n : media?.kind === \"native-video\"\n ? CHECK_OUTPUT_SCHEMA_NATIVE_VIDEO\n : CHECK_OUTPUT_SCHEMA_IMAGE,\n );\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 = [\n media?.kind === \"video\"\n ? DEFAULT_ASK_ROLE_VIDEO\n : media?.kind === \"native-video\"\n ? DEFAULT_ASK_ROLE_NATIVE_VIDEO\n : DEFAULT_ASK_ROLE,\n ];\n\n if (media?.kind === \"video\") {\n sections.push(\n buildVideoTimelineSection(\n media.frameTimestamps,\n media.durationSeconds,\n media.droppedUnchanged,\n ),\n );\n } else if (media?.kind === \"native-video\") {\n sections.push(buildNativeVideoSection(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(\n media?.kind === \"video\"\n ? ASK_OUTPUT_SCHEMA_VIDEO\n : media?.kind === \"native-video\"\n ? ASK_OUTPUT_SCHEMA_NATIVE_VIDEO\n : ASK_OUTPUT_SCHEMA_IMAGE,\n );\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_HIDDEN_ROLE =\n \"Check whether specific UI elements are absent or hidden in this screenshot.\";\n\n/** \"a and b\" for two clauses, \"a, b, and c\" for more. */\nfunction joinClauses(clauses: readonly string[]): string {\n if (clauses.length <= 1) return clauses[0] ?? \"\";\n if (clauses.length === 2) return `${clauses[0]} and ${clauses[1]}`;\n return `${clauses.slice(0, -1).join(\", \")}, and ${clauses[clauses.length - 1]}`;\n}\n\n/**\n * The role names exactly what the enabled rules judge, so the headline never\n * promises more or less than the instructions below it.\n */\nfunction visibleRole(finalState: boolean, requireCorrectRendering: boolean): string {\n const clauses = [\"present\", \"properly visible\"];\n if (requireCorrectRendering) clauses.push(\"correctly rendered\");\n if (finalState) clauses.push(\"in their finished state\");\n return `Check whether specific UI elements are ${joinClauses(clauses)} in this screenshot.`;\n}\n\n/**\n * Being cut off at an edge means two opposite things, and the difference is the\n * judgement a human tester makes without thinking about it.\n *\n * A card peeking past the end of a carousel is not a defect: the peek is how\n * the interface advertises that the row scrolls, and the user reaches the card\n * normally. Failing that check would report a bug that does not exist, which is\n * what a naive \"partial means fail\" rule does to every scrollable row.\n *\n * A control sliced by the status bar or the home indicator is a defect: no\n * amount of scrolling brings it into view.\n *\n * A single screenshot does not record whether a surface scrolls, so the model\n * has to infer it from layout convention, the same way a person does.\n *\n * Something drawn on top of an element splits the same way, and the test is not\n * how much it covers. A price pill or a favourite icon coexists with finished\n * content, and on a typical card those cover more of the image than a spinner\n * does. A spinner covering less of it still means something different: it\n * asserts the element is not ready. So the question is what the overlay says\n * about the element's state, not how much of it you can still see.\n */\nconst ELEMENTS_VISIBLE_CLIPPING_RULES: readonly string[] = [\n \"When an element is partly rendered but cut off at an edge, decide whether ordinary scrolling would bring it fully into view. For example, a card peeking past the end of a horizontal carousel, a filter chip in a row that continues past the screen edge, or a list item partly below the bottom of a scrolling feed is reachable that way, so the check for that element PASSES. Say in your reasoning that it is reached by scrolling.\",\n \"An element that scrolling cannot bring into view is NOT properly visible: one sliced by the screen edge itself, or cut off or overlapped by fixed chrome such as the status bar, a notch, a home indicator, a sticky header, or a fixed bottom navigation bar. That is a layout fault, so the check for that element FAILS. Describe the clipping in your reasoning.\",\n \"The scrolling allowance above applies only to elements that are at least partly rendered. If no part of an element is on screen, the check for that element FAILS: do not infer that it exists below the fold. Judge only what this screenshot actually shows.\",\n];\n\n/**\n * Dropped when the caller passes `finalState: false`, because a screenshot\n * captured mid-load is expected to carry loading chrome. Everything else still\n * applies: whether an element is present, and whether it is clipped or covered,\n * does not depend on the interface having settled.\n */\nconst ELEMENTS_VISIBLE_FINAL_STATE_RULE =\n \"Judge each element in its finished, presented state. Things a design draws on top of an element — a badge, a favourite icon, a duration or price pill, a gradient scrim — coexist with finished content and leave it visible. An overlay that says the element is NOT ready — a loading spinner, a skeleton placeholder, a shimmer, a progress bar, an error or retry overlay — means the element is not properly visible even when you can still make out what sits underneath, so the check for that element FAILS. Name which of the two you are seeing in your reasoning.\";\n\n/**\n * Opt-in. Measured on an element-visibility benchmark (since removed) with the\n * rule on and off, five reps each: it caught the one bullet naming a present-but-overlapping element\n * (5/5 on both models, against 3/5 and 0/5 without it) and nothing else, while\n * tripling Gemini's flakiness on unrelated presence questions. A presence\n * assertion should not pay that by default; pass `requireCorrectRendering: true`\n * for the assertions where a rendering defect is the thing being checked.\n *\n * Open-ended defect hunting makes models over-report, which is why the closing\n * sentence draws the line at defects worth arguing about.\n *\n * Misalignment is named even though the models measured so far never report it;\n * a model that can see it should have the instruction available.\n */\nconst ELEMENTS_VISIBLE_CORRECT_RENDERING_RULE =\n \"An element that is present but clearly defective in how it is rendered is NOT properly visible: text at contrast too low to read, elements overlapping or colliding with one another, an element visibly out of alignment with the siblings it should line up with, or text cut off mid-word inside its own container. The check for that element FAILS. In your reasoning, say that the element is present and then name the defect. Only clear, unambiguous defects count: do not fail an element for tight spacing, stylistic choices, or anything you would have to argue for.\";\n\n/** A blocking overlay hides an element whether or not the interface has settled. */\nconst ELEMENTS_VISIBLE_OCCLUSION_RULE =\n \"An element a user could not read or use because a modal, dialog, cookie banner, toast or similar overlay covers it is NOT visible: the check for that element FAILS.\";\n\nfunction visibleRules(finalState: boolean, requireCorrectRendering: boolean): readonly string[] {\n return [\n ...ELEMENTS_VISIBLE_CLIPPING_RULES,\n ...(finalState ? [ELEMENTS_VISIBLE_FINAL_STATE_RULE] : []),\n ...(requireCorrectRendering ? [ELEMENTS_VISIBLE_CORRECT_RENDERING_RULE] : []),\n ELEMENTS_VISIBLE_OCCLUSION_RULE,\n ];\n}\n\nconst ELEMENTS_HIDDEN_BASE_RULES: readonly string[] = [\n \"An element that is rendered at all, even partly, is not hidden, so the check for that element FAILS. This includes one peeking past the edge of a scrollable row or feed, which the user reaches by scrolling normally. Note the partial visibility in your reasoning.\",\n \"An element that appears nowhere in this screenshot counts as hidden, even if the page might reveal it after scrolling. Judge only what this screenshot actually shows.\",\n];\n\n/** Mirrors the visible side: dropped when the caller says the screen is mid-load. */\nconst ELEMENTS_HIDDEN_FINAL_STATE_RULE =\n \"An element sitting under a loading spinner, skeleton, progress bar or error overlay is still rendered, so it is not hidden and the check for that element FAILS. It is not properly visible either; that is what the visible check is for.\";\n\nfunction hiddenRules(finalState: boolean): readonly string[] {\n return finalState\n ? [...ELEMENTS_HIDDEN_BASE_RULES, ELEMENTS_HIDDEN_FINAL_STATE_RULE]\n : ELEMENTS_HIDDEN_BASE_RULES;\n}\n\nexport function buildElementsVisibilityPrompt(\n elements: string[],\n visible: boolean,\n options?: ElementsVisibilityOptions,\n): string {\n // \"visible\" rather than \"fully visible\": an element reached by scrolling\n // counts, and a headline that said otherwise would contradict the edge rules.\n const statements = visible\n ? elements.map((el) => `The element \"${el}\" is visible on the page`)\n : elements.map((el) => `The element \"${el}\" is NOT visible on the page`);\n\n // Default: the screenshot shows a settled interface, which is what a test\n // asserting on a finished screen means. `finalState: false` drops the rules\n // that treat loading chrome as a defect.\n const finalState = options?.finalState ?? true;\n // Presence-only by default. `elementsHidden` asks about absence, so a\n // rendering defect cannot change its answer and the option is ignored there.\n const correctRendering = visible && (options?.requireCorrectRendering ?? false);\n const defaultRules = visible\n ? visibleRules(finalState, correctRendering)\n : hiddenRules(finalState);\n const instructions = options?.instructions\n ? [...defaultRules, ...options.instructions]\n : defaultRules;\n\n const role = visible ? visibleRole(finalState, correctRendering) : ELEMENTS_HIDDEN_ROLE;\n\n return buildCheckPrompt(statements, { role, instructions });\n}\n","import type { ProviderName } from \"./types.js\";\n\n// --- Reasoning effort constants ---\n\n/** Supported reasoning effort levels. */\n/**\n * Abstract reasoning-effort hint. Each provider driver maps it to its native\n * mechanism; levels a provider lacks clamp to its nearest tier.\n *\n * `minimal` is **not universally accepted**. OpenAI rejects it per-model with\n * HTTP 400 (\"Unsupported value: 'minimal' is not supported with the\n * '<model>' model\"), observed on `gpt-6-astra` and `gpt-6.1-sol`, whose\n * supported set is low/medium/high/xhigh/max, and on `gpt-6-sol` and\n * `gpt-6-luna`, which accept none/low/medium/high/xhigh/max. Gemini defines\n * the tier but some models (e.g. Gemini 3.1 Pro) reject it, so Google and\n * OpenRouter clamp it to `low` rather than send it. Use `minimal` only\n * against an OpenAI model known to accept it; `low` is the portable floor.\n */\nexport const ReasoningEffort = {\n MINIMAL: \"minimal\",\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// --- Image fidelity constants ---\n\n/**\n * Abstract image-detail hint. Each provider driver maps it to its native\n * mechanism (OpenAI/OpenRouter `detail`, Google `mediaResolution`); Anthropic\n * has no equivalent (Claude auto-downscales to ~1568px / 1.15MP regardless).\n * `\"auto\"` sends no detail field, preserving each provider's default.\n */\nexport const ImageDetail = {\n AUTO: \"auto\",\n LOW: \"low\",\n HIGH: \"high\",\n} as const;\n\n/** Union of valid image-detail values, derived from the ImageDetail constant. */\nexport type ImageDetailLevel = (typeof ImageDetail)[keyof typeof ImageDetail];\n\n/** Default image-detail hint: `\"auto\"` leaves each provider's own default in place. */\nexport const DEFAULT_IMAGE_DETAIL: ImageDetailLevel = ImageDetail.AUTO;\n\n/**\n * Longest-edge pixel cap applied when normalizing images before they reach a\n * provider. 1568 matches Anthropic's recommended max edge (images within a\n * 1568px box stay under Claude's ~1.15MP limit). Raising it only affects\n * providers that also accept a higher-resolution request (e.g. OpenAI at\n * `detail:\"high\"`, which scales into a 2048px box); Anthropic re-downscales.\n */\nexport const DEFAULT_MAX_IMAGE_DIMENSION = 1568;\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 OPENROUTER: \"openrouter\",\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 FABLE_5_1: \"claude-fable-5-1\",\n FABLE_5: \"claude-fable-5\",\n OPUS_5_5: \"claude-opus-5-5\",\n OPUS_5: \"claude-opus-5\",\n OPUS_4_8: \"claude-opus-4-8\",\n OPUS_4_7: \"claude-opus-4-7\",\n OPUS_4_6: \"claude-opus-4-6\",\n SONNET_5_5: \"claude-sonnet-5-5\",\n SONNET_5: \"claude-sonnet-5\",\n SONNET_4_6: \"claude-sonnet-4-6\",\n HAIKU_5_5: \"claude-haiku-5-5\",\n HAIKU_4_5: \"claude-haiku-4-5\",\n },\n OpenAI: {\n GPT_6_ASTRA: \"gpt-6-astra\",\n GPT_6_1_SOL: \"gpt-6.1-sol\",\n GPT_6_SOL: \"gpt-6-sol\",\n GPT_6_LUNA: \"gpt-6-luna\",\n GPT_5_6_SOL: \"gpt-5.6-sol\",\n GPT_5_6_TERRA: \"gpt-5.6-terra\",\n GPT_5_6_LUNA: \"gpt-5.6-luna\",\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_8_FLASH: \"gemini-3.8-flash\",\n GEMINI_3_7_FLASH: \"gemini-3.7-flash\",\n GEMINI_3_6_FLASH: \"gemini-3.6-flash\",\n GEMINI_3_5_FLASH: \"gemini-3.5-flash\",\n GEMINI_3_5_FLASH_LITE: \"gemini-3.5-flash-lite\",\n GEMINI_3_1_PRO_PREVIEW: \"gemini-3.1-pro-preview\",\n GEMINI_3_1_FLASH_LITE: \"gemini-3.1-flash-lite\",\n GEMINI_3_FLASH_PREVIEW: \"gemini-3-flash-preview\",\n },\n /**\n * Models routed through OpenRouter (https://openrouter.ai). Slugs always\n * carry a vendor prefix (`vendor/model`), which is how provider inference\n * recognizes them. All listed models accept image input.\n */\n OpenRouter: {\n MUSE_SPARK_1_3: \"meta/muse-spark-1.3\",\n GROK_4_7: \"x-ai/grok-4.7\",\n GROK_4_6: \"x-ai/grok-4.6\",\n GROK_4_5: \"x-ai/grok-4.5\",\n KIMI_K3: \"moonshotai/kimi-k3\",\n KIMI_K2_7_CODE: \"moonshotai/kimi-k2.7-code\",\n QWEN_3_8_MAX: \"qwen/qwen3.8-max\",\n QWEN_3_7_PLUS: \"qwen/qwen3.7-plus\",\n QWEN_3_6_FLASH: \"qwen/qwen3.6-flash\",\n GLM_5_3_FLASH: \"z-ai/glm-5.3-flash\",\n MIMO_V2_6_PRO: \"xiaomi/mimo-v2.6-pro\",\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 | (typeof Model.OpenRouter)[keyof typeof Model.OpenRouter];\n\n// --- Default model per provider ---\n\n/**\n * Default model selection used when a caller omits `config.model`.\n *\n * Two of these carry constraints the older defaults did not:\n * - `gpt-6.1-sol` rejects `reasoningEffort: \"minimal\"` (and `none`) with HTTP\n * 400; `low` is the floor. See the `ReasoningEffort` note above.\n * - `meta/muse-spark-1.3` is age-gated on OpenRouter and returns HTTP 403 until\n * the account completes the 18+ confirmation at\n * openrouter.ai/settings/preferences. It also reasons by default (~370-814\n * reasoning tokens per call) even with no effort configured, so cost per call\n * runs above what its headline rate suggests.\n */\nexport const DEFAULT_MODELS = {\n [Provider.ANTHROPIC]: Model.Anthropic.SONNET_5_5,\n [Provider.OPENAI]: Model.OpenAI.GPT_6_1_SOL,\n [Provider.GOOGLE]: Model.Google.GEMINI_3_8_FLASH,\n [Provider.OPENROUTER]: Model.OpenRouter.MUSE_SPARK_1_3,\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/**\n * Budget for `MODELS_REQUIRING_LARGE_OUTPUT_BUDGET`. OpenAI's reasoning guide\n * recommends reserving \"at least 25,000 tokens for reasoning and outputs\" when\n * starting out; this leaves headroom above that while still capping cost. The\n * listed models' longest observed calls stayed under 9000 output tokens, and at\n * their output prices a call that used the whole budget would cost at most\n * about $0.20 (qwen3.8-max, $6/MTok).\n */\nexport const OPENAI_HEAVY_REASONING_MAX_TOKENS = 32768;\n\n/**\n * Models that exhaust `DEFAULT_MAX_TOKENS` on reasoning at *every* effort\n * level, not just high/xhigh, and so return status \"incomplete\" with no\n * visible answer. They get `OPENAI_HEAVY_REASONING_MAX_TOKENS` by default\n * regardless of `reasoningEffort`; an explicit `maxTokens` still wins.\n *\n * `gpt-6-astra` was listed here after image `ask()` calls truncated at 4096\n * and even 16384. That turned out to be the image response schema requiring\n * the video-only `frameReferences` (see `AskImageResponseSchema`), not\n * reasoning: the model printed whitespace until the budget ran out. With that\n * fixed, Astra completed `ask()` and `check()` at 4096 and never exceeded 548\n * output tokens on the discovery bench, so it was removed.\n *\n * The Qwen entries were verified live at the 4096 default with that fix in\n * place. Unlike Astra they genuinely reason past it (4500-5400 reasoning tokens\n * on long calls, under 600 visible): at `medium`, qwen3.8-max truncated 5/8\n * `ask()` and 3/8 `check()` calls and qwen3.7-plus 5/8 `ask()`; with this\n * budget both completed 16/16. grok-4.7 reasons as long but never truncated,\n * since its upstream does not count reasoning against `max_tokens`.\n *\n * kimi-k2.7-code is deliberately absent. It truncates ~8% of calls at 4096,\n * but those are calls where it writes prose instead of JSON: at 32768 they\n * finish and fail to parse instead (~12%), so a larger budget only makes each\n * failure cost more.\n */\nexport const MODELS_REQUIRING_LARGE_OUTPUT_BUDGET: ReadonlySet<string> = new Set<string>([\n Model.OpenRouter.QWEN_3_8_MAX,\n Model.OpenRouter.QWEN_3_7_PLUS,\n]);\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 ...Object.values(Model.OpenRouter).map((m) => [m, Provider.OPENROUTER] 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 // Varies by upstream model; the driver sends no reasoning field unless configured.\n openrouter: \"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 /** Detects color choices likely to be indistinguishable to viewers with common color vision deficiencies */\n COLOR_BLINDNESS: \"color-blindness\",\n /** Detects information conveyed by color alone, without a non-color cue (icon, text, pattern, position) */\n COLOR_ALONE: \"color-alone\",\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 { 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 \"Purely decorative color (branding, backgrounds, gradients) is not a color-blindness or color-alone issue — only flag color usage that carries meaning a user must perceive to use the interface.\",\n \"Only evaluate color cues that are visible in the screenshot. Do not assume hover/focus state colors exist if they are not shown.\",\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 [Accessibility.COLOR_BLINDNESS]:\n \"Information conveyed by color remains distinguishable to viewers with common color vision deficiencies — status indicators, chart series, and other meaningful color pairings do not rely on hue combinations that are commonly confused (e.g., red/green, blue/purple)\",\n [Accessibility.COLOR_ALONE]:\n \"Information conveyed by color is also conveyed through at least one non-color cue — text labels, icons, shapes, patterns, underlines, or position accompany any color-based meaning (e.g., required fields, error states, chart legends, link styling)\",\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 { DEFAULT_MAX_TOKENS, 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, carried forward by\n// Fable 5.1, Fable 5, Opus 4.8, Opus 5, Opus 5.5, Sonnet 5, Sonnet 5.5, and Haiku 5.5.\n// Older Anthropic models (Opus 4.6, Sonnet 4.6) reject \"xhigh\" but accept\n// \"max\", which is why our xhigh maps to \"max\" on those models only.\nconst XHIGH_CAPABLE_MODELS: ReadonlySet<string> = new Set([\n Model.Anthropic.FABLE_5_1,\n Model.Anthropic.FABLE_5,\n Model.Anthropic.OPUS_5_5,\n Model.Anthropic.OPUS_5,\n Model.Anthropic.OPUS_4_8,\n Model.Anthropic.OPUS_4_7,\n Model.Anthropic.SONNET_5_5,\n Model.Anthropic.SONNET_5,\n Model.Anthropic.HAIKU_5_5,\n]);\n\nfunction mapEffort(level: ReasoningEffortLevel, model: string): string {\n // Anthropic's adaptive thinking has no \"minimal\" tier; clamp to its floor.\n if (level === \"minimal\") return \"low\";\n if (level !== \"xhigh\") return level;\n return XHIGH_CAPABLE_MODELS.has(model) ? \"xhigh\" : \"max\";\n}\n\n// Haiku 4.5 rejects `thinking: {type: \"adaptive\"}` — it only supports the older\n// budget-based extended thinking API, so effort maps to a token budget instead.\nconst BUDGET_THINKING_MODELS: ReadonlySet<string> = new Set([Model.Anthropic.HAIKU_4_5]);\n\nconst EFFORT_TO_BUDGET_TOKENS: Readonly<Record<ReasoningEffortLevel, number>> = {\n // 1024 is Anthropic's minimum thinking budget, so minimal and low coincide.\n minimal: 1024,\n low: 1024,\n medium: 4096,\n high: 8192,\n xhigh: 16384,\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: {\n input_tokens: number;\n output_tokens: number;\n /**\n * Prompt tokens served from Anthropic's cache. Reported in its own bucket\n * *alongside* `input_tokens` rather than inside it, unlike OpenAI/Google.\n */\n cache_read_input_tokens?: number;\n };\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 private timeout: ProviderConfig[\"timeout\"];\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 this.timeout = config.timeout;\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: {\n apiKey: string;\n timeout?: number;\n }) => AnthropicClient)({\n apiKey,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // Note: `imageDetail` (ProviderConfig) is intentionally not used here. Claude\n // has no detail/resolution parameter and auto-downscales images to ~1568px /\n // 1.15MP, so a higher-fidelity request would give no additional performance.\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 if (BUDGET_THINKING_MODELS.has(this.model)) {\n const budgetTokens = EFFORT_TO_BUDGET_TOKENS[this.reasoningEffort];\n requestParams.thinking = { type: \"enabled\", budget_tokens: budgetTokens };\n // Thinking tokens share the max_tokens budget and the API requires\n // budget_tokens < max_tokens, so leave room for the actual response.\n requestParams.max_tokens = Math.max(this.maxTokens, budgetTokens + DEFAULT_MAX_TOKENS);\n } else {\n requestParams.thinking = { type: \"adaptive\" };\n requestParams.output_config = {\n effort: mapEffort(this.reasoningEffort, this.model),\n };\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 ...(message.usage.cache_read_input_tokens !== undefined && {\n cachedInputTokens: message.usage.cache_read_input_tokens,\n }),\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, NormalizedVideo } from \"../types.js\";\nimport type { ImageDetailLevel, ReasoningEffortLevel } from \"../constants.js\";\nimport type {\n ImageGenerationOptions,\n ImageGenerationResponse,\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n RawVideoProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst DEFAULT_IMAGE_GEN_MODEL = \"gemini-2.5-flash-image\";\n\n/**\n * Gemini caps a whole inline request at 20 MB; videos up to this size go\n * inline as base64 and anything larger is uploaded through the Files API.\n */\nexport const GEMINI_INLINE_VIDEO_LIMIT_BYTES = 19 * 1024 * 1024;\nconst GEMINI_FILE_POLL_INTERVAL_MS = 2_000;\nconst GEMINI_FILE_POLL_TIMEOUT_MS = 5 * 60_000;\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 /** Cached subset of `promptTokenCount` (implicit or explicit context caching). */\n cachedContentTokenCount?: number;\n };\n}\n\n/** Minimal shape of a Gemini Files API record. */\ninterface GeminiFile {\n name?: string;\n uri?: string;\n mimeType?: string;\n state?: string;\n error?: { message?: string };\n}\n\ninterface GoogleClient {\n models: {\n generateContent(params: Record<string, unknown>): Promise<GoogleGenerateContentResponse>;\n };\n files: {\n upload(params: { file: Blob; config?: { mimeType?: string } }): Promise<GeminiFile>;\n get(params: { name: string }): Promise<GeminiFile>;\n delete(params: { name: string }): Promise<unknown>;\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 1:1 mapping so \"medium\" means native medium, matching the other providers.\n * (Until v0.17 this was shifted one level down — medium -> \"low\" — which left\n * Gemini models thinking far less than peers at the same configured effort.)\n * Google has no level above \"high\", so xhigh clamps; \"minimal\" is unused,\n * which also sidesteps models that reject it (e.g. Gemini 3.1 Pro).\n */\nconst GOOGLE_THINKING_LEVEL = {\n // Gemini does define a \"minimal\" thinking level, but some models reject it\n // (e.g. Gemini 3.1 Pro), so \"minimal\" clamps to \"low\" here as well.\n minimal: \"low\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"high\",\n} as const satisfies Record<ReasoningEffortLevel, string>;\n\n/**\n * Maps the abstract image-detail hint to Gemini's `mediaResolution` token-budget\n * tier (LOW 280 → HIGH 1120 tokens/image). \"auto\" is omitted so Gemini's own\n * default resolution applies. Gemini 3 also offers MEDIA_RESOLUTION_ULTRA_HIGH\n * (2240 tokens) if an even higher tier is ever wanted.\n */\nconst GOOGLE_MEDIA_RESOLUTION: Partial<Record<ImageDetailLevel, string>> = {\n low: \"MEDIA_RESOLUTION_LOW\",\n high: \"MEDIA_RESOLUTION_HIGH\",\n};\n\ninterface GeminiUsageMetadata {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n thoughtsTokenCount?: number;\n /** Cached subset of `promptTokenCount` (implicit or explicit context caching). */\n cachedContentTokenCount?: number;\n}\n\n/**\n * Normalize Gemini usage. Gemini reports the visible answer\n * (`candidatesTokenCount`) and the thinking trace (`thoughtsTokenCount`)\n * separately, but bills thinking at the output-token rate. We fold thinking into\n * `outputTokens` so the count matches what's billed (and matches OpenAI/OpenRouter,\n * whose output counts already include reasoning), and expose `reasoningTokens` as\n * the breakdown. Without this, cost estimates omit Gemini's thinking entirely.\n */\nfunction toGeminiUsage(um: GeminiUsageMetadata | undefined) {\n if (!um) return undefined;\n const thoughts = um.thoughtsTokenCount ?? 0;\n return {\n inputTokens: um.promptTokenCount ?? 0,\n outputTokens: (um.candidatesTokenCount ?? 0) + thoughts,\n ...(um.thoughtsTokenCount !== undefined && { reasoningTokens: um.thoughtsTokenCount }),\n ...(um.cachedContentTokenCount !== undefined && {\n cachedInputTokens: um.cachedContentTokenCount,\n }),\n };\n}\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 private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\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: {\n apiKey: string;\n httpOptions?: { timeout: number };\n }) => GoogleClient)({\n apiKey,\n ...(this.timeout !== undefined && { httpOptions: { timeout: this.timeout } }),\n });\n return this.client;\n }\n\n /** Request config shared by image and video messages. */\n private generationConfig(): Record<string, unknown> {\n return {\n responseMimeType: \"application/json\",\n maxOutputTokens: this.maxTokens,\n ...(this.reasoningEffort && {\n thinkingConfig: {\n thinkingLevel: GOOGLE_THINKING_LEVEL[this.reasoningEffort],\n },\n }),\n ...(this.imageDetail &&\n GOOGLE_MEDIA_RESOLUTION[this.imageDetail] && {\n mediaResolution: GOOGLE_MEDIA_RESOLUTION[this.imageDetail],\n }),\n };\n }\n\n /** Runs one generateContent call and normalizes finish reasons, text, and usage. */\n private async generate(client: GoogleClient, contents: unknown[]): Promise<RawProviderResponse> {\n try {\n const response = await client.models.generateContent({\n model: this.model,\n contents,\n config: this.generationConfig(),\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 return {\n text: response.text ?? \"\",\n usage: toGeminiUsage(response.usageMetadata),\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n async sendMessage(\n images: NormalizedImage[],\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawProviderResponse> {\n const client = await this.getClient();\n return this.generate(client, [...this.toGeminiParts(images), prompt]);\n }\n\n /**\n * Uploads a video through the Files API and waits until Gemini has finished\n * processing it. Returns the ACTIVE file record.\n */\n private async uploadVideo(client: GoogleClient, video: NormalizedVideo): Promise<GeminiFile> {\n try {\n const uploaded = await client.files.upload({\n file: new Blob([new Uint8Array(video.data)], { type: video.mimeType }),\n config: { mimeType: video.mimeType },\n });\n const name = uploaded.name;\n if (!name) {\n throw new VisualAIProviderError(\"Gemini Files API returned a file without a name.\");\n }\n const deadline = Date.now() + GEMINI_FILE_POLL_TIMEOUT_MS;\n let current = uploaded;\n while (current.state === \"PROCESSING\") {\n if (Date.now() > deadline) {\n throw new VisualAIProviderError(\n `Gemini file ${name} was still processing after ${GEMINI_FILE_POLL_TIMEOUT_MS}ms.`,\n );\n }\n await sleep(GEMINI_FILE_POLL_INTERVAL_MS);\n current = await client.files.get({ name });\n }\n if (current.state !== \"ACTIVE\") {\n throw new VisualAIProviderError(\n `Gemini file ${name} ended in state ${current.state ?? \"unknown\"}: ${current.error?.message ?? \"no error message\"}`,\n );\n }\n if (!current.uri) {\n throw new VisualAIProviderError(\"Gemini Files API returned an ACTIVE file without a URI.\");\n }\n return current;\n } catch (err) {\n if (err instanceof VisualAIProviderError) throw err;\n throw mapProviderError(err);\n }\n }\n\n /**\n * Sends the video bytes themselves. Gemini samples the clip server-side at\n * `video.fps` and, unlike sampled frames, also hears the audio track. Small\n * videos go inline; larger ones are uploaded via the Files API and deleted\n * again afterwards (they would expire on their own after 48 h).\n */\n async sendVideoMessage(\n video: NormalizedVideo,\n prompt: string,\n _options?: SendMessageOptions,\n ): Promise<RawVideoProviderResponse> {\n const client = await this.getClient();\n const videoMetadata = { fps: video.fps };\n\n if (video.data.byteLength <= GEMINI_INLINE_VIDEO_LIMIT_BYTES) {\n const part = {\n inlineData: { data: video.data.toString(\"base64\"), mimeType: video.mimeType },\n videoMetadata,\n };\n const response = await this.generate(client, [part, prompt]);\n return { ...response, delivery: \"inline\" };\n }\n\n const file = await this.uploadVideo(client, video);\n try {\n const part = {\n fileData: { fileUri: file.uri, mimeType: file.mimeType ?? video.mimeType },\n videoMetadata,\n };\n const response = await this.generate(client, [part, prompt]);\n return { ...response, delivery: \"file\" };\n } finally {\n if (file.name) {\n // Best-effort tidiness; never mask the original control flow.\n await client.files.delete({ name: file.name }).catch(() => undefined);\n }\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: toGeminiUsage(response.usageMetadata),\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 /** OpenAI caches prompt prefixes automatically above ~1024 tokens; this is\n * the cached subset of `input_tokens`, already discounted in billing. */\n input_tokens_details?: { cached_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 private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\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; timeout?: number }) => OpenAIClient)({\n apiKey,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // OpenAI `detail` scales high→2048px box (shortest side 768), low→512px.\n // Omitted for \"auto\" so the default request shape is unchanged.\n const detail = this.imageDetail && this.imageDetail !== \"auto\" ? this.imageDetail : undefined;\n const imageBlocks = images.map((img) => ({\n type: \"input_image\" as const,\n image_url: `data:${img.mimeType};base64,${img.base64}`,\n ...(detail ? { detail } : {}),\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 const cachedInputTokens = response.usage?.input_tokens_details?.cached_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 ...(cachedInputTokens !== undefined && { cachedInputTokens }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError) throw err;\n throw mapProviderError(err);\n }\n }\n}\n","import {\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 ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"./types.js\";\n\nconst OPENROUTER_BASE_URL = \"https://openrouter.ai/api/v1\";\n\n/**\n * OpenRouter normalizes reasoning across upstream vendors to low/medium/high;\n * there is no xhigh, so it clamps to high.\n */\nconst OPENROUTER_REASONING_EFFORT: Record<ReasoningEffortLevel, \"low\" | \"medium\" | \"high\"> = {\n // OpenRouter normalizes upstream vendors to low/medium/high only, so\n // \"minimal\" has no native equivalent and clamps to the floor.\n minimal: \"low\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"high\",\n};\n\n/** Minimal interface for the OpenAI SDK chat-completions surface used by this driver. */\ninterface OpenRouterCompletionResult {\n choices?: {\n message?: { content?: string | null };\n finish_reason?: string | null;\n }[];\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n /** Cached subset of `prompt_tokens`, when the upstream provider reports one. */\n prompt_tokens_details?: { cached_tokens?: number };\n /** Actual cost in USD, returned because the request sets `usage: { include: true }`. */\n cost?: number;\n };\n}\n\ninterface OpenRouterClient {\n chat: {\n completions: {\n create(params: Record<string, unknown>): Promise<OpenRouterCompletionResult>;\n };\n };\n}\n\n/**\n * Driver for models routed through OpenRouter (xAI, Moonshot, Qwen, ...).\n * Speaks the OpenAI chat-completions protocol via the `openai` SDK pointed at\n * the OpenRouter base URL, so no additional SDK dependency is required.\n */\nexport class OpenRouterDriver implements ProviderDriver {\n private client: OpenRouterClient | null;\n private model: string;\n private maxTokens: number;\n private apiKeyOrEnv: string | undefined;\n private reasoningEffort: ProviderConfig[\"reasoningEffort\"];\n private imageDetail: ProviderConfig[\"imageDetail\"];\n private timeout: ProviderConfig[\"timeout\"];\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 this.imageDetail = config.imageDetail;\n this.timeout = config.timeout;\n }\n\n private async getClient(): Promise<OpenRouterClient> {\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(\n \"OpenAI SDK not installed (required for the OpenRouter provider). Run: npm install openai\",\n );\n }\n\n const apiKey = this.apiKeyOrEnv ?? process.env.OPENROUTER_API_KEY;\n if (!apiKey) {\n throw new VisualAIAuthError(\n \"OpenRouter API key not found. Set OPENROUTER_API_KEY or pass apiKey in config.\",\n );\n }\n\n this.client = new (OpenAI as new (opts: {\n apiKey: string;\n baseURL: string;\n timeout?: number;\n }) => OpenRouterClient)({\n apiKey,\n baseURL: OPENROUTER_BASE_URL,\n ...(this.timeout !== undefined && { timeout: this.timeout }),\n });\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 // OpenAI-chat-completions `detail`, passed through to the upstream model.\n // Omitted for \"auto\" so the default request shape is unchanged.\n const detail = this.imageDetail && this.imageDetail !== \"auto\" ? this.imageDetail : undefined;\n const imageParts = images.map((img) => ({\n type: \"image_url\" as const,\n image_url: {\n url: `data:${img.mimeType};base64,${img.base64}`,\n ...(detail ? { detail } : {}),\n },\n }));\n\n try {\n const responseFormat = options?.responseSchema\n ? {\n type: \"json_schema\" as const,\n json_schema: {\n name: \"visual_ai_response\",\n strict: true,\n schema: options.responseSchema,\n },\n }\n : { type: \"json_object\" as const };\n\n const requestParams: Record<string, unknown> = {\n model: this.model,\n max_tokens: this.maxTokens,\n response_format: responseFormat,\n messages: [\n {\n role: \"user\",\n content: [...imageParts, { type: \"text\" as const, text: prompt }],\n },\n ],\n // OpenRouter-specific: include token accounting in the response.\n usage: { include: true },\n };\n\n if (this.reasoningEffort) {\n requestParams.reasoning = { effort: OPENROUTER_REASONING_EFFORT[this.reasoningEffort] };\n }\n\n const response = await client.chat.completions.create(requestParams);\n\n const choice = response.choices?.[0];\n if (!choice?.message) {\n throw new VisualAIProviderError(\"OpenRouter returned an empty response (no choices).\");\n }\n\n const text = choice.message.content ?? \"\";\n\n if (choice.finish_reason === \"length\") {\n throw new VisualAITruncationError(\n `Response truncated: OpenRouter returned finish_reason \"length\". 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 text,\n this.maxTokens,\n );\n }\n\n const reasoningTokens = response.usage?.completion_tokens_details?.reasoning_tokens;\n const cachedInputTokens = response.usage?.prompt_tokens_details?.cached_tokens;\n const cost = response.usage?.cost;\n\n return {\n text,\n usage: response.usage\n ? {\n inputTokens: response.usage.prompt_tokens,\n outputTokens: response.usage.completion_tokens,\n ...(reasoningTokens !== undefined && { reasoningTokens }),\n ...(cachedInputTokens !== undefined && { cachedInputTokens }),\n ...(cost !== undefined && { cost }),\n }\n : undefined,\n };\n } catch (err) {\n if (err instanceof VisualAITruncationError || err instanceof VisualAIProviderError) {\n throw err;\n }\n throw mapProviderError(err);\n }\n }\n}\n","import {\n DEFAULT_IMAGE_DETAIL,\n DEFAULT_MAX_IMAGE_DIMENSION,\n DEFAULT_MAX_TOKENS,\n DEFAULT_MODELS,\n type ImageDetailLevel,\n MODEL_TO_PROVIDER,\n MODELS_REQUIRING_LARGE_OUTPUT_BUDGET,\n OPENAI_HEAVY_REASONING_MAX_TOKENS,\n OPENAI_REASONING_MAX_TOKENS,\n ReasoningEffort,\n type ReasoningEffortLevel,\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 maxImageDimension: number;\n imageDetail: ImageDetailLevel;\n /** Per-request timeout in ms; undefined leaves the provider SDK default in place. */\n timeout: number | undefined;\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 // OpenRouter slugs are always vendor-prefixed (\"x-ai/grok-4.5\"); no\n // first-party model name contains a slash.\n if (model.includes(\"/\")) return \"openrouter\";\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 [\"OPENROUTER_API_KEY\", \"openrouter\"],\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, OPENROUTER_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\n/**\n * `reasoningEffort` is a closed enum, so an unrecognised value is a typo rather\n * than a preference: throw like `parseBooleanEnv` instead of silently running at\n * the provider's default. The accepted list is derived from `ReasoningEffort` so\n * it cannot drift from the type.\n */\nfunction parseReasoningEffortEnv(\n envName: string,\n value: string | undefined,\n): ReasoningEffortLevel | undefined {\n if (value === undefined || value === \"\") return undefined;\n const levels: readonly string[] = Object.values(ReasoningEffort);\n const lower = value.toLowerCase();\n if (levels.includes(lower)) return lower as ReasoningEffortLevel;\n throw new VisualAIConfigError(\n `Invalid ${envName} value: \"${value}\". Use one of: ${levels.join(\", \")}.`,\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 // Resolved before the maxTokens rules below, which branch on the effort: an\n // env-set effort has to raise the OpenAI budget exactly as a param-set one does.\n const reasoningEffort =\n config.reasoningEffort ??\n parseReasoningEffortEnv(\"VISUAL_AI_REASONING_EFFORT\", process.env.VISUAL_AI_REASONING_EFFORT);\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 if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {\n throw new VisualAIConfigError(\n `Invalid timeout: ${config.timeout}. Must be a positive number of milliseconds.`,\n );\n }\n\n const userSetMaxTokens = config.maxTokens !== undefined;\n let maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // OpenAI and OpenRouter reasoning tokens share the output budget, so the\n // 4096 default can be consumed entirely by reasoning. Auto-increase either\n // when the effort is high/xhigh, or when the model reasons heavily enough to\n // truncate at every effort level (see MODELS_REQUIRING_LARGE_OUTPUT_BUDGET).\n const effortNeedsLargeBudget = reasoningEffort === \"high\" || reasoningEffort === \"xhigh\";\n const modelNeedsLargeBudget = MODELS_REQUIRING_LARGE_OUTPUT_BUDGET.has(model);\n if (\n !userSetMaxTokens &&\n (provider === \"openai\" || provider === \"openrouter\") &&\n (effortNeedsLargeBudget || modelNeedsLargeBudget)\n ) {\n maxTokens = modelNeedsLargeBudget\n ? OPENAI_HEAVY_REASONING_MAX_TOKENS\n : OPENAI_REASONING_MAX_TOKENS;\n if (debug) {\n const reason = modelNeedsLargeBudget\n ? `model \"${model}\", which exhausts smaller budgets on reasoning at any effort`\n : `provider \"${provider}\" with reasoningEffort \"${reasoningEffort}\"`;\n process.stderr.write(\n `[visual-ai-assertions] Auto-increased maxTokens from ${DEFAULT_MAX_TOKENS} to ${maxTokens} for ${reason}.\\n`,\n );\n }\n }\n\n return {\n provider,\n apiKey: config.apiKey,\n model,\n maxTokens,\n reasoningEffort,\n maxImageDimension: config.maxImageDimension ?? DEFAULT_MAX_IMAGE_DIMENSION,\n imageDetail: config.imageDetail ?? DEFAULT_IMAGE_DETAIL,\n timeout: config.timeout,\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 // Fable 5.1 is priced identically to Fable 5, the tier it succeeds.\n [`${Provider.ANTHROPIC}:${Model.Anthropic.FABLE_5_1}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.FABLE_5}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_5_5}`]: {\n inputPricePerToken: 4 / PER_MILLION,\n outputPricePerToken: 20 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_5}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.OPUS_4_8}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 25 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_5_5}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.SONNET_5}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\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 // Prompts above 100K tokens bill at $0.50/$2.50, far beyond screenshot-sized\n // calls. Cached input is $0.01/MTok and cache writes $0.125/MTok (not modelled).\n [`${Provider.ANTHROPIC}:${Model.Anthropic.HAIKU_5_5}`]: {\n inputPricePerToken: 0.1 / PER_MILLION,\n outputPricePerToken: 0.5 / PER_MILLION,\n },\n [`${Provider.ANTHROPIC}:${Model.Anthropic.HAIKU_4_5}`]: {\n inputPricePerToken: 1 / PER_MILLION,\n outputPricePerToken: 5 / PER_MILLION,\n },\n // Cached input is $1/MTok and cache writes $12.50/MTok; neither is modelled\n // here, since `calculateCost` applies no cache discount on any provider.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_ASTRA}`]: {\n inputPricePerToken: 10 / PER_MILLION,\n outputPricePerToken: 50 / PER_MILLION,\n },\n // Cached input is $0.10/MTok (not modelled), half GPT-6 Sol's cached rate.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_1_SOL}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n // Cached input is $0.20/MTok (not modelled). Prompts above 272K input tokens\n // bill at 2x input / 1.5x output, which is far beyond screenshot-sized calls.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_SOL}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 10 / PER_MILLION,\n },\n // Cached input is $0.01/MTok and cache writes $0.125/MTok; neither is modelled.\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_6_LUNA}`]: {\n inputPricePerToken: 0.1 / PER_MILLION,\n outputPricePerToken: 0.5 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_SOL}`]: {\n inputPricePerToken: 5 / PER_MILLION,\n outputPricePerToken: 30 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_TERRA}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 12 / PER_MILLION,\n },\n [`${Provider.OPENAI}:${Model.OpenAI.GPT_5_6_LUNA}`]: {\n inputPricePerToken: 0.2 / PER_MILLION,\n outputPricePerToken: 1.2 / 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 // Introductory pricing through 2026-12-31; reverts to $1.50/$7.50 per MTok\n // on 2027-01-01 (https://blog.google/.../3-8-flash-and-3-8-flash-cyber/).\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_8_FLASH}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n // Introductory pricing through 2026-12-31; reverts to $1.50/$7.50 per MTok\n // on 2027-01-01 (https://blog.google/.../introducing-gemini-3-7-flash/).\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_7_FLASH}`]: {\n inputPricePerToken: 0.75 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_6_FLASH}`]: {\n inputPricePerToken: 1.5 / PER_MILLION,\n outputPricePerToken: 7.5 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_5_FLASH}`]: {\n inputPricePerToken: 1.5 / PER_MILLION,\n outputPricePerToken: 9 / PER_MILLION,\n },\n [`${Provider.GOOGLE}:${Model.Google.GEMINI_3_5_FLASH_LITE}`]: {\n inputPricePerToken: 0.3 / PER_MILLION,\n outputPricePerToken: 2.5 / 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}`]: {\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 // OpenRouter passes through upstream per-model pricing (verified 2026-09-03\n // against https://openrouter.ai/api/v1/models).\n // Meta's own listed rates ($1.25 / $4.25, cached input $0.15) match\n // OpenRouter's pass-through exactly. Cached input is not modelled here:\n // `calculateCost` applies no cache discount on any provider.\n [`${Provider.OPENROUTER}:${Model.OpenRouter.MUSE_SPARK_1_3}`]: {\n inputPricePerToken: 1.25 / PER_MILLION,\n outputPricePerToken: 4.25 / PER_MILLION,\n },\n // Muse Spark 1.3's data-sharing tier: same model, ~12x cheaper, because\n // Meta trains on everything submitted through it. Deliberately keyed by the\n // literal slug rather than a `Model.OpenRouter` entry — it must never be\n // reachable via autocomplete or default selection. Pass the string yourself\n // (`model: \"meta/muse-spark-1.3-contributor\"`) to opt in; cost is still\n // tracked correctly once you do. Cached input is $0.002/MTok, not modelled\n // (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:meta/muse-spark-1.3-contributor`]: {\n inputPricePerToken: 0.1 / PER_MILLION,\n outputPricePerToken: 0.2 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_7}`]: {\n inputPricePerToken: 1.6 / PER_MILLION,\n outputPricePerToken: 4.8 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_6}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GROK_4_5}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.KIMI_K3}`]: {\n inputPricePerToken: 3 / PER_MILLION,\n outputPricePerToken: 15 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.KIMI_K2_7_CODE}`]: {\n inputPricePerToken: 0.82 / PER_MILLION,\n outputPricePerToken: 3.75 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_8_MAX}`]: {\n inputPricePerToken: 2 / PER_MILLION,\n outputPricePerToken: 6 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_7_PLUS}`]: {\n inputPricePerToken: 0.32 / PER_MILLION,\n outputPricePerToken: 1.28 / PER_MILLION,\n },\n [`${Provider.OPENROUTER}:${Model.OpenRouter.QWEN_3_6_FLASH}`]: {\n inputPricePerToken: 0.1875 / PER_MILLION,\n outputPricePerToken: 1.125 / PER_MILLION,\n },\n // Verified 2026-09-10 against https://openrouter.ai/api/v1/models. Cached\n // input is $0.03/MTok, not modelled (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:${Model.OpenRouter.GLM_5_3_FLASH}`]: {\n inputPricePerToken: 0.15 / PER_MILLION,\n outputPricePerToken: 0.5 / PER_MILLION,\n },\n // Verified 2026-09-23 against https://openrouter.ai/api/v1/models; both\n // upstream endpoints (Xiaomi, DeepInfra) charge the same rate. Cached input\n // is $0.0036/MTok, not modelled (no provider gets a cache discount here).\n [`${Provider.OPENROUTER}:${Model.OpenRouter.MIMO_V2_6_PRO}`]: {\n inputPricePerToken: 0.435 / PER_MILLION,\n outputPricePerToken: 0.87 / 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 RawVideoProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport type { NormalizedImage, NormalizedVideo, 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 const cachedTokenStr =\n usage.cachedInputTokens !== undefined ? ` (${usage.cachedInputTokens} cached)` : \"\";\n process.stderr.write(\n `[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input${cachedTokenStr} + ${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 ...(rawUsage?.cachedInputTokens !== undefined && {\n cachedInputTokens: rawUsage.cachedInputTokens,\n }),\n estimatedCost: calculateCost(config.provider, config.model, inputTokens, outputTokens),\n ...(rawUsage?.cost !== undefined && { reportedCost: rawUsage.cost }),\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\n/**\n * Native-video counterpart of `timedSendMessage`. The caller has already\n * checked that the driver implements `sendVideoMessage`.\n */\nexport async function timedSendVideoMessage(\n driver: ProviderDriver,\n video: NormalizedVideo,\n prompt: string,\n options?: SendMessageOptions,\n): Promise<RawVideoProviderResponse & { durationSeconds: number }> {\n if (!driver.sendVideoMessage) {\n throw new VisualAIError(\"Provider driver does not support native video delivery\");\n }\n const start = performance.now();\n const response = await driver.sendVideoMessage(video, 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\n/**\n * Models that return annotated diff images via Gemini code execution: the whole\n * Gemini flash tier. `compare()` auto-enables a diff image on every model in\n * this set, and `generateAiDiff` refuses any model outside it.\n *\n * The Flash-Lite models (`gemini-3.5-flash-lite`, `gemini-3.1-flash-lite`) and\n * the Pro tier (`gemini-3.1-pro-preview`) are deliberately absent: they do not\n * drive code execution reliably enough to annotate an image.\n */\nexport const DIFF_ALLOWED_MODELS: ReadonlySet<string> = new Set([\n Model.Google.GEMINI_3_FLASH_PREVIEW,\n Model.Google.GEMINI_3_5_FLASH,\n Model.Google.GEMINI_3_6_FLASH,\n Model.Google.GEMINI_3_7_FLASH,\n Model.Google.GEMINI_3_8_FLASH,\n]);\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 (!DIFF_ALLOWED_MODELS.has(model)) {\n throw new VisualAIConfigError(\n `Annotated diff images are only supported with these Google models: ${[...DIFF_ALLOWED_MODELS].join(\", \")}.`,\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 { DEFAULT_MAX_IMAGE_DIMENSION } from \"../constants.js\";\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 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(\n data: Buffer,\n mimeType: SupportedMimeType,\n maxDimension: number,\n): 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 <= maxDimension && height <= maxDimension) {\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 <= maxDimension && height <= maxDimension) {\n return data;\n }\n\n return pipeline\n .resize({\n width: maxDimension,\n height: maxDimension,\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 maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\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, maxDimension);\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 { DEFAULT_MAX_IMAGE_DIMENSION } from \"../constants.js\";\nimport { VisualAIVideoError } from \"../errors.js\";\nimport { readFile } from \"node:fs/promises\";\nimport type {\n Frame,\n FramesInput,\n ImageInput,\n MediaInput,\n NormalizedImage,\n NormalizedVideo,\n TimestampedFrameInput,\n VideoSamplingOptions,\n} from \"../types.js\";\nimport { saveDebugFrames } from \"./debug-frames.js\";\nimport { dedupeFrames, resolveDedupeOptions } from \"./frame-dedupe.js\";\nimport { normalizeImage } from \"./image.js\";\nimport {\n decodeBase64,\n isDataUrl,\n isFilePath,\n looksLikeVideoBase64,\n parseDataUrl,\n} from \"./input-detect.js\";\nimport {\n DEFAULT_FPS,\n MAX_FRAMES_HARD_CAP,\n detectVideoMimeType,\n assertDurationWithinLimit,\n extractFrames,\n getVideoMimeFromExtension,\n probeDurationSeconds,\n resolveVideoSamplingOptions,\n resolveVideoToPath,\n} from \"./video.js\";\n\nexport type NormalizedMedia =\n | { kind: \"image\"; image: NormalizedImage }\n | {\n kind: \"video\";\n /** Frames to send to the provider, after unchanged frames were dropped. */\n frames: Frame[];\n durationSeconds: number;\n /** Sampled frames dropped because they matched the preceding kept frame. */\n droppedUnchanged: number;\n }\n | {\n kind: \"native-video\";\n /** The video itself, to be sent to a provider that accepts video input. */\n video: NormalizedVideo;\n };\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 * Narrows an input to the pre-sampled `FramesInput` shape. Buffers, typed\n * arrays, and strings (all valid `MediaInput`s) are excluded so only a plain\n * `{ frames: [...] }` object matches.\n */\nexport function isFramesInput(input: unknown): input is FramesInput {\n return (\n typeof input === \"object\" &&\n input !== null &&\n !Buffer.isBuffer(input) &&\n !(input instanceof Uint8Array) &&\n Array.isArray((input as { frames?: unknown }).frames)\n );\n}\n\nfunction isTimestampedFrameInput(\n frame: ImageInput | TimestampedFrameInput,\n): frame is TimestampedFrameInput {\n return (\n typeof frame === \"object\" &&\n !Buffer.isBuffer(frame) &&\n !(frame instanceof Uint8Array) &&\n \"image\" in frame\n );\n}\n\n/**\n * Normalizes pre-sampled frames into the same `{ kind: \"video\", frames,\n * durationSeconds }` envelope the video path produces, so everything\n * downstream (provider call, timeline prompt, frame metadata, timestamps,\n * frame references) works unchanged. Never loads ffmpeg.\n */\nexport async function normalizeFrames(\n input: FramesInput,\n maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\n): Promise<NormalizedMedia> {\n const rawFrames = input.frames;\n const fps = input.fps ?? DEFAULT_FPS;\n\n // Validate before decoding any image so a bad option fails fast.\n resolveDedupeOptions(input.dedupe);\n\n if (rawFrames.length === 0) {\n throw new VisualAIVideoError(\"frames must be a non-empty array of image inputs\");\n }\n if (rawFrames.length > MAX_FRAMES_HARD_CAP) {\n throw new VisualAIVideoError(\n `frames length ${rawFrames.length} exceeds the hard cap of ${MAX_FRAMES_HARD_CAP}. ` +\n `Pass fewer frames or open an issue if you need a larger limit.`,\n );\n }\n if (!Number.isFinite(fps) || fps <= 0) {\n throw new VisualAIVideoError(`Invalid fps: ${fps}. Must be a finite number > 0.`);\n }\n\n const sampled: Frame[] = await Promise.all(\n rawFrames.map(async (raw, index): Promise<Frame> => {\n const timestamped = isTimestampedFrameInput(raw);\n const imageInput = timestamped ? raw.image : raw;\n const explicit = timestamped ? raw.timestampSeconds : undefined;\n const timestampSeconds = explicit ?? index / fps;\n if (!Number.isFinite(timestampSeconds) || timestampSeconds < 0) {\n throw new VisualAIVideoError(\n `Invalid timestampSeconds for frame ${index}: ${String(timestampSeconds)}. ` +\n `Must be a finite number >= 0.`,\n );\n }\n const image = await normalizeImage(imageInput, maxDimension);\n return {\n data: image.data,\n mimeType: image.mimeType,\n get base64(): string {\n return image.base64;\n },\n timestampSeconds,\n index,\n };\n }),\n );\n\n // Duration isn't known for a bare frame sequence; use the latest timestamp so\n // the timeline prompt and frame metadata report a sensible span. Computed\n // over every supplied frame so dropping a static tail does not shorten it.\n const durationSeconds = sampled.reduce((max, f) => Math.max(max, f.timestampSeconds), 0);\n\n const { frames, dropped } = await dedupeFrames(sampled, input.dedupe);\n await saveDebugFrames(frames);\n return { kind: \"video\", frames, durationSeconds, droppedUnchanged: dropped };\n}\n\n/**\n * Prepares a video for native delivery: resolves it to bytes, probes the\n * duration, and enforces `maxDurationSeconds` before any provider call, exactly\n * as frame sampling does. No frames are extracted.\n */\nexport async function normalizeNativeVideo(\n input: MediaInput,\n videoOptions?: VideoSamplingOptions,\n): Promise<NormalizedMedia> {\n const { fps, maxDurationSeconds } = resolveVideoSamplingOptions(videoOptions);\n const { path, mimeType, cleanup } = await resolveVideoToPath(input);\n try {\n const durationSeconds = await probeDurationSeconds(path);\n assertDurationWithinLimit(durationSeconds, maxDurationSeconds);\n const data = await readFile(path);\n return { kind: \"native-video\", video: { data, mimeType, durationSeconds, fps } };\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/**\n * Single entry point used by the client. Accepts pre-sampled frames or auto-detects\n * whether `input` is an image or a video, returning a uniform `NormalizedMedia`\n * envelope. With `nativeVideo` set, a video input is prepared for native\n * delivery instead of being sampled into frames; images and pre-sampled frames\n * are unaffected.\n */\nexport async function normalizeMedia(\n input: MediaInput | FramesInput,\n videoOptions?: VideoSamplingOptions,\n maxDimension: number = DEFAULT_MAX_IMAGE_DIMENSION,\n nativeVideo = false,\n): Promise<NormalizedMedia> {\n if (isFramesInput(input)) {\n return normalizeFrames(input, maxDimension);\n }\n\n if (isVideoInput(input)) {\n if (nativeVideo) {\n return normalizeNativeVideo(input, videoOptions);\n }\n // Validate before touching the filesystem or ffmpeg so a bad option fails fast.\n resolveDedupeOptions(videoOptions?.dedupe);\n const { path, cleanup } = await resolveVideoToPath(input);\n try {\n const { frames: sampled, durationSeconds } = await extractFrames(\n path,\n videoOptions,\n maxDimension,\n );\n const { frames, dropped } = await dedupeFrames(sampled, videoOptions?.dedupe);\n // Debug output shows exactly the frames the model receives.\n await saveDebugFrames(frames);\n return { kind: \"video\", frames, durationSeconds, droppedUnchanged: dropped };\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, maxDimension);\n return { kind: \"image\", image };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { Frame } from \"../types.js\";\n\nexport const DEBUG_FRAMES_ENV = \"VISUAL_AI_DEBUG_FRAMES\";\nexport const DEBUG_FRAMES_DIR_ENV = \"VISUAL_AI_DEBUG_FRAMES_DIR\";\nconst DEFAULT_DIR_NAME = \"visual-ai-debug-frames\";\n\nfunction isEnabled(env: NodeJS.ProcessEnv): boolean {\n const raw = env[DEBUG_FRAMES_ENV];\n if (raw === undefined || raw === \"\") return false;\n const lower = raw.toLowerCase();\n return lower === \"true\" || lower === \"1\";\n}\n\nfunction timestampSlug(date: Date): string {\n return date.toISOString().replace(/[:.]/g, \"-\");\n}\n\nfunction paddedIndex(value: number, total: number): string {\n const width = Math.max(2, String(total - 1).length);\n return String(value).padStart(width, \"0\");\n}\n\nfunction extensionFromMimeType(mimeType: string): string {\n if (mimeType === \"image/png\") return \".png\";\n if (mimeType === \"image/webp\") return \".webp\";\n return \".jpg\";\n}\n\n/**\n * When `VISUAL_AI_DEBUG_FRAMES=true|1`, persist sampled video frames to disk\n * for offline inspection. Frames are written to a per-call subdirectory under\n * `VISUAL_AI_DEBUG_FRAMES_DIR` (or `./visual-ai-debug-frames` if unset).\n *\n * Best-effort: any failure is logged to stderr and swallowed so debug-only\n * disk problems never break the actual provider call.\n */\nexport async function saveDebugFrames(\n frames: readonly Frame[],\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string | undefined> {\n if (!isEnabled(env)) return undefined;\n if (frames.length === 0) return undefined;\n\n const baseDir = env[DEBUG_FRAMES_DIR_ENV]?.trim() || DEFAULT_DIR_NAME;\n const runDir = resolve(baseDir, `${timestampSlug(new Date())}-${randomBytes(3).toString(\"hex\")}`);\n\n try {\n await mkdir(runDir, { recursive: true });\n await Promise.all(\n frames.map((frame) => {\n const idx = paddedIndex(frame.index, frames.length);\n const ts = frame.timestampSeconds.toFixed(2);\n const ext = extensionFromMimeType(frame.mimeType);\n const filename = `frame-${idx}-t${ts}s${ext}`;\n return writeFile(join(runDir, filename), frame.data);\n }),\n );\n } catch (err) {\n process.stderr.write(\n `[visual-ai-assertions] warning: failed to save debug frames to ${runDir}: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n return undefined;\n }\n\n process.stderr.write(\n `[visual-ai-assertions] Saved ${frames.length} debug frame(s) to ${runDir}\\n`,\n );\n return runDir;\n}\n","import sharp from \"sharp\";\nimport { VisualAIVideoError } from \"../errors.js\";\nimport type { Frame, FrameDedupeOptions } from \"../types.js\";\n\n/**\n * Longer edge of the grayscale thumbnail frames are compared on. 1568x880\n * becomes 256x144 (36,864 px): a 300x60 toast still covers ~490 px, while a\n * 2x16 text caret shrinks to ~3 px.\n */\nconst DEDUPE_THUMBNAIL_EDGE = 256;\n\n/**\n * Per-pixel gray-level delta (out of 255) a thumbnail pixel must exceed to\n * count as changed. Sits above JPEG `-q:v 3` and codec re-quantisation noise,\n * which stays under ~10 levels once averaged down to thumbnail size.\n */\nconst DEDUPE_PIXEL_TOLERANCE = 24;\n\n/**\n * Default fraction of thumbnail pixels that must change for a frame to be kept.\n * 0.1% is ~37 px of a 256x144 thumbnail: a 37x37 px region on a 1568x880 frame.\n */\nexport const DEFAULT_DEDUPE_THRESHOLD = 0.001;\n\nexport interface ResolvedDedupeOptions {\n readonly enabled: boolean;\n readonly threshold: number;\n}\n\n/** Grayscale thumbnail used to compare two frames. */\nexport interface FrameSignature {\n readonly width: number;\n readonly height: number;\n /** One byte per pixel, row-major. */\n readonly pixels: Buffer;\n}\n\n/**\n * Normalizes a `dedupe` option into an explicit `{ enabled, threshold }` pair,\n * throwing `VisualAIVideoError` for a threshold outside `(0, 1]`. Call this\n * before any expensive work so an invalid option fails fast.\n */\nexport function resolveDedupeOptions(raw: FrameDedupeOptions | undefined): ResolvedDedupeOptions {\n if (raw === undefined || raw === true) {\n return { enabled: true, threshold: DEFAULT_DEDUPE_THRESHOLD };\n }\n if (raw === false) {\n return { enabled: false, threshold: DEFAULT_DEDUPE_THRESHOLD };\n }\n const threshold = raw.threshold ?? DEFAULT_DEDUPE_THRESHOLD;\n if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {\n throw new VisualAIVideoError(\n `Invalid dedupe threshold: ${String(threshold)}. Must be a finite number in (0, 1].`,\n );\n }\n return { enabled: true, threshold };\n}\n\n/**\n * Decodes a frame into a small single-channel grayscale thumbnail. Alpha is\n * flattened onto white first so RGBA inputs produce one channel, not two.\n */\nexport async function frameSignature(frame: Frame): Promise<FrameSignature> {\n try {\n const { data, info } = await sharp(frame.data)\n .flatten({ background: { r: 255, g: 255, b: 255 } })\n .greyscale()\n .resize(DEDUPE_THUMBNAIL_EDGE, DEDUPE_THUMBNAIL_EDGE, {\n fit: \"inside\",\n withoutEnlargement: true,\n })\n .raw()\n .toBuffer({ resolveWithObject: true });\n return { width: info.width, height: info.height, pixels: data };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new VisualAIVideoError(\n `Failed to decode frame ${frame.index} (${frame.timestampSeconds.toFixed(2)}s) ` +\n `for change detection: ${reason}`,\n );\n }\n}\n\n/** Fraction of pixels whose gray level differs by more than the tolerance. */\nfunction changedFraction(a: FrameSignature, b: FrameSignature): number {\n if (a.width !== b.width || a.height !== b.height) {\n return 1;\n }\n let changed = 0;\n for (let i = 0; i < a.pixels.length; i++) {\n if (Math.abs((a.pixels[i] ?? 0) - (b.pixels[i] ?? 0)) > DEDUPE_PIXEL_TOLERANCE) {\n changed++;\n }\n }\n return changed / a.pixels.length;\n}\n\n/**\n * Returns `frame` with the given index. A new object is built field by field\n * so the source's lazy `base64` getter is forwarded rather than evaluated, as\n * object spread would do.\n */\nfunction reindex(frame: Frame, index: number): Frame {\n if (frame.index === index) {\n return frame;\n }\n return {\n data: frame.data,\n mimeType: frame.mimeType,\n get base64(): string {\n return frame.base64;\n },\n timestampSeconds: frame.timestampSeconds,\n index,\n };\n}\n\n/**\n * Drops frames that did not visibly change from the most recently kept frame.\n * The first frame is always kept; the last is not treated specially. Surviving\n * frames are re-indexed contiguously so `frameReferences` and the timeline\n * prompt line up with `frames.timestampsSeconds`.\n *\n * @throws {VisualAIVideoError} For an invalid threshold or an undecodable frame.\n */\nexport async function dedupeFrames(\n frames: readonly Frame[],\n options: FrameDedupeOptions | undefined,\n): Promise<{ frames: Frame[]; dropped: number }> {\n const { enabled, threshold } = resolveDedupeOptions(options);\n if (!enabled || frames.length < 2) {\n return { frames: [...frames], dropped: 0 };\n }\n\n const signed = await Promise.all(\n frames.map(async (frame) => ({ frame, signature: await frameSignature(frame) })),\n );\n\n const [first, ...rest] = signed;\n if (first === undefined) {\n return { frames: [], dropped: 0 };\n }\n\n const kept: Frame[] = [first.frame];\n let lastKept = first.signature;\n for (const { frame, signature } of rest) {\n if (changedFraction(lastKept, signature) >= threshold) {\n kept.push(frame);\n lastKept = signature;\n }\n }\n\n return {\n frames: kept.map((frame, index) => reindex(frame, index)),\n dropped: frames.length - kept.length,\n };\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;\n/** Default frame-sampling rate, shared by video sampling and pre-sampled frame inputs. */\nexport const DEFAULT_FPS = 1;\nconst DEFAULT_MAX_FRAMES = 10;\nconst DEFAULT_MAX_DURATION_SECONDS = 10;\n/** Hard upper bound on caller-supplied frame counts to keep memory bounded. */\nexport const 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 \"Could not load fluent-ffmpeg. It ships as a dependency of visual-ai-assertions, \" +\n \"so this usually means the install was pruned or the platform-specific binary is unavailable. \" +\n \"Reinstall the package or run: pnpm add fluent-ffmpeg @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe\",\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 interface ResolvedVideoSamplingOptions {\n readonly fps: number;\n readonly maxFrames: number;\n readonly maxDurationSeconds: number;\n}\n\n/**\n * Applies defaults to the numeric sampling options and validates them, throwing\n * `VisualAIVideoError` for anything out of range. Shared by frame extraction\n * and native delivery so both paths reject the same inputs before any I/O.\n */\nexport function resolveVideoSamplingOptions(\n options: VideoSamplingOptions = {},\n): ResolvedVideoSamplingOptions {\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 return { fps, maxFrames, maxDurationSeconds };\n}\n\n/** Throws `VisualAIVideoError` when a probed duration exceeds the configured limit. */\nexport function assertDurationWithinLimit(\n durationSeconds: number,\n maxDurationSeconds: number,\n): void {\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\nexport async function extractFrames(\n videoPath: string,\n options: VideoSamplingOptions = {},\n maxDimension: number = FRAME_MAX_DIMENSION,\n): Promise<{ frames: Frame[]; durationSeconds: number }> {\n const { fps, maxFrames, maxDurationSeconds } = resolveVideoSamplingOptions(options);\n\n const ffmpeg = await loadFfmpegFactory();\n const durationSeconds = await probeDurationSeconds(videoPath);\n assertDurationWithinLimit(durationSeconds, maxDurationSeconds);\n\n const outputDir = await mkdtemp(join(tmpdir(), \"visual-ai-frames-\"));\n try {\n // Constrain the longer edge to maxDimension for both portrait and landscape inputs.\n const filter =\n `fps=${fps},` +\n `scale='if(gt(iw,ih),min(${maxDimension},iw),-2)':` +\n `'if(gt(iw,ih),-2,min(${maxDimension},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 { z } from \"zod\";\nimport type {\n AccessibilityCheckName,\n ContentCheckName,\n ImageDetailLevel,\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 /**\n * Prompt tokens served from the provider's cache, when reported. Informational\n * only — `estimatedCost` does not apply a cache discount, because providers\n * differ on whether these are counted inside `inputTokens` (OpenAI, OpenRouter,\n * Google) or billed as a separate bucket alongside it (Anthropic).\n */\n cachedInputTokens: z.number().optional(),\n /** Cost in USD from the library's local pricing table (inputTokens/outputTokens × per-model rates). */\n estimatedCost: z.number().optional(),\n /** Actual cost in USD reported by the provider itself, when available (OpenRouter). Authoritative over `estimatedCost`. */\n reportedCost: 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 /** Number of frames actually sent to the model (after unchanged frames were dropped). */\n count: number;\n /** Timestamp (seconds, from the start of the clip) of each sent frame, in order. */\n timestampsSeconds: number[];\n /** Total duration of the source video in seconds. */\n durationSeconds: number;\n /**\n * Number of sampled frames dropped because they did not visibly change from\n * the preceding kept frame. `0` when dedupe is disabled or nothing was dropped.\n * See `FrameDedupeOptions`.\n */\n droppedUnchanged: number;\n}\n/**\n * Metadata describing a video that was delivered to the model natively (as the\n * video itself rather than sampled frames). Populated client-side.\n */\nexport interface NativeVideoMetadata {\n /** Total duration of the source video in seconds. */\n durationSeconds: number;\n /** Sampling rate requested from the provider, in frames per second. */\n fps: number;\n /** MIME type the video was sent as. */\n mimeType: SupportedVideoMimeType;\n /** Whether the bytes went inline in the request or through the provider's file upload API. */\n delivery: \"inline\" | \"file\";\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 sampled into frames. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n /** Present only when the input was a video delivered natively to the provider. */\n video?: NativeVideoMetadata;\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 * Nullable because providers with strict structured-output schemas (e.g. OpenAI)\n * must mark every field required and represent \"no value\" as `null` rather than\n * omitting the key, even for image inputs that were never asked to populate it.\n */\n frameReferences: z.array(z.number().int().nonnegative()).nullable().optional(),\n /**\n * For natively delivered video, the timestamps (seconds from the start of\n * the clip) the model relied on to answer. The native counterpart of\n * `frameReferences`. Nullable for the same strict-schema reason.\n */\n timestampReferences: z.array(z.number().nonnegative()).nullable().optional(),\n usage: UsageInfoSchema.optional(),\n});\n/** Result returned by `ask()`. */\nexport type AskResult = Omit<\n z.infer<typeof AskResultSchema>,\n \"frameReferences\" | \"timestampReferences\"\n> & {\n /** Present only when the input was a video sampled into frames. Indices into `frames.timestampsSeconds`. */\n frameReferences?: number[];\n /** Present only when the input was a video delivered natively. Seconds from the start of the clip. */\n timestampReferences?: number[];\n /** Present only when the input was a video sampled into frames. Describes which frames the model saw. */\n frames?: VideoFramesMetadata;\n /** Present only when the input was a video delivered natively to the provider. */\n video?: NativeVideoMetadata;\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/**\n * A single pre-sampled frame with an explicit timestamp. Use this shape when\n * you want to control the timestamp the model sees; otherwise pass a bare\n * `ImageInput` and the timestamp is derived from `fps` and the frame's position.\n */\nexport interface TimestampedFrameInput {\n /** Image source for the frame (buffer, data URL, base64, file path, or URL). */\n image: ImageInput;\n /** Timestamp in seconds, from the start of the sequence. Derived from `fps` when omitted. */\n timestampSeconds?: number;\n}\n\n/**\n * Pre-sampled frames supplied directly instead of a video file. Useful when you\n * already have an array of screenshots and cannot (or would rather not) pass a\n * video — this path never loads ffmpeg. Passed to `check()` / `ask()` in place\n * of a `MediaInput`; downstream handling is identical to a sampled video.\n */\nexport interface FramesInput {\n /**\n * Ordered frames, earliest first. Each entry is an image source or a\n * `{ image, timestampSeconds }` pair. Must be non-empty and is capped at the\n * same per-request frame limit as video sampling (60).\n */\n frames: ReadonlyArray<ImageInput | TimestampedFrameInput>;\n /**\n * Frame rate used to derive timestamps for frames that don't carry their own\n * `timestampSeconds` (frame `i` maps to `i / fps` seconds). Default `1`.\n */\n fps?: number;\n /**\n * Drop frames that did not visibly change from the preceding kept frame\n * before sending to the provider. Default `true`. See `FrameDedupeOptions`.\n */\n dedupe?: FrameDedupeOptions;\n}\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\" | \"openrouter\";\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 /**\n * Longest-edge pixel cap for image inputs before they are sent to the\n * provider. Defaults to 1568. Raising it only helps providers that accept\n * higher-resolution requests (OpenAI with `imageDetail: \"high\"` scales into a\n * 2048px box); Anthropic re-downscales to ~1568px regardless.\n */\n maxImageDimension?: number;\n /**\n * Image-detail hint mapped per provider: OpenAI/OpenRouter `detail`, Google\n * `mediaResolution`. `\"auto\"` (default) sends no detail field. No effect on\n * Anthropic (Claude auto-downscales images).\n */\n imageDetail?: ImageDetailLevel;\n /**\n * Per-request timeout in milliseconds, forwarded to the provider SDK.\n * Omitted by default, so each SDK's own default applies (OpenAI and\n * OpenRouter 10 minutes, Google 1 minute, Anthropic per its own rules).\n *\n * Worth setting for heavy reasoning models: they can spend many minutes on a\n * single call, and the SDK default lets a request hang far longer than most\n * test suites should tolerate. Note that provider SDKs retry timed-out\n * requests, so total wall time can exceed this value by a multiple.\n */\n timeout?: number;\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 * Whether the screenshot shows the interface in its finished state. Defaults\n * to `true`, which is what a test asserting on a settled screen wants: an\n * element under a loading spinner, skeleton or error overlay is reported as\n * not properly visible, because the overlay says the content is not ready.\n *\n * Set to `false` when the screenshot was deliberately captured mid-load, so\n * loading chrome is expected rather than a defect. The finished-state rules\n * are then left out of the prompt entirely, and only presence and clipping\n * are judged. Note this omits the guidance rather than inverting it — to have\n * the model actively disregard loading indicators, say so in `instructions`.\n */\n finalState?: boolean;\n /**\n * Whether an element that is present but clearly badly rendered should fail.\n * Defaults to `false`: `elementsVisible()` is a presence check, and an element\n * counts as visible if it is there at all, whatever it looks like.\n *\n * Set to `true` to judge presentation as well. Text at contrast too low to\n * read, elements overlapping, an element out of alignment with its siblings,\n * or text cut off mid-word then fail, with the model told to say the element\n * is present before naming the defect. Opt in per assertion where a rendering\n * defect is the thing being checked: measured with the rule on and off, it\n * caught exactly the bullet naming a present-but-overlapping element and\n * nothing else, while making the better model noticeably flakier on plain\n * presence questions.\n *\n * Ignored by `elementsHidden()`, where the question is absence and a rendering\n * defect cannot change the answer.\n */\n requireCorrectRendering?: boolean;\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 * Drop sampled frames that did not visibly change from the preceding kept\n * frame before sending to the provider. Default `true`. See `FrameDedupeOptions`.\n * Only applies when frames are sampled; ignored for native delivery.\n */\n dedupe?: FrameDedupeOptions;\n /**\n * How the video reaches the model. Default `\"auto\"`. See `VideoDeliveryMode`.\n */\n mode?: VideoDeliveryMode;\n}\n\n/**\n * How a video input is delivered to the model.\n *\n * - `\"auto\"` (default): send the video itself when the provider accepts video\n * natively (Google models), otherwise sample frames with ffmpeg.\n * - `\"native\"`: always send the video itself. Throws `VisualAIConfigError`\n * when the provider has no native video support.\n * - `\"frames\"`: always sample frames with ffmpeg, whatever the provider.\n *\n * Native delivery still probes the duration and enforces `maxDurationSeconds`\n * before any provider call, and passes `fps` on as the provider's sampling\n * rate. `maxFrames` and `dedupe` apply to frame sampling only. Pre-sampled\n * `FramesInput` is always sent as frames.\n */\nexport type VideoDeliveryMode = \"auto\" | \"native\" | \"frames\";\n\n/**\n * Controls dropping of frames that did not visibly change from the previous\n * kept frame, so a static screen does not cost input tokens for every sample.\n *\n * - `true` (default): drop unchanged frames using the default threshold.\n * - `false`: send every sampled frame.\n * - `{ threshold }`: the fraction `(0, 1]` of a frame's pixels that must differ\n * from the last kept frame for it to count as changed. Default `0.001` (0.1%,\n * roughly a 37x37 px region on a 1568x880 frame). Lower it to keep smaller\n * changes; raise it to ignore more.\n *\n * Each frame is compared against the most recently *kept* frame, so gradual\n * drift accumulates and is eventually kept. The first frame is always kept.\n * Pixel-level compression noise and tiny flickers such as a blinking text\n * caret fall below the default threshold.\n */\nexport type FrameDedupeOptions = boolean | { threshold?: number };\n\n/** Internal normalized video passed to a provider driver's `sendVideoMessage`. */\nexport interface NormalizedVideo {\n readonly data: Buffer;\n readonly mimeType: SupportedVideoMimeType;\n /** Probed duration in seconds; already checked against `maxDurationSeconds`. */\n readonly durationSeconds: number;\n /** Sampling rate to request from the provider, in frames per second. */\n readonly fps: 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 });\n/**\n * What `ask()` requests from strict structured-output providers, one schema per\n * media kind so the schema never requires a field its prompt does not mention.\n * The image prompt describes only `summary` and `issues`; requiring the\n * video-only `frameReferences` as well made gpt-6.1-sol and gpt-6-astra stall\n * in endless whitespace after `issues` until `max_output_tokens` ran out (7 of\n * 20 and 3 of 12 calls, against 0 of 60 and 0 of 12 without it). Sampled-frame\n * video prompts ask for `frameReferences` and native video prompts for\n * `timestampReferences`, so each keeps only its own. Parsing still uses\n * `AskResponseSchema`, where both fields are optional.\n */\nexport const AskImageResponseSchema = AskResponseSchema.omit({\n frameReferences: true,\n timestampReferences: true,\n});\nexport const AskFramesResponseSchema = AskResponseSchema.omit({ timestampReferences: true });\nexport const AskNativeVideoResponseSchema = AskResponseSchema.omit({ frameReferences: true });\nexport const CompareResponseSchema = CompareResultSchema.omit({ usage: true });\n\n/**\n * Control characters other than tab, newline and carriage return. Nothing a\n * model says about a screenshot legitimately contains them, but two routes have\n * been observed emitting them anyway:\n *\n * - Kimi via OpenRouter writes them raw inside string literals, which strict\n * JSON.parse rejects outright, failing an otherwise good response.\n * - Muse Spark via OpenRouter mangles non-ASCII characters into escaped control\n * characters: \"−\" arrives as \\u0002 followed by the digits, \"é\" as \\u0000\n * followed by \"e9\". Those parse cleanly and then reach the caller as garbage\n * inside `statements[].statement`, where Zod cannot catch them because any\n * string validates.\n *\n * The original character cannot be recovered, so it is dropped. That leaves\n * \"20%\" rather than \"−20%\", which is wrong but printable, searchable, and safe\n * to log; the control byte was none of those.\n */\n// eslint-disable-next-line no-control-regex\nconst STRAY_CONTROL_CHARS = /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/g;\n\n/**\n * Parse JSON, retrying once with raw control characters replaced by spaces.\n * The retry is safe: between tokens a control character is whitespace, and\n * inside a string it stands in for text that was already unreadable.\n */\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown;\n } catch (first) {\n try {\n // eslint-disable-next-line no-control-regex\n return JSON.parse(text.replace(/[\\u0000-\\u001f]/g, \" \")) as unknown;\n } catch {\n throw first;\n }\n }\n}\n\n/** Drop stray control characters from every string in a parsed JSON value. */\nexport function stripControlCharacters<T>(value: T): T {\n if (typeof value === \"string\") return value.replace(STRAY_CONTROL_CHARS, \"\") as T;\n if (Array.isArray(value)) return value.map((item: unknown) => stripControlCharacters(item)) as T;\n if (value !== null && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n out[key] = stripControlCharacters(item);\n }\n return out as T;\n }\n return value;\n}\n\nfunction parseResponse<T>(raw: string, schema: z.ZodType<T>): T {\n let parsed: unknown;\n try {\n parsed = stripControlCharacters(parseJson(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 const result = parseResponse(raw, AskResponseSchema);\n return {\n ...result,\n frameReferences: result.frameReferences ?? undefined,\n timestampReferences: result.timestampReferences ?? undefined,\n };\n}\n\nexport function parseCompareResponse(raw: string): Omit<CompareResult, \"usage\"> {\n return parseResponse(raw, CompareResponseSchema);\n}\n","import { 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 FramesInput,\n ImageInput,\n LayoutOptions,\n MediaInput,\n NativeVideoMetadata,\n NormalizedImage,\n NormalizedVideo,\n PageLoadOptions,\n ProviderName,\n VideoFramesMetadata,\n VideoSamplingOptions,\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 { OpenRouterDriver } from \"../providers/openrouter.js\";\nimport type {\n ProviderConfig,\n ProviderDriver,\n RawProviderResponse,\n SendMessageOptions,\n} from \"../providers/types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport {\n debugLog,\n processUsage,\n timedSendMessage,\n timedSendVideoMessage,\n withErrorDebug,\n} from \"./debug.js\";\nimport { DIFF_ALLOWED_MODELS, generateAiDiff } from \"./diff.js\";\nimport { normalizeImage } from \"./image.js\";\nimport { isFramesInput, isVideoInput, normalizeMedia, type NormalizedMedia } from \"./media.js\";\nimport {\n buildAskPrompt,\n buildCheckPrompt,\n buildComparePrompt,\n type MediaContext,\n} from \"./prompt.js\";\nimport {\n AskFramesResponseSchema,\n AskImageResponseSchema,\n AskNativeVideoResponseSchema,\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, base64, Buffer) and statements pass if they\n * are true at any point, with each statement result carrying the timestamp\n * where it matched. On providers that accept video natively (Google models)\n * the video itself is sent and the result's `video` metadata describes the\n * delivery; elsewhere the client samples frames with ffmpeg and the `frames`\n * metadata reports which timestamps the model saw. Control this with\n * `video.mode`. Pass a `FramesInput` (`{ frames, fps? }`) to supply\n * pre-sampled frames directly — handled identically to a sampled timeline but\n * without loading ffmpeg.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string, or a `FramesInput` of pre-sampled frames.\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 | FramesInput,\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 analyzed as a chronological timeline. On providers that\n * accept video natively (Google models) the video itself is sent and the\n * result's `timestampReferences` array surfaces the moments the model relied\n * on; elsewhere frames are sampled with ffmpeg and `frameReferences` indexes\n * into `frames.timestampsSeconds`. Control this with `video.mode`. Pass a\n * `FramesInput` (`{ frames, fps? }`) to supply pre-sampled frames directly —\n * handled identically to a sampled timeline but without loading ffmpeg.\n *\n * @param input Image or video source as a buffer, URL, file path, or base64 string, or a `FramesInput` of pre-sampled frames.\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 | FramesInput, 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 flash models (`DIFF_ALLOWED_MODELS`) generate an annotated diff image\n * by default; pass `{ diffImage: false }` to opt out. Flash-Lite and Pro tiers\n * produce none even when asked.\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 openrouter: (config) => new OpenRouterDriver(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 askSchemaOptionsByMedia = {\n image: toSchemaOptions(AskImageResponseSchema),\n video: toSchemaOptions(AskFramesResponseSchema),\n \"native-video\": toSchemaOptions(AskNativeVideoResponseSchema),\n} as const satisfies Record<MediaContext[\"kind\"], SendMessageOptions>;\nconst compareSchemaOptions = toSchemaOptions(CompareResponseSchema);\n\n/** Media-derived fields spread onto a `check()` / `ask()` result. */\ninterface MediaResultMetadata {\n frames?: VideoFramesMetadata;\n video?: NativeVideoMetadata;\n}\n\ntype MediaDispatch =\n | {\n kind: \"images\";\n images: NormalizedImage[];\n mediaContext: MediaContext;\n frames: VideoFramesMetadata | undefined;\n }\n | { kind: \"native-video\"; video: NormalizedVideo; mediaContext: MediaContext };\n\nfunction mediaToProviderInputs(media: NormalizedMedia): MediaDispatch {\n if (media.kind === \"image\") {\n return {\n kind: \"images\",\n images: [media.image],\n mediaContext: { kind: \"image\" },\n frames: undefined,\n };\n }\n if (media.kind === \"native-video\") {\n return {\n kind: \"native-video\",\n video: media.video,\n mediaContext: { kind: \"native-video\", durationSeconds: media.video.durationSeconds },\n };\n }\n const timestamps = media.frames.map((f) => f.timestampSeconds);\n return {\n kind: \"images\",\n images: media.frames,\n mediaContext: {\n kind: \"video\",\n frameTimestamps: timestamps,\n durationSeconds: media.durationSeconds,\n droppedUnchanged: media.droppedUnchanged,\n },\n frames: {\n count: media.frames.length,\n timestampsSeconds: timestamps,\n durationSeconds: media.durationSeconds,\n droppedUnchanged: media.droppedUnchanged,\n },\n };\n}\n\n/** Sends the dispatched media through the matching driver method and returns the metadata for the result. */\nasync function sendMedia(\n driver: ProviderDriver,\n dispatch: MediaDispatch,\n prompt: string,\n options: SendMessageOptions,\n): Promise<{\n response: RawProviderResponse & { durationSeconds: number };\n metadata: MediaResultMetadata;\n}> {\n if (dispatch.kind === \"native-video\") {\n const response = await timedSendVideoMessage(driver, dispatch.video, prompt, options);\n const { durationSeconds, fps, mimeType } = dispatch.video;\n return {\n response,\n metadata: { video: { durationSeconds, fps, mimeType, delivery: response.delivery } },\n };\n }\n const response = await timedSendMessage(driver, dispatch.images, prompt, options);\n return { response, metadata: dispatch.frames ? { frames: dispatch.frames } : {} };\n}\n\n/**\n * Decides whether a video input should be delivered natively, honouring\n * `video.mode`. `\"native\"` on a provider without support throws, but only\n * when the input really is a video, so images and pre-sampled frames pass\n * through untouched whatever the mode says.\n */\nfunction resolveNativeVideo(\n input: MediaInput | FramesInput,\n videoOptions: VideoSamplingOptions | undefined,\n driver: ProviderDriver,\n provider: ProviderName,\n): boolean {\n // Widened to string so a value outside the union still gets a clear error at runtime.\n const mode: string = videoOptions?.mode ?? \"auto\";\n if (mode !== \"auto\" && mode !== \"native\" && mode !== \"frames\") {\n throw new VisualAIConfigError(\n `Invalid video mode: ${mode}. Expected \"auto\", \"native\", or \"frames\".`,\n );\n }\n const supported = typeof driver.sendVideoMessage === \"function\";\n if (mode === \"frames\") return false;\n if (mode === \"auto\") return supported;\n if (!supported && !isFramesInput(input) && isVideoInput(input)) {\n throw new VisualAIConfigError(\n `Native video delivery is not supported by the \"${provider}\" provider. ` +\n `Use a Google model, or set video: { mode: \"frames\" } to sample frames instead.`,\n );\n }\n return true;\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 imageDetail: resolvedConfig.imageDetail,\n timeout: resolvedConfig.timeout,\n };\n const driver = createDriver(resolvedConfig.provider, driverConfig);\n // Longest-edge pixel cap applied to every image/frame before it reaches a driver.\n const maxImageDimension = resolvedConfig.maxImageDimension;\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, maxImageDimension);\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 nativeVideo = resolveNativeVideo(\n input,\n options?.video,\n driver,\n resolvedConfig.provider,\n );\n const media = await normalizeMedia(input, options?.video, maxImageDimension, nativeVideo);\n const dispatch = mediaToProviderInputs(media);\n const prompt = buildCheckPrompt(stmts, {\n instructions: options?.instructions,\n media: dispatch.mediaContext,\n });\n debugLog(resolvedConfig, \"check prompt\", prompt, \"prompt\");\n\n const { response, metadata } = await sendMedia(\n driver,\n dispatch,\n prompt,\n checkSchemaOptions,\n );\n debugLog(resolvedConfig, \"check response\", response.text, \"response\");\n\n const result = parseCheckResponse(response.text);\n return {\n ...result,\n ...metadata,\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 nativeVideo = resolveNativeVideo(\n input,\n options?.video,\n driver,\n resolvedConfig.provider,\n );\n const media = await normalizeMedia(input, options?.video, maxImageDimension, nativeVideo);\n const dispatch = mediaToProviderInputs(media);\n const prompt = buildAskPrompt(userPrompt, {\n instructions: options?.instructions,\n media: dispatch.mediaContext,\n });\n debugLog(resolvedConfig, \"ask prompt\", prompt, \"prompt\");\n\n const { response, metadata } = await sendMedia(\n driver,\n dispatch,\n prompt,\n askSchemaOptionsByMedia[dispatch.mediaContext.kind],\n );\n debugLog(resolvedConfig, \"ask response\", response.text, \"response\");\n\n const result = parseAskResponse(response.text);\n return {\n ...result,\n ...metadata,\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([\n normalizeImage(imageA, maxImageDimension),\n normalizeImage(imageB, maxImageDimension),\n ]);\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 // Every Gemini flash model returns annotated diffs via code execution, so\n // the auto-trigger follows the allowlist rather than naming one model.\n // Flash-Lite and Pro tiers are absent from it and so never auto-enable.\n const supportsAnnotatedDiff =\n resolvedConfig.provider === \"google\" && DIFF_ALLOWED_MODELS.has(resolvedConfig.model);\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, maxImageDimension);\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, maxImageDimension);\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, maxImageDimension);\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, maxImageDimension);\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","resolve","detail","sharp","width","height","sharp","readFile","sharp","sharp","readFile","writeFile","extname","join","extname","buf","mimeType","join","writeFile","resolve","readFile","readFile","response","header"]}