visual-ai-assertions 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/index.cjs +169 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +169 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -387,11 +387,14 @@ The `VisualAIKnownError` union and `isVisualAIKnownError()` helper are useful wh
|
|
|
387
387
|
|
|
388
388
|
### Optional Configuration
|
|
389
389
|
|
|
390
|
-
| Variable
|
|
391
|
-
|
|
|
392
|
-
| `VISUAL_AI_MODEL`
|
|
393
|
-
| `VISUAL_AI_DEBUG`
|
|
394
|
-
| `
|
|
390
|
+
| Variable | Description |
|
|
391
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
392
|
+
| `VISUAL_AI_MODEL` | Default model when `model` is not set in config. Overrides the provider's default model. |
|
|
393
|
+
| `VISUAL_AI_DEBUG` | Enable error diagnostic logging to stderr. Does **not** enable prompt/response logging. Use `"true"` or `"1"`. |
|
|
394
|
+
| `VISUAL_AI_DEBUG_PROMPT` | Enable prompt-only debug logging to stderr. Use `"true"` or `"1"`. |
|
|
395
|
+
| `VISUAL_AI_DEBUG_RESPONSE` | Enable response-only debug logging to stderr. Use `"true"` or `"1"`. |
|
|
396
|
+
| `VISUAL_AI_REASONING_EFFORT` | Default reasoning effort when `reasoningEffort` is not set in config. Use `"low"`, `"medium"`, `"high"`, or `"xhigh"`. |
|
|
397
|
+
| `VISUAL_AI_TRACK_USAGE` | Enable usage tracking (token counts and cost) to stderr. Use `"true"` or `"1"`. |
|
|
395
398
|
|
|
396
399
|
## Configuration
|
|
397
400
|
|
|
@@ -399,7 +402,9 @@ The `VisualAIKnownError` union and `isVisualAIKnownError()` helper are useful wh
|
|
|
399
402
|
| ----------------- | ------- | ---------------- | ----------------------------------------------------------------------------- |
|
|
400
403
|
| `apiKey` | string | env var | API key for the provider |
|
|
401
404
|
| `model` | string | provider default | Model to use |
|
|
402
|
-
| `debug` | boolean | `false` |
|
|
405
|
+
| `debug` | boolean | `false` | Enable error diagnostic logging to stderr |
|
|
406
|
+
| `debugPrompt` | boolean | `false` | Log prompts to stderr |
|
|
407
|
+
| `debugResponse` | boolean | `false` | Log responses to stderr |
|
|
403
408
|
| `maxTokens` | number | `4096` | Max tokens for AI response |
|
|
404
409
|
| `reasoningEffort` | string | `undefined` | `"low"` `"medium"` `"high"` `"xhigh"` — controls how deeply the model reasons |
|
|
405
410
|
| `trackUsage` | boolean | `false` | Log token usage and estimated cost to stderr |
|
package/dist/index.cjs
CHANGED
|
@@ -100,6 +100,11 @@ var MODEL_TO_PROVIDER = new Map([
|
|
|
100
100
|
...Object.values(Model.Google).map((m) => [m, Provider.GOOGLE])
|
|
101
101
|
]);
|
|
102
102
|
var VALID_PROVIDERS = Object.values(Provider);
|
|
103
|
+
var PROVIDER_DEFAULT_REASONING = {
|
|
104
|
+
openai: "medium",
|
|
105
|
+
anthropic: "off",
|
|
106
|
+
google: "off"
|
|
107
|
+
};
|
|
103
108
|
var Content = {
|
|
104
109
|
/** Detects Lorem ipsum, TODO, TBD, and similar placeholder text */
|
|
105
110
|
PLACEHOLDER_TEXT: "placeholder-text",
|
|
@@ -752,16 +757,38 @@ function parseBooleanEnv(envName, value) {
|
|
|
752
757
|
`Invalid ${envName} value: "${value}". Use "true", "1", "false", or "0".`
|
|
753
758
|
);
|
|
754
759
|
}
|
|
760
|
+
var VALID_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
761
|
+
function parseReasoningEffortEnv(envName, value) {
|
|
762
|
+
if (value === void 0 || value === "") return void 0;
|
|
763
|
+
const lower = value.toLowerCase();
|
|
764
|
+
if (VALID_REASONING_EFFORTS.includes(lower)) return lower;
|
|
765
|
+
throw new VisualAIConfigError(
|
|
766
|
+
`Invalid ${envName} value: "${value}". Use "low", "medium", "high", or "xhigh".`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
var debugDeprecationWarned = false;
|
|
755
770
|
function resolveConfig(config) {
|
|
756
771
|
const provider = resolveProvider(config);
|
|
757
772
|
const model = config.model ?? process.env.VISUAL_AI_MODEL ?? DEFAULT_MODELS[provider];
|
|
773
|
+
const debug = config.debug ?? parseBooleanEnv("VISUAL_AI_DEBUG", process.env.VISUAL_AI_DEBUG) ?? false;
|
|
774
|
+
const debugPrompt = config.debugPrompt ?? parseBooleanEnv("VISUAL_AI_DEBUG_PROMPT", process.env.VISUAL_AI_DEBUG_PROMPT) ?? false;
|
|
775
|
+
const debugResponse = config.debugResponse ?? parseBooleanEnv("VISUAL_AI_DEBUG_RESPONSE", process.env.VISUAL_AI_DEBUG_RESPONSE) ?? false;
|
|
776
|
+
if (debug && !debugPrompt && !debugResponse && !debugDeprecationWarned) {
|
|
777
|
+
debugDeprecationWarned = true;
|
|
778
|
+
process.stderr.write(
|
|
779
|
+
`[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.
|
|
780
|
+
`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
758
783
|
return {
|
|
759
784
|
provider,
|
|
760
785
|
apiKey: config.apiKey,
|
|
761
786
|
model,
|
|
762
787
|
maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
763
|
-
reasoningEffort: config.reasoningEffort,
|
|
764
|
-
debug
|
|
788
|
+
reasoningEffort: config.reasoningEffort ?? parseReasoningEffortEnv("VISUAL_AI_REASONING_EFFORT", process.env.VISUAL_AI_REASONING_EFFORT),
|
|
789
|
+
debug,
|
|
790
|
+
debugPrompt,
|
|
791
|
+
debugResponse,
|
|
765
792
|
trackUsage: config.trackUsage ?? parseBooleanEnv("VISUAL_AI_TRACK_USAGE", process.env.VISUAL_AI_TRACK_USAGE) ?? false
|
|
766
793
|
};
|
|
767
794
|
}
|
|
@@ -822,8 +849,9 @@ function calculateCost(provider, model, inputTokens, outputTokens) {
|
|
|
822
849
|
}
|
|
823
850
|
|
|
824
851
|
// src/core/debug.ts
|
|
825
|
-
function debugLog(config, label, data) {
|
|
826
|
-
|
|
852
|
+
function debugLog(config, label, data, kind = "error") {
|
|
853
|
+
const enabled = kind === "prompt" ? config.debugPrompt : kind === "response" ? config.debugResponse : config.debug;
|
|
854
|
+
if (enabled) {
|
|
827
855
|
process.stderr.write(`[visual-ai-assertions] ${label}: ${data}
|
|
828
856
|
`);
|
|
829
857
|
}
|
|
@@ -831,8 +859,9 @@ function debugLog(config, label, data) {
|
|
|
831
859
|
function usageLog(config, method, usage) {
|
|
832
860
|
if (!config.trackUsage) return;
|
|
833
861
|
const costStr = usage.estimatedCost !== void 0 ? `$${usage.estimatedCost.toFixed(6)}` : "unknown";
|
|
862
|
+
const reasoningStr = config.reasoningEffort ? `reasoning: ${config.reasoningEffort}` : `reasoning: ${PROVIDER_DEFAULT_REASONING[config.provider]} (provider default)`;
|
|
834
863
|
process.stderr.write(
|
|
835
|
-
`[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? "0.000"}s [${config.model}]
|
|
864
|
+
`[visual-ai-assertions] ${method} usage: ${usage.inputTokens} input + ${usage.outputTokens} output tokens (${costStr}) in ${usage.durationSeconds?.toFixed(3) ?? "0.000"}s [${config.model}, ${reasoningStr}]
|
|
836
865
|
`
|
|
837
866
|
);
|
|
838
867
|
}
|
|
@@ -848,6 +877,28 @@ function processUsage(method, rawUsage, durationSeconds, config) {
|
|
|
848
877
|
usageLog(config, method, usage);
|
|
849
878
|
return usage;
|
|
850
879
|
}
|
|
880
|
+
var MAX_RAW_RESPONSE_PREVIEW = 500;
|
|
881
|
+
function formatError(error) {
|
|
882
|
+
if (error instanceof VisualAIResponseParseError) {
|
|
883
|
+
const truncated = error.rawResponse.length > MAX_RAW_RESPONSE_PREVIEW ? error.rawResponse.slice(0, MAX_RAW_RESPONSE_PREVIEW) + "..." : error.rawResponse;
|
|
884
|
+
return `${error.name} (${error.code}): ${error.message}. Raw (truncated): ${truncated}`;
|
|
885
|
+
}
|
|
886
|
+
if (error instanceof VisualAIError) {
|
|
887
|
+
return `${error.name} (${error.code}): ${error.message}`;
|
|
888
|
+
}
|
|
889
|
+
if (error instanceof Error) {
|
|
890
|
+
return `${error.name}: ${error.message}`;
|
|
891
|
+
}
|
|
892
|
+
return String(error);
|
|
893
|
+
}
|
|
894
|
+
async function withErrorDebug(config, method, fn) {
|
|
895
|
+
try {
|
|
896
|
+
return await fn();
|
|
897
|
+
} catch (error) {
|
|
898
|
+
debugLog(config, `${method} error`, formatError(error), "error");
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
851
902
|
async function timedSendMessage(driver, images, prompt) {
|
|
852
903
|
const start = performance.now();
|
|
853
904
|
const response = await driver.sendMessage(images, prompt);
|
|
@@ -1176,16 +1227,18 @@ function visualAI(config = {}) {
|
|
|
1176
1227
|
if (elements.length === 0) {
|
|
1177
1228
|
throw new VisualAIConfigError(`At least one element is required for ${methodName}()`);
|
|
1178
1229
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1230
|
+
return withErrorDebug(resolvedConfig, methodName, async () => {
|
|
1231
|
+
const img = await normalizeImage(image);
|
|
1232
|
+
const prompt = buildElementsVisibilityPrompt(elements, visible, options);
|
|
1233
|
+
debugLog(resolvedConfig, `${methodName} prompt`, prompt, "prompt");
|
|
1234
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1235
|
+
debugLog(resolvedConfig, `${methodName} response`, response.text, "response");
|
|
1236
|
+
const result = parseCheckResponse(response.text);
|
|
1237
|
+
return {
|
|
1238
|
+
...result,
|
|
1239
|
+
usage: processUsage(methodName, response.usage, response.durationSeconds, resolvedConfig)
|
|
1240
|
+
};
|
|
1241
|
+
});
|
|
1189
1242
|
}
|
|
1190
1243
|
return {
|
|
1191
1244
|
async check(image, statements, options) {
|
|
@@ -1193,61 +1246,64 @@ function visualAI(config = {}) {
|
|
|
1193
1246
|
if (stmts.length === 0) {
|
|
1194
1247
|
throw new VisualAIConfigError("At least one statement is required for check()");
|
|
1195
1248
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1249
|
+
return withErrorDebug(resolvedConfig, "check", async () => {
|
|
1250
|
+
const img = await normalizeImage(image);
|
|
1251
|
+
const prompt = buildCheckPrompt(stmts, { instructions: options?.instructions });
|
|
1252
|
+
debugLog(resolvedConfig, "check prompt", prompt, "prompt");
|
|
1253
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1254
|
+
debugLog(resolvedConfig, "check response", response.text, "response");
|
|
1255
|
+
const result = parseCheckResponse(response.text);
|
|
1256
|
+
return {
|
|
1257
|
+
...result,
|
|
1258
|
+
usage: processUsage("check", response.usage, response.durationSeconds, resolvedConfig)
|
|
1259
|
+
};
|
|
1260
|
+
});
|
|
1206
1261
|
},
|
|
1207
1262
|
async ask(image, userPrompt, options) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1263
|
+
return withErrorDebug(resolvedConfig, "ask", async () => {
|
|
1264
|
+
const img = await normalizeImage(image);
|
|
1265
|
+
const prompt = buildAskPrompt(userPrompt, { instructions: options?.instructions });
|
|
1266
|
+
debugLog(resolvedConfig, "ask prompt", prompt, "prompt");
|
|
1267
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1268
|
+
debugLog(resolvedConfig, "ask response", response.text, "response");
|
|
1269
|
+
const result = parseAskResponse(response.text);
|
|
1270
|
+
return {
|
|
1271
|
+
...result,
|
|
1272
|
+
usage: processUsage("ask", response.usage, response.durationSeconds, resolvedConfig)
|
|
1273
|
+
};
|
|
1274
|
+
});
|
|
1218
1275
|
},
|
|
1219
1276
|
async compare(imageA, imageB, options) {
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
if (!resolvedConfig.debug) {
|
|
1277
|
+
return withErrorDebug(resolvedConfig, "compare", async () => {
|
|
1278
|
+
const [imgA, imgB] = await Promise.all([normalizeImage(imageA), normalizeImage(imageB)]);
|
|
1279
|
+
const prompt = buildComparePrompt({
|
|
1280
|
+
userPrompt: options?.prompt,
|
|
1281
|
+
instructions: options?.instructions
|
|
1282
|
+
});
|
|
1283
|
+
debugLog(resolvedConfig, "compare prompt", prompt, "prompt");
|
|
1284
|
+
const response = await timedSendMessage(driver, [imgA, imgB], prompt);
|
|
1285
|
+
debugLog(resolvedConfig, "compare response", response.text, "response");
|
|
1286
|
+
const supportsAnnotatedDiff = resolvedConfig.provider === "google" && resolvedConfig.model === Model.Google.GEMINI_3_FLASH_PREVIEW;
|
|
1287
|
+
const effectiveDiffImage = options?.diffImage ?? (supportsAnnotatedDiff ? true : false);
|
|
1288
|
+
let diffImage;
|
|
1289
|
+
if (effectiveDiffImage) {
|
|
1290
|
+
try {
|
|
1291
|
+
diffImage = await generateAiDiff(imgA, imgB, resolvedConfig.model, driver);
|
|
1292
|
+
} catch (err) {
|
|
1293
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1238
1294
|
process.stderr.write(
|
|
1239
1295
|
`[visual-ai-assertions] warning: diff generation failed: ${msg}
|
|
1240
1296
|
`
|
|
1241
1297
|
);
|
|
1242
1298
|
}
|
|
1243
1299
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
};
|
|
1300
|
+
const result = parseCompareResponse(response.text);
|
|
1301
|
+
return {
|
|
1302
|
+
...result,
|
|
1303
|
+
...diffImage ? { diffImage } : {},
|
|
1304
|
+
usage: processUsage("compare", response.usage, response.durationSeconds, resolvedConfig)
|
|
1305
|
+
};
|
|
1306
|
+
});
|
|
1251
1307
|
},
|
|
1252
1308
|
elementsVisible(image, elements, options) {
|
|
1253
1309
|
return checkElementsVisibility(image, elements, true, options);
|
|
@@ -1256,57 +1312,65 @@ function visualAI(config = {}) {
|
|
|
1256
1312
|
return checkElementsVisibility(image, elements, false, options);
|
|
1257
1313
|
},
|
|
1258
1314
|
async accessibility(image, options) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1315
|
+
return withErrorDebug(resolvedConfig, "accessibility", async () => {
|
|
1316
|
+
const img = await normalizeImage(image);
|
|
1317
|
+
const prompt = buildAccessibilityPrompt(options);
|
|
1318
|
+
debugLog(resolvedConfig, "accessibility prompt", prompt, "prompt");
|
|
1319
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1320
|
+
debugLog(resolvedConfig, "accessibility response", response.text, "response");
|
|
1321
|
+
const result = parseCheckResponse(response.text);
|
|
1322
|
+
return {
|
|
1323
|
+
...result,
|
|
1324
|
+
usage: processUsage(
|
|
1325
|
+
"accessibility",
|
|
1326
|
+
response.usage,
|
|
1327
|
+
response.durationSeconds,
|
|
1328
|
+
resolvedConfig
|
|
1329
|
+
)
|
|
1330
|
+
};
|
|
1331
|
+
});
|
|
1274
1332
|
},
|
|
1275
1333
|
async layout(image, options) {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1334
|
+
return withErrorDebug(resolvedConfig, "layout", async () => {
|
|
1335
|
+
const img = await normalizeImage(image);
|
|
1336
|
+
const prompt = buildLayoutPrompt(options);
|
|
1337
|
+
debugLog(resolvedConfig, "layout prompt", prompt, "prompt");
|
|
1338
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1339
|
+
debugLog(resolvedConfig, "layout response", response.text, "response");
|
|
1340
|
+
const result = parseCheckResponse(response.text);
|
|
1341
|
+
return {
|
|
1342
|
+
...result,
|
|
1343
|
+
usage: processUsage("layout", response.usage, response.durationSeconds, resolvedConfig)
|
|
1344
|
+
};
|
|
1345
|
+
});
|
|
1286
1346
|
},
|
|
1287
1347
|
async pageLoad(image, options) {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1348
|
+
return withErrorDebug(resolvedConfig, "pageLoad", async () => {
|
|
1349
|
+
const img = await normalizeImage(image);
|
|
1350
|
+
const prompt = buildPageLoadPrompt(options);
|
|
1351
|
+
debugLog(resolvedConfig, "pageLoad prompt", prompt, "prompt");
|
|
1352
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1353
|
+
debugLog(resolvedConfig, "pageLoad response", response.text, "response");
|
|
1354
|
+
const result = parseCheckResponse(response.text);
|
|
1355
|
+
return {
|
|
1356
|
+
...result,
|
|
1357
|
+
usage: processUsage("pageLoad", response.usage, response.durationSeconds, resolvedConfig)
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1298
1360
|
},
|
|
1299
1361
|
async content(image, options) {
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1362
|
+
return withErrorDebug(resolvedConfig, "content", async () => {
|
|
1363
|
+
const img = await normalizeImage(image);
|
|
1364
|
+
const prompt = buildContentPrompt(options);
|
|
1365
|
+
debugLog(resolvedConfig, "content prompt", prompt, "prompt");
|
|
1366
|
+
const response = await timedSendMessage(driver, [img], prompt);
|
|
1367
|
+
debugLog(resolvedConfig, "content response", response.text, "response");
|
|
1368
|
+
const result = parseCheckResponse(response.text);
|
|
1369
|
+
return {
|
|
1370
|
+
...result,
|
|
1371
|
+
usage: processUsage("content", response.usage, response.durationSeconds, resolvedConfig)
|
|
1372
|
+
};
|
|
1373
|
+
});
|
|
1310
1374
|
}
|
|
1311
1375
|
};
|
|
1312
1376
|
}
|