@juspay/neurolink 10.10.12 → 10.11.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/adapters/imageFormatSupport.d.ts +75 -0
  3. package/dist/adapters/imageFormatSupport.js +283 -0
  4. package/dist/adapters/video/ffmpegAdapter.d.ts +6 -0
  5. package/dist/adapters/video/ffmpegAdapter.js +1 -1
  6. package/dist/browser/neurolink.min.js +399 -398
  7. package/dist/lib/adapters/imageFormatSupport.d.ts +75 -0
  8. package/dist/lib/adapters/imageFormatSupport.js +284 -0
  9. package/dist/lib/adapters/video/ffmpegAdapter.d.ts +6 -0
  10. package/dist/lib/adapters/video/ffmpegAdapter.js +1 -1
  11. package/dist/lib/processors/config/fileExtensions.d.ts +32 -15
  12. package/dist/lib/processors/config/fileExtensions.js +27 -66
  13. package/dist/lib/processors/config/fileTypeRegistry.d.ts +106 -0
  14. package/dist/lib/processors/config/fileTypeRegistry.js +702 -0
  15. package/dist/lib/processors/config/index.d.ts +2 -1
  16. package/dist/lib/processors/config/index.js +5 -1
  17. package/dist/lib/processors/config/mimeConstants.d.ts +22 -7
  18. package/dist/lib/processors/config/mimeConstants.js +45 -66
  19. package/dist/lib/processors/media/AudioProcessor.js +16 -38
  20. package/dist/lib/processors/media/VideoProcessor.js +11 -32
  21. package/dist/lib/providers/googleAiStudio/client.js +12 -1
  22. package/dist/lib/providers/googleVertex/client.js +123 -66
  23. package/dist/lib/types/file.d.ts +41 -0
  24. package/dist/lib/utils/fileDetector.js +367 -251
  25. package/dist/lib/utils/imageProcessor.js +14 -17
  26. package/dist/lib/utils/markupSniff.d.ts +37 -0
  27. package/dist/lib/utils/markupSniff.js +125 -0
  28. package/dist/lib/utils/messageBuilder.d.ts +14 -0
  29. package/dist/lib/utils/messageBuilder.js +172 -92
  30. package/dist/processors/config/fileExtensions.d.ts +32 -15
  31. package/dist/processors/config/fileExtensions.js +27 -66
  32. package/dist/processors/config/fileTypeRegistry.d.ts +106 -0
  33. package/dist/processors/config/fileTypeRegistry.js +701 -0
  34. package/dist/processors/config/index.d.ts +2 -1
  35. package/dist/processors/config/index.js +5 -1
  36. package/dist/processors/config/mimeConstants.d.ts +22 -7
  37. package/dist/processors/config/mimeConstants.js +45 -66
  38. package/dist/processors/media/AudioProcessor.js +16 -38
  39. package/dist/processors/media/VideoProcessor.js +11 -32
  40. package/dist/providers/googleAiStudio/client.js +12 -1
  41. package/dist/providers/googleVertex/client.js +123 -66
  42. package/dist/types/file.d.ts +41 -0
  43. package/dist/utils/fileDetector.js +367 -251
  44. package/dist/utils/imageProcessor.js +14 -17
  45. package/dist/utils/markupSniff.d.ts +37 -0
  46. package/dist/utils/markupSniff.js +124 -0
  47. package/dist/utils/messageBuilder.d.ts +14 -0
  48. package/dist/utils/messageBuilder.js +172 -92
  49. package/package.json +3 -2
@@ -5,16 +5,18 @@ import path from "path";
5
5
  import os from "os";
6
6
  import { AIProviderName, ErrorCategory, ErrorSeverity, } from "../../constants/enums.js";
7
7
  import { BaseProvider } from "../../core/baseProvider.js";
8
+ import { unwrapImagePayload } from "../../adapters/imageFormatSupport.js";
9
+ import { getMimeTypeForExtension } from "../../processors/config/mimeConstants.js";
8
10
  import { DEFAULT_GEMINI_STREAM_TIMEOUT_MS, DEFAULT_MAX_STEPS, DEFAULT_TOOL_EXECUTION_TIMEOUT_MS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, IMAGE_GENERATION_MODELS, TOOL_STORAGE_TIMEOUT_MS, } from "../../core/constants.js";
9
11
  import { ModelConfigurationManager } from "../../core/modelConfiguration.js";
10
12
  import { isSchemaComplexityError } from "../../core/modules/structuredOutputPolicy.js";
11
- import { stringifyContentSafe } from "../../utils/logSanitize.js";
13
+ import { redactUrlForError, stringifyContentSafe, } from "../../utils/logSanitize.js";
12
14
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
13
15
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
14
16
  import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
15
17
  import { applyVertexAnthropicCacheBreakpoints } from "../../utils/anthropicCacheBreakpoints.js";
16
18
  import { FileDetector } from "../../utils/fileDetector.js";
17
- import { mergeMediaFileAliases, processUnifiedFilesArray, } from "../../utils/messageBuilder.js";
19
+ import { mergeMediaFileAliases, normalizeVisionImageFormats, processUnifiedFilesArray, } from "../../utils/messageBuilder.js";
18
20
  import { logger } from "../../utils/logger.js";
19
21
  import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
20
22
  import { ANTHROPIC_ELISION_NOTE, planAnthropicLoopReclaim, previewAnthropicToolResultText, } from "../../context/anthropicLoopGuard.js";
@@ -1035,16 +1037,19 @@ export class GoogleVertexProvider extends BaseProvider {
1035
1037
  if (options.input) {
1036
1038
  mergeMediaFileAliases(options.input);
1037
1039
  }
1038
- if (!options.input?.files?.length) {
1039
- return;
1040
- }
1041
- try {
1042
- // Mutates options.input.text / .images / .pdfFiles in place.
1043
- await processUnifiedFilesArray(options, 100 * 1024 * 1024, this.providerName);
1044
- }
1045
- catch (fileError) {
1046
- logger.warn(`[GoogleVertex] processUnifiedFilesArray threw, continuing without file content: ${fileError instanceof Error ? fileError.message : String(fileError)}`);
1040
+ if (options.input?.files?.length) {
1041
+ try {
1042
+ // Mutates options.input.text / .images / .pdfFiles in place.
1043
+ await processUnifiedFilesArray(options, 100 * 1024 * 1024, this.providerName);
1044
+ }
1045
+ catch (fileError) {
1046
+ logger.warn(`[GoogleVertex] processUnifiedFilesArray threw, continuing without file content: ${fileError instanceof Error ? fileError.message : String(fileError)}`);
1047
+ }
1047
1048
  }
1049
+ // Runs even without input.files: a caller can populate input.images
1050
+ // directly, and this native path never reaches the shared multimodal
1051
+ // builder that would otherwise normalize the formats.
1052
+ await normalizeVisionImageFormats(options.input);
1048
1053
  }
1049
1054
  async executeStream(options, _analysisSchema) {
1050
1055
  // ALL models now use native SDKs - no more @ai-sdk/google-vertex dependency
@@ -1401,23 +1406,31 @@ export class GoogleVertexProvider extends BaseProvider {
1401
1406
  // Add images as inlineData parts if present
1402
1407
  if (multimodalInput?.images && multimodalInput.images.length > 0) {
1403
1408
  logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native stream`);
1404
- for (const image of multimodalInput.images) {
1409
+ for (const rawImage of multimodalInput.images) {
1410
+ // `input.images` accepts `{ data, altText }` as a documented public
1411
+ // shape, but this loop only ever handled Buffer | string — a wrapper
1412
+ // fell through to the "assume raw bytes" branch and base64-encoded the
1413
+ // OBJECT, sending the literal "[object Object]" to Vertex. Unwrap once,
1414
+ // here, so every branch below sees the payload it expects.
1415
+ const image = unwrapImagePayload(rawImage);
1405
1416
  let imageBuffer;
1406
1417
  let mimeType = "image/jpeg"; // Default
1407
1418
  if (typeof image === "string") {
1408
1419
  if (fs.existsSync(image)) {
1409
1420
  imageBuffer = fs.readFileSync(image);
1410
1421
  // Detect mime type from extension
1411
- const ext = path.extname(image).toLowerCase();
1412
- if (ext === ".png") {
1413
- mimeType = "image/png";
1414
- }
1415
- else if (ext === ".gif") {
1416
- mimeType = "image/gif";
1417
- }
1418
- else if (ext === ".webp") {
1419
- mimeType = "image/webp";
1420
- }
1422
+ // Registry lookup rather than a png/gif/webp switch defaulting to
1423
+ // JPEG: that default labelled a .heic/.bmp/.tiff/.avif reference
1424
+ // image as image/jpeg, which is simply untrue and is what Vertex
1425
+ // then rejected. The registry answers "application/octet-stream"
1426
+ // for a missing or unregistered extension though, and Vertex
1427
+ // rejects a non-image media type just as firmly — so fall back to
1428
+ // the bytes, which are already in hand, rather than sending either
1429
+ // a guess or a non-image type.
1430
+ const byExtension = getMimeTypeForExtension(image);
1431
+ mimeType = byExtension.startsWith("image/")
1432
+ ? byExtension
1433
+ : this.detectImageType(imageBuffer);
1421
1434
  }
1422
1435
  else if (image.startsWith("data:")) {
1423
1436
  // Handle data URL
@@ -1438,7 +1451,9 @@ export class GoogleVertexProvider extends BaseProvider {
1438
1451
  try {
1439
1452
  const response = await fetch(image);
1440
1453
  if (!response.ok) {
1441
- logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
1454
+ // The URL may be presigned or carry credentials in its query
1455
+ // string, and wrapper URLs now reach this branch too.
1456
+ logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: redactUrlForError(image) });
1442
1457
  continue;
1443
1458
  }
1444
1459
  const arrayBuffer = await response.arrayBuffer();
@@ -1449,7 +1464,7 @@ export class GoogleVertexProvider extends BaseProvider {
1449
1464
  }
1450
1465
  }
1451
1466
  catch (fetchError) {
1452
- logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: image });
1467
+ logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: redactUrlForError(image) });
1453
1468
  continue;
1454
1469
  }
1455
1470
  }
@@ -2396,23 +2411,31 @@ export class GoogleVertexProvider extends BaseProvider {
2396
2411
  // Add images as inlineData parts if present
2397
2412
  if (multimodalInput?.images && multimodalInput.images.length > 0) {
2398
2413
  logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native generate`);
2399
- for (const image of multimodalInput.images) {
2414
+ for (const rawImage of multimodalInput.images) {
2415
+ // `input.images` accepts `{ data, altText }` as a documented public
2416
+ // shape, but this loop only ever handled Buffer | string — a wrapper
2417
+ // fell through to the "assume raw bytes" branch and base64-encoded the
2418
+ // OBJECT, sending the literal "[object Object]" to Vertex. Unwrap once,
2419
+ // here, so every branch below sees the payload it expects.
2420
+ const image = unwrapImagePayload(rawImage);
2400
2421
  let imageBuffer;
2401
2422
  let mimeType = "image/jpeg"; // Default
2402
2423
  if (typeof image === "string") {
2403
2424
  if (fs.existsSync(image)) {
2404
2425
  imageBuffer = fs.readFileSync(image);
2405
2426
  // Detect mime type from extension
2406
- const ext = path.extname(image).toLowerCase();
2407
- if (ext === ".png") {
2408
- mimeType = "image/png";
2409
- }
2410
- else if (ext === ".gif") {
2411
- mimeType = "image/gif";
2412
- }
2413
- else if (ext === ".webp") {
2414
- mimeType = "image/webp";
2415
- }
2427
+ // Registry lookup rather than a png/gif/webp switch defaulting to
2428
+ // JPEG: that default labelled a .heic/.bmp/.tiff/.avif reference
2429
+ // image as image/jpeg, which is simply untrue and is what Vertex
2430
+ // then rejected. The registry answers "application/octet-stream"
2431
+ // for a missing or unregistered extension though, and Vertex
2432
+ // rejects a non-image media type just as firmly — so fall back to
2433
+ // the bytes, which are already in hand, rather than sending either
2434
+ // a guess or a non-image type.
2435
+ const byExtension = getMimeTypeForExtension(image);
2436
+ mimeType = byExtension.startsWith("image/")
2437
+ ? byExtension
2438
+ : this.detectImageType(imageBuffer);
2416
2439
  }
2417
2440
  else if (image.startsWith("data:")) {
2418
2441
  // Handle data URL
@@ -2433,7 +2456,9 @@ export class GoogleVertexProvider extends BaseProvider {
2433
2456
  try {
2434
2457
  const response = await fetch(image);
2435
2458
  if (!response.ok) {
2436
- logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
2459
+ // The URL may be presigned or carry credentials in its query
2460
+ // string, and wrapper URLs now reach this branch too.
2461
+ logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: redactUrlForError(image) });
2437
2462
  continue;
2438
2463
  }
2439
2464
  const arrayBuffer = await response.arrayBuffer();
@@ -2444,7 +2469,7 @@ export class GoogleVertexProvider extends BaseProvider {
2444
2469
  }
2445
2470
  }
2446
2471
  catch (fetchError) {
2447
- logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: image });
2472
+ logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: redactUrlForError(image) });
2448
2473
  continue;
2449
2474
  }
2450
2475
  }
@@ -3497,23 +3522,31 @@ export class GoogleVertexProvider extends BaseProvider {
3497
3522
  // Add images as image parts if present
3498
3523
  if (multimodalInput?.images && multimodalInput.images.length > 0) {
3499
3524
  logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native Anthropic stream`);
3500
- for (const image of multimodalInput.images) {
3525
+ for (const rawImage of multimodalInput.images) {
3526
+ // `input.images` accepts `{ data, altText }` as a documented public
3527
+ // shape, but this loop only ever handled Buffer | string — a wrapper
3528
+ // fell through to the "assume raw bytes" branch and base64-encoded the
3529
+ // OBJECT, sending the literal "[object Object]" to Vertex. Unwrap once,
3530
+ // here, so every branch below sees the payload it expects.
3531
+ const image = unwrapImagePayload(rawImage);
3501
3532
  let imageBuffer;
3502
3533
  let mimeType = "image/jpeg"; // Default
3503
3534
  if (typeof image === "string") {
3504
3535
  if (fs.existsSync(image)) {
3505
3536
  imageBuffer = fs.readFileSync(image);
3506
3537
  // Detect mime type from extension
3507
- const ext = path.extname(image).toLowerCase();
3508
- if (ext === ".png") {
3509
- mimeType = "image/png";
3510
- }
3511
- else if (ext === ".gif") {
3512
- mimeType = "image/gif";
3513
- }
3514
- else if (ext === ".webp") {
3515
- mimeType = "image/webp";
3516
- }
3538
+ // Registry lookup rather than a png/gif/webp switch defaulting to
3539
+ // JPEG: that default labelled a .heic/.bmp/.tiff/.avif reference
3540
+ // image as image/jpeg, which is simply untrue and is what Vertex
3541
+ // then rejected. The registry answers "application/octet-stream"
3542
+ // for a missing or unregistered extension though, and Vertex
3543
+ // rejects a non-image media type just as firmly — so fall back to
3544
+ // the bytes, which are already in hand, rather than sending either
3545
+ // a guess or a non-image type.
3546
+ const byExtension = getMimeTypeForExtension(image);
3547
+ mimeType = byExtension.startsWith("image/")
3548
+ ? byExtension
3549
+ : this.detectImageType(imageBuffer);
3517
3550
  }
3518
3551
  else if (image.startsWith("data:")) {
3519
3552
  // Handle data URL
@@ -3534,7 +3567,9 @@ export class GoogleVertexProvider extends BaseProvider {
3534
3567
  try {
3535
3568
  const response = await fetch(image);
3536
3569
  if (!response.ok) {
3537
- logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
3570
+ // The URL may be presigned or carry credentials in its query
3571
+ // string, and wrapper URLs now reach this branch too.
3572
+ logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: redactUrlForError(image) });
3538
3573
  continue;
3539
3574
  }
3540
3575
  const arrayBuffer = await response.arrayBuffer();
@@ -3545,7 +3580,7 @@ export class GoogleVertexProvider extends BaseProvider {
3545
3580
  }
3546
3581
  }
3547
3582
  catch (fetchError) {
3548
- logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: image });
3583
+ logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: redactUrlForError(image) });
3549
3584
  continue;
3550
3585
  }
3551
3586
  }
@@ -4832,23 +4867,31 @@ export class GoogleVertexProvider extends BaseProvider {
4832
4867
  // Add images as image parts if present
4833
4868
  if (multimodalInput?.images && multimodalInput.images.length > 0) {
4834
4869
  logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native Anthropic generate`);
4835
- for (const image of multimodalInput.images) {
4870
+ for (const rawImage of multimodalInput.images) {
4871
+ // `input.images` accepts `{ data, altText }` as a documented public
4872
+ // shape, but this loop only ever handled Buffer | string — a wrapper
4873
+ // fell through to the "assume raw bytes" branch and base64-encoded the
4874
+ // OBJECT, sending the literal "[object Object]" to Vertex. Unwrap once,
4875
+ // here, so every branch below sees the payload it expects.
4876
+ const image = unwrapImagePayload(rawImage);
4836
4877
  let imageBuffer;
4837
4878
  let mimeType = "image/jpeg"; // Default
4838
4879
  if (typeof image === "string") {
4839
4880
  if (fs.existsSync(image)) {
4840
4881
  imageBuffer = fs.readFileSync(image);
4841
4882
  // Detect mime type from extension
4842
- const ext = path.extname(image).toLowerCase();
4843
- if (ext === ".png") {
4844
- mimeType = "image/png";
4845
- }
4846
- else if (ext === ".gif") {
4847
- mimeType = "image/gif";
4848
- }
4849
- else if (ext === ".webp") {
4850
- mimeType = "image/webp";
4851
- }
4883
+ // Registry lookup rather than a png/gif/webp switch defaulting to
4884
+ // JPEG: that default labelled a .heic/.bmp/.tiff/.avif reference
4885
+ // image as image/jpeg, which is simply untrue and is what Vertex
4886
+ // then rejected. The registry answers "application/octet-stream"
4887
+ // for a missing or unregistered extension though, and Vertex
4888
+ // rejects a non-image media type just as firmly — so fall back to
4889
+ // the bytes, which are already in hand, rather than sending either
4890
+ // a guess or a non-image type.
4891
+ const byExtension = getMimeTypeForExtension(image);
4892
+ mimeType = byExtension.startsWith("image/")
4893
+ ? byExtension
4894
+ : this.detectImageType(imageBuffer);
4852
4895
  }
4853
4896
  else if (image.startsWith("data:")) {
4854
4897
  // Handle data URL
@@ -4869,7 +4912,9 @@ export class GoogleVertexProvider extends BaseProvider {
4869
4912
  try {
4870
4913
  const response = await fetch(image);
4871
4914
  if (!response.ok) {
4872
- logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
4915
+ // The URL may be presigned or carry credentials in its query
4916
+ // string, and wrapper URLs now reach this branch too.
4917
+ logger.warn(`[GoogleVertex] Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: redactUrlForError(image) });
4873
4918
  continue;
4874
4919
  }
4875
4920
  const arrayBuffer = await response.arrayBuffer();
@@ -4880,7 +4925,7 @@ export class GoogleVertexProvider extends BaseProvider {
4880
4925
  }
4881
4926
  }
4882
4927
  catch (fetchError) {
4883
- logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: image });
4928
+ logger.warn(`[GoogleVertex] Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: redactUrlForError(image) });
4884
4929
  continue;
4885
4930
  }
4886
4931
  }
@@ -7043,6 +7088,14 @@ export class GoogleVertexProvider extends BaseProvider {
7043
7088
  * @returns A promise that resolves to the generation result, including the image data.
7044
7089
  */
7045
7090
  async executeImageGeneration(options) {
7091
+ // Image-to-image generation takes reference images through the same
7092
+ // `input.images` array, and both generate() and stream() route here BEFORE
7093
+ // reaching preprocessNativeFileInput's normalization — so a reference photo
7094
+ // in HEIC/BMP/AVIF would arrive at the API untranscoded. Normalizing at the
7095
+ // top of this method covers every route in, rather than relying on each of
7096
+ // the several call sites to remember. Idempotent, so an already-normalized
7097
+ // request pays nothing.
7098
+ await normalizeVisionImageFormats(options.input);
7046
7099
  const prompt = options.prompt || options.input?.text || "";
7047
7100
  const pdfFiles = options.input?.pdfFiles || [];
7048
7101
  const inputImages = options.input?.images || [];
@@ -7159,7 +7212,11 @@ export class GoogleVertexProvider extends BaseProvider {
7159
7212
  // This handles the case where PDFs are converted to images for models that don't support native PDF
7160
7213
  if (hasImageInput) {
7161
7214
  for (let i = 0; i < inputImages.length; i++) {
7162
- const image = inputImages[i];
7215
+ // A fifth image loop that predates the four above and has the same
7216
+ // blind spot: an ImageWithAltText wrapper matches neither the Buffer
7217
+ // nor the string branch, so a wrapped reference image was silently
7218
+ // skipped — including one this method had just transcoded.
7219
+ const image = unwrapImagePayload(inputImages[i]);
7163
7220
  let imageBase64;
7164
7221
  let mimeType;
7165
7222
  if (Buffer.isBuffer(image)) {
@@ -7208,7 +7265,7 @@ export class GoogleVertexProvider extends BaseProvider {
7208
7265
  try {
7209
7266
  const response = await fetch(image);
7210
7267
  if (!response.ok) {
7211
- logger.warn(`Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image, index: i });
7268
+ logger.warn(`Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: redactUrlForError(image), index: i });
7212
7269
  continue;
7213
7270
  }
7214
7271
  const arrayBuffer = await response.arrayBuffer();
@@ -7221,7 +7278,7 @@ export class GoogleVertexProvider extends BaseProvider {
7221
7278
  : this.detectImageType(fetchedBuffer);
7222
7279
  }
7223
7280
  catch (fetchError) {
7224
- logger.warn(`Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: image, index: i });
7281
+ logger.warn(`Image URL fetch threw, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { url: redactUrlForError(image), index: i });
7225
7282
  continue;
7226
7283
  }
7227
7284
  }
@@ -9,6 +9,47 @@ export type FileType = "csv" | "image" | "pdf" | "audio" | "video" | "archive" |
9
9
  * Office document types
10
10
  */
11
11
  export type OfficeDocumentType = "docx" | "pptx" | "xlsx";
12
+ /**
13
+ * Outcome of a vision-compatibility pass over one image.
14
+ *
15
+ * See `adapters/imageFormatSupport.ts` — `converted` is false both when the
16
+ * source format was already universally accepted and when no decoder could
17
+ * read it, so callers must not treat it as a success flag.
18
+ */
19
+ export type VisionImageConversion = {
20
+ readonly buffer: Buffer;
21
+ readonly mimeType: string;
22
+ /** True when the bytes were re-encoded; false when they were left alone. */
23
+ readonly converted: boolean;
24
+ };
25
+ /**
26
+ * Broad category a file format belongs to, as a human would name it.
27
+ *
28
+ * Distinct from {@link FileType}, which is the *routing* type the detector
29
+ * emits. The two deliberately differ where one processor handles several
30
+ * formats: an .odt has `modality: "document"` but `fileType: "docx"`, and a
31
+ * .svg has `modality: "image"` but `fileType: "svg"` because it is sanitised as
32
+ * markup rather than sent to a vision API.
33
+ */
34
+ export type FileModality = "image" | "audio" | "video" | "document" | "data" | "archive";
35
+ /**
36
+ * One format in the canonical file-type registry.
37
+ *
38
+ * `extensions[0]` and `mimeTypes[0]` are canonical; the remaining entries are
39
+ * aliases accepted on input. See `processors/config/fileTypeRegistry.ts`.
40
+ */
41
+ export type FileFormatEntry = {
42
+ /** Human-readable format name, used in registry-conflict errors. */
43
+ readonly label: string;
44
+ /** Extensions with leading dots, lowercase; first is canonical. */
45
+ readonly extensions: readonly string[];
46
+ /** MIME types, lowercase; first is canonical. */
47
+ readonly mimeTypes: readonly string[];
48
+ /** Routing type the detector emits for this format. */
49
+ readonly fileType: FileType;
50
+ /** Category a human would put this format in. */
51
+ readonly modality: FileModality;
52
+ };
12
53
  /**
13
54
  * File with metadata — allows callers to pass filename alongside a Buffer.
14
55
  *