@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.
- package/CHANGELOG.md +6 -0
- package/dist/adapters/imageFormatSupport.d.ts +75 -0
- package/dist/adapters/imageFormatSupport.js +283 -0
- package/dist/adapters/video/ffmpegAdapter.d.ts +6 -0
- package/dist/adapters/video/ffmpegAdapter.js +1 -1
- package/dist/browser/neurolink.min.js +399 -398
- package/dist/lib/adapters/imageFormatSupport.d.ts +75 -0
- package/dist/lib/adapters/imageFormatSupport.js +284 -0
- package/dist/lib/adapters/video/ffmpegAdapter.d.ts +6 -0
- package/dist/lib/adapters/video/ffmpegAdapter.js +1 -1
- package/dist/lib/processors/config/fileExtensions.d.ts +32 -15
- package/dist/lib/processors/config/fileExtensions.js +27 -66
- package/dist/lib/processors/config/fileTypeRegistry.d.ts +106 -0
- package/dist/lib/processors/config/fileTypeRegistry.js +702 -0
- package/dist/lib/processors/config/index.d.ts +2 -1
- package/dist/lib/processors/config/index.js +5 -1
- package/dist/lib/processors/config/mimeConstants.d.ts +22 -7
- package/dist/lib/processors/config/mimeConstants.js +45 -66
- package/dist/lib/processors/media/AudioProcessor.js +16 -38
- package/dist/lib/processors/media/VideoProcessor.js +11 -32
- package/dist/lib/providers/googleAiStudio/client.js +12 -1
- package/dist/lib/providers/googleVertex/client.js +123 -66
- package/dist/lib/types/file.d.ts +41 -0
- package/dist/lib/utils/fileDetector.js +367 -251
- package/dist/lib/utils/imageProcessor.js +14 -17
- package/dist/lib/utils/markupSniff.d.ts +37 -0
- package/dist/lib/utils/markupSniff.js +125 -0
- package/dist/lib/utils/messageBuilder.d.ts +14 -0
- package/dist/lib/utils/messageBuilder.js +172 -92
- package/dist/processors/config/fileExtensions.d.ts +32 -15
- package/dist/processors/config/fileExtensions.js +27 -66
- package/dist/processors/config/fileTypeRegistry.d.ts +106 -0
- package/dist/processors/config/fileTypeRegistry.js +701 -0
- package/dist/processors/config/index.d.ts +2 -1
- package/dist/processors/config/index.js +5 -1
- package/dist/processors/config/mimeConstants.d.ts +22 -7
- package/dist/processors/config/mimeConstants.js +45 -66
- package/dist/processors/media/AudioProcessor.js +16 -38
- package/dist/processors/media/VideoProcessor.js +11 -32
- package/dist/providers/googleAiStudio/client.js +12 -1
- package/dist/providers/googleVertex/client.js +123 -66
- package/dist/types/file.d.ts +41 -0
- package/dist/utils/fileDetector.js +367 -251
- package/dist/utils/imageProcessor.js +14 -17
- package/dist/utils/markupSniff.d.ts +37 -0
- package/dist/utils/markupSniff.js +124 -0
- package/dist/utils/messageBuilder.d.ts +14 -0
- package/dist/utils/messageBuilder.js +172 -92
- package/package.json +3 -2
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision-provider image format compatibility.
|
|
3
|
+
*
|
|
4
|
+
* NeuroLink identifies far more image formats than any vision API accepts. BMP,
|
|
5
|
+
* TIFF, AVIF, ICO and JPEG 2000 are accepted by none of them; HEIC and HEIF are
|
|
6
|
+
* accepted only by Google. Until this module existed those formats were
|
|
7
|
+
* detected correctly, labelled correctly, and then forwarded verbatim — the
|
|
8
|
+
* request reached the provider and came back as an opaque HTTP 400, which is
|
|
9
|
+
* the least useful outcome available: the file was clearly an image, NeuroLink
|
|
10
|
+
* knew exactly which kind, and still nothing worked.
|
|
11
|
+
*
|
|
12
|
+
* A phone photo is the common case. iOS writes HEIC by default, so "attach a
|
|
13
|
+
* photo and ask what is in it" failed for every provider except Google.
|
|
14
|
+
*
|
|
15
|
+
* Anything outside the universal set is transcoded to PNG. PNG rather than JPEG
|
|
16
|
+
* because the sources are frequently lossless (TIFF, BMP, ICO) or already
|
|
17
|
+
* carry alpha, and a lossy re-encode of an image the model is about to read
|
|
18
|
+
* closely is the wrong default.
|
|
19
|
+
*
|
|
20
|
+
* @module adapters/imageFormatSupport
|
|
21
|
+
*/
|
|
22
|
+
import type { ImageWithAltText, VisionImageConversion } from "../types/index.js";
|
|
23
|
+
/**
|
|
24
|
+
* MIME types every vision-capable provider accepts as-is.
|
|
25
|
+
*
|
|
26
|
+
* This is the intersection across OpenAI, Anthropic, Google (AI Studio and
|
|
27
|
+
* Vertex), Bedrock, Azure and Mistral — deliberately the intersection and not
|
|
28
|
+
* a per-provider matrix. Google additionally accepts HEIC/HEIF natively, but
|
|
29
|
+
* converting those for Google as well costs one transcode and removes an
|
|
30
|
+
* entire axis of provider-specific branching from the dispatch path.
|
|
31
|
+
*/
|
|
32
|
+
export declare const UNIVERSAL_VISION_IMAGE_MIME_TYPES: ReadonlySet<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Every image MIME type NeuroLink accepts as *input*.
|
|
35
|
+
*
|
|
36
|
+
* The union of what providers take as-is and what this module can convert for
|
|
37
|
+
* them. Intake validation must use this rather than the universal set alone:
|
|
38
|
+
* a format we can transcode is a format we accept, and gating intake on the
|
|
39
|
+
* provider-acceptable list rejects the file before conversion ever runs.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SUPPORTED_INPUT_IMAGE_MIME_TYPES: ReadonlySet<string>;
|
|
42
|
+
/**
|
|
43
|
+
* True when a MIME type needs transcoding before it can be sent to a vision
|
|
44
|
+
* provider. Cheap enough to call on every image; callers use it to avoid
|
|
45
|
+
* reading a file off disk that would not have been converted anyway.
|
|
46
|
+
*/
|
|
47
|
+
export declare function needsVisionTranscode(mimeType: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Return image bytes every vision provider can read, transcoding to PNG when
|
|
50
|
+
* the source format is one no provider accepts.
|
|
51
|
+
*
|
|
52
|
+
* Never throws for image reasons. When neither backend can decode the input,
|
|
53
|
+
* the original bytes are returned with a warning naming the format — the
|
|
54
|
+
* request then fails at the provider exactly as it did before, rather than this
|
|
55
|
+
* compatibility step becoming a new way for a previously working request to
|
|
56
|
+
* break.
|
|
57
|
+
*
|
|
58
|
+
* @param buffer - Raw image bytes.
|
|
59
|
+
* @param mimeType - Detected MIME type of `buffer`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toVisionCompatibleImage(buffer: Buffer, mimeType: string): Promise<VisionImageConversion>;
|
|
62
|
+
/**
|
|
63
|
+
* Unwrap an `input.images` entry to its payload.
|
|
64
|
+
*
|
|
65
|
+
* `ImageWithAltText` (`{ data, altText }`) is a documented public input shape,
|
|
66
|
+
* but the provider image loops typed the array as `Buffer | string` and so
|
|
67
|
+
* treated a wrapper as raw bytes — `toString("base64")` on the object yields
|
|
68
|
+
* the literal "[object Object]", which is valid base64 that decodes to seven
|
|
69
|
+
* bytes of garbage. The request therefore reached the API and failed as
|
|
70
|
+
* "invalid image data", with nothing pointing at the stringification.
|
|
71
|
+
*
|
|
72
|
+
* Alt text has no representation in Vertex's `inlineData` part, so it is
|
|
73
|
+
* dropped here deliberately rather than corrupting the payload to carry it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function unwrapImagePayload(entry: Buffer | string | ImageWithAltText): Buffer | string;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision-provider image format compatibility.
|
|
3
|
+
*
|
|
4
|
+
* NeuroLink identifies far more image formats than any vision API accepts. BMP,
|
|
5
|
+
* TIFF, AVIF, ICO and JPEG 2000 are accepted by none of them; HEIC and HEIF are
|
|
6
|
+
* accepted only by Google. Until this module existed those formats were
|
|
7
|
+
* detected correctly, labelled correctly, and then forwarded verbatim — the
|
|
8
|
+
* request reached the provider and came back as an opaque HTTP 400, which is
|
|
9
|
+
* the least useful outcome available: the file was clearly an image, NeuroLink
|
|
10
|
+
* knew exactly which kind, and still nothing worked.
|
|
11
|
+
*
|
|
12
|
+
* A phone photo is the common case. iOS writes HEIC by default, so "attach a
|
|
13
|
+
* photo and ask what is in it" failed for every provider except Google.
|
|
14
|
+
*
|
|
15
|
+
* Anything outside the universal set is transcoded to PNG. PNG rather than JPEG
|
|
16
|
+
* because the sources are frequently lossless (TIFF, BMP, ICO) or already
|
|
17
|
+
* carry alpha, and a lossy re-encode of an image the model is about to read
|
|
18
|
+
* closely is the wrong default.
|
|
19
|
+
*
|
|
20
|
+
* @module adapters/imageFormatSupport
|
|
21
|
+
*/
|
|
22
|
+
import { extensionForMimeType } from "../processors/config/fileTypeRegistry.js";
|
|
23
|
+
import { withTimeout } from "../utils/errorHandling.js";
|
|
24
|
+
import { logger } from "../utils/logger.js";
|
|
25
|
+
import { tryImport } from "../utils/tryImport.js";
|
|
26
|
+
import { getFfmpegPath, runFfmpeg } from "./video/ffmpegAdapter.js";
|
|
27
|
+
/**
|
|
28
|
+
* Per-backend ceiling for one image conversion.
|
|
29
|
+
*
|
|
30
|
+
* Generous enough for a large TIFF or a HEIC burst frame on a loaded machine,
|
|
31
|
+
* short enough that a hung decoder cannot hold a generation request open.
|
|
32
|
+
*/
|
|
33
|
+
const IMAGE_TRANSCODE_TIMEOUT_MS = 30_000;
|
|
34
|
+
/**
|
|
35
|
+
* MIME types every vision-capable provider accepts as-is.
|
|
36
|
+
*
|
|
37
|
+
* This is the intersection across OpenAI, Anthropic, Google (AI Studio and
|
|
38
|
+
* Vertex), Bedrock, Azure and Mistral — deliberately the intersection and not
|
|
39
|
+
* a per-provider matrix. Google additionally accepts HEIC/HEIF natively, but
|
|
40
|
+
* converting those for Google as well costs one transcode and removes an
|
|
41
|
+
* entire axis of provider-specific branching from the dispatch path.
|
|
42
|
+
*/
|
|
43
|
+
export const UNIVERSAL_VISION_IMAGE_MIME_TYPES = new Set([
|
|
44
|
+
"image/png",
|
|
45
|
+
"image/jpeg",
|
|
46
|
+
"image/gif",
|
|
47
|
+
"image/webp",
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Formats that are transcoded to PNG before dispatch.
|
|
51
|
+
*
|
|
52
|
+
* An explicit allowlist rather than "anything not universal": a MIME type this
|
|
53
|
+
* module does not recognise is more likely a mislabelled file than a format
|
|
54
|
+
* sharp can decode, and passing it through unchanged preserves the provider's
|
|
55
|
+
* own error message instead of replacing it with a decode failure here.
|
|
56
|
+
*/
|
|
57
|
+
const TRANSCODABLE_IMAGE_MIME_TYPES = new Set([
|
|
58
|
+
"image/bmp",
|
|
59
|
+
"image/x-ms-bmp",
|
|
60
|
+
"image/tiff",
|
|
61
|
+
"image/x-tiff",
|
|
62
|
+
"image/avif",
|
|
63
|
+
"image/heic",
|
|
64
|
+
"image/heic-sequence",
|
|
65
|
+
"image/heif",
|
|
66
|
+
"image/heif-sequence",
|
|
67
|
+
"image/x-icon",
|
|
68
|
+
"image/vnd.microsoft.icon",
|
|
69
|
+
"image/jp2",
|
|
70
|
+
"image/jpx",
|
|
71
|
+
"image/apng",
|
|
72
|
+
// SVG reaches this module only when it arrives as raw bytes in `input.images`
|
|
73
|
+
// rather than through detection (which routes .svg to the sanitizer). No
|
|
74
|
+
// vision provider accepts image/svg+xml, and sharp rasterises SVG natively,
|
|
75
|
+
// so converting is strictly better than shipping markup labelled as an image.
|
|
76
|
+
"image/svg+xml",
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* Every image MIME type NeuroLink accepts as *input*.
|
|
80
|
+
*
|
|
81
|
+
* The union of what providers take as-is and what this module can convert for
|
|
82
|
+
* them. Intake validation must use this rather than the universal set alone:
|
|
83
|
+
* a format we can transcode is a format we accept, and gating intake on the
|
|
84
|
+
* provider-acceptable list rejects the file before conversion ever runs.
|
|
85
|
+
*/
|
|
86
|
+
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = new Set([
|
|
87
|
+
...UNIVERSAL_VISION_IMAGE_MIME_TYPES,
|
|
88
|
+
...TRANSCODABLE_IMAGE_MIME_TYPES,
|
|
89
|
+
]);
|
|
90
|
+
/**
|
|
91
|
+
* True when a MIME type needs transcoding before it can be sent to a vision
|
|
92
|
+
* provider. Cheap enough to call on every image; callers use it to avoid
|
|
93
|
+
* reading a file off disk that would not have been converted anyway.
|
|
94
|
+
*/
|
|
95
|
+
export function needsVisionTranscode(mimeType) {
|
|
96
|
+
const normalized = mimeType.split(";")[0].trim().toLowerCase();
|
|
97
|
+
return (!UNIVERSAL_VISION_IMAGE_MIME_TYPES.has(normalized) &&
|
|
98
|
+
TRANSCODABLE_IMAGE_MIME_TYPES.has(normalized));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Decode with sharp. Covers TIFF, AVIF, GIF and SVG in-process with no temp
|
|
102
|
+
* files, which is the fast path.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately does NOT cover every transcodable format: sharp's prebuilt
|
|
105
|
+
* binaries report `heif` as an input format but that is AV1-in-HEIF (AVIF)
|
|
106
|
+
* only — actual HEVC-coded HEIC fails inside libheif, and BMP, ICO and
|
|
107
|
+
* JPEG 2000 are not compiled in at all. Those fall through to ffmpeg below.
|
|
108
|
+
*/
|
|
109
|
+
async function transcodeWithSharp(buffer) {
|
|
110
|
+
const sharpModule = await tryImport("sharp", "Image format conversion for vision providers");
|
|
111
|
+
// A shape guard, not a crash guard: an absent or malformed sharp already
|
|
112
|
+
// fails safely, because `tryImport` throws a named install error and any
|
|
113
|
+
// TypeError from calling a non-function is caught by the backend loop, which
|
|
114
|
+
// then tries ffmpeg. What this adds is a legible reason in that loop's
|
|
115
|
+
// failure list instead of "sharpModule.default is not a function".
|
|
116
|
+
//
|
|
117
|
+
// It deliberately throws rather than returning `buffer`: returning the input
|
|
118
|
+
// would report a successful conversion and relabel the original bytes as PNG,
|
|
119
|
+
// and would also skip the ffmpeg backend — the one that actually handles
|
|
120
|
+
// HEIC, BMP, ICO and JPEG 2000.
|
|
121
|
+
if (typeof sharpModule?.default !== "function") {
|
|
122
|
+
throw new Error("the installed sharp package does not expose a callable default export");
|
|
123
|
+
}
|
|
124
|
+
// `pages: 1` keeps a multi-frame source (animated AVIF, a .heics sequence,
|
|
125
|
+
// a multi-page TIFF) from being flattened into one tall strip — the first
|
|
126
|
+
// frame is what a vision model should receive.
|
|
127
|
+
const pipeline = sharpModule.default(buffer, { pages: 1 });
|
|
128
|
+
// The instance shape is checked as well as the factory: a build that exports
|
|
129
|
+
// a callable but returns something without `.png()` would otherwise fail as
|
|
130
|
+
// an opaque TypeError inside the backend loop.
|
|
131
|
+
if (typeof pipeline?.png !== "function") {
|
|
132
|
+
throw new Error("the installed sharp package returned a pipeline without a png() encoder");
|
|
133
|
+
}
|
|
134
|
+
return pipeline.png().toBuffer();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Decode with ffmpeg, which handles what sharp cannot — most importantly HEIC,
|
|
138
|
+
* the format iPhones write by default and therefore the single most common
|
|
139
|
+
* "why can't the model see my photo" case.
|
|
140
|
+
*
|
|
141
|
+
* ffmpeg is already a soft dependency for video keyframe extraction and is
|
|
142
|
+
* resolved through the same `FFMPEG_PATH` → `ffmpeg-static` → system-PATH
|
|
143
|
+
* chain, so this adds a code path rather than a new requirement.
|
|
144
|
+
*
|
|
145
|
+
* Requires temp files: ffmpeg's image demuxers seek, so piping through stdin is
|
|
146
|
+
* not reliable for these formats. The temp directory is removed in `finally`
|
|
147
|
+
* whether or not the conversion succeeded.
|
|
148
|
+
*
|
|
149
|
+
* The Node builtins are imported dynamically rather than at module scope
|
|
150
|
+
* because the browser bundle stubs `node:fs/promises` and its stub has no
|
|
151
|
+
* `mkdtemp`. This whole path is server-only — nothing in a browser is going to
|
|
152
|
+
* spawn ffmpeg — so the import belongs where it is used.
|
|
153
|
+
*/
|
|
154
|
+
async function transcodeWithFfmpeg(buffer, extension, binaryPath) {
|
|
155
|
+
const [{ randomUUID }, { mkdtemp, readFile, rm, writeFile }, { tmpdir }, { join },] = await Promise.all([
|
|
156
|
+
import("node:crypto"),
|
|
157
|
+
import("node:fs/promises"),
|
|
158
|
+
import("node:os"),
|
|
159
|
+
import("node:path"),
|
|
160
|
+
]);
|
|
161
|
+
const workDir = await mkdtemp(join(tmpdir(), "neurolink-img-"));
|
|
162
|
+
const inputPath = join(workDir, `${randomUUID()}${extension}`);
|
|
163
|
+
const outputPath = join(workDir, `${randomUUID()}.png`);
|
|
164
|
+
try {
|
|
165
|
+
await writeFile(inputPath, buffer);
|
|
166
|
+
await runFfmpeg([
|
|
167
|
+
"-y",
|
|
168
|
+
"-v",
|
|
169
|
+
"error",
|
|
170
|
+
"-i",
|
|
171
|
+
inputPath,
|
|
172
|
+
// Take a single frame so a multi-image container yields one PNG rather
|
|
173
|
+
// than ffmpeg erroring on a missing output-sequence pattern.
|
|
174
|
+
"-frames:v",
|
|
175
|
+
"1",
|
|
176
|
+
"-f",
|
|
177
|
+
"image2",
|
|
178
|
+
"-c:v",
|
|
179
|
+
"png",
|
|
180
|
+
outputPath,
|
|
181
|
+
], binaryPath ? { binaryPath } : {});
|
|
182
|
+
return await readFile(outputPath);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The decode backends to try, in order, cheapest first.
|
|
190
|
+
*
|
|
191
|
+
* The two ffmpeg entries are not redundant. `getFfmpegPath()` prefers the
|
|
192
|
+
* `ffmpeg-static` package, whose LGPL build omits HEVC and therefore cannot
|
|
193
|
+
* read HEIC — the format iPhones write by default. A system ffmpeg usually can,
|
|
194
|
+
* so when the resolved binary is not already the system one it is retried
|
|
195
|
+
* explicitly rather than reporting a photo as unsupported.
|
|
196
|
+
*/
|
|
197
|
+
async function* transcodeBackends(buffer, extension) {
|
|
198
|
+
yield { name: "sharp", run: () => transcodeWithSharp(buffer) };
|
|
199
|
+
const resolved = await getFfmpegPath().catch(() => "ffmpeg");
|
|
200
|
+
yield {
|
|
201
|
+
name: "ffmpeg",
|
|
202
|
+
run: () => transcodeWithFfmpeg(buffer, extension),
|
|
203
|
+
};
|
|
204
|
+
if (resolved !== "ffmpeg") {
|
|
205
|
+
yield {
|
|
206
|
+
name: "system ffmpeg",
|
|
207
|
+
run: () => transcodeWithFfmpeg(buffer, extension, "ffmpeg"),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Return image bytes every vision provider can read, transcoding to PNG when
|
|
213
|
+
* the source format is one no provider accepts.
|
|
214
|
+
*
|
|
215
|
+
* Never throws for image reasons. When neither backend can decode the input,
|
|
216
|
+
* the original bytes are returned with a warning naming the format — the
|
|
217
|
+
* request then fails at the provider exactly as it did before, rather than this
|
|
218
|
+
* compatibility step becoming a new way for a previously working request to
|
|
219
|
+
* break.
|
|
220
|
+
*
|
|
221
|
+
* @param buffer - Raw image bytes.
|
|
222
|
+
* @param mimeType - Detected MIME type of `buffer`.
|
|
223
|
+
*/
|
|
224
|
+
export async function toVisionCompatibleImage(buffer, mimeType) {
|
|
225
|
+
if (!needsVisionTranscode(mimeType)) {
|
|
226
|
+
return { buffer, mimeType, converted: false };
|
|
227
|
+
}
|
|
228
|
+
const normalized = mimeType.split(";")[0].trim().toLowerCase();
|
|
229
|
+
const extension = extensionForMimeType(normalized) ?? ".bin";
|
|
230
|
+
const failures = [];
|
|
231
|
+
for await (const backend of transcodeBackends(buffer, extension)) {
|
|
232
|
+
try {
|
|
233
|
+
// Both backends can stall — sharp on a malformed stream, ffmpeg on a
|
|
234
|
+
// container it half-understands — and this runs inline on the request
|
|
235
|
+
// path. A bounded failure falls through to the next backend and finally
|
|
236
|
+
// to pass-through, which is the same degradation as a decode error.
|
|
237
|
+
const converted = await withTimeout(backend.run(), IMAGE_TRANSCODE_TIMEOUT_MS, new Error(`${backend.name} image transcode exceeded ${IMAGE_TRANSCODE_TIMEOUT_MS}ms`));
|
|
238
|
+
if (converted.length === 0) {
|
|
239
|
+
throw new Error("produced an empty image");
|
|
240
|
+
}
|
|
241
|
+
logger.debug(`[imageFormatSupport] Transcoded ${normalized} → image/png via ${backend.name} ` +
|
|
242
|
+
`(${buffer.length} → ${converted.length} bytes) for vision compatibility`);
|
|
243
|
+
return { buffer: converted, mimeType: "image/png", converted: true };
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
failures.push(`${backend.name}: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
logger.warn(`[imageFormatSupport] Could not transcode ${normalized} to PNG — sending the ` +
|
|
250
|
+
`original bytes, which most vision providers will reject. Install a full ` +
|
|
251
|
+
`ffmpeg build (or set FFMPEG_PATH to one) to enable this format. ` +
|
|
252
|
+
`Tried ${failures.join("; ")}`);
|
|
253
|
+
return { buffer, mimeType, converted: false };
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Whether an `input.images` entry is the `{ data, altText }` wrapper.
|
|
257
|
+
*
|
|
258
|
+
* Narrowing on the `data` property rather than on `typeof entry === "object"`:
|
|
259
|
+
* a Buffer is also an object, so the looser test leaves `ImageWithAltText` in
|
|
260
|
+
* the union on the false branch and only compiles behind a cast.
|
|
261
|
+
*/
|
|
262
|
+
function isImageWithAltTextEntry(entry) {
|
|
263
|
+
return (typeof entry === "object" &&
|
|
264
|
+
entry !== null &&
|
|
265
|
+
!Buffer.isBuffer(entry) &&
|
|
266
|
+
"data" in entry);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Unwrap an `input.images` entry to its payload.
|
|
270
|
+
*
|
|
271
|
+
* `ImageWithAltText` (`{ data, altText }`) is a documented public input shape,
|
|
272
|
+
* but the provider image loops typed the array as `Buffer | string` and so
|
|
273
|
+
* treated a wrapper as raw bytes — `toString("base64")` on the object yields
|
|
274
|
+
* the literal "[object Object]", which is valid base64 that decodes to seven
|
|
275
|
+
* bytes of garbage. The request therefore reached the API and failed as
|
|
276
|
+
* "invalid image data", with nothing pointing at the stringification.
|
|
277
|
+
*
|
|
278
|
+
* Alt text has no representation in Vertex's `inlineData` part, so it is
|
|
279
|
+
* dropped here deliberately rather than corrupting the payload to carry it.
|
|
280
|
+
*/
|
|
281
|
+
export function unwrapImagePayload(entry) {
|
|
282
|
+
return isImageWithAltTextEntry(entry) ? entry.data : entry;
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=imageFormatSupport.js.map
|
|
@@ -62,6 +62,12 @@ export declare function getFfmpegPath(): Promise<string>;
|
|
|
62
62
|
export declare function runFfmpeg(args: string[], options?: {
|
|
63
63
|
timeoutMs?: number;
|
|
64
64
|
maxBuffer?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Override the resolved binary. Used to retry against the system `ffmpeg`
|
|
67
|
+
* when the `ffmpeg-static` build lacks a codec — its LGPL build ships
|
|
68
|
+
* without HEVC, so it cannot decode HEIC even though a system build can.
|
|
69
|
+
*/
|
|
70
|
+
binaryPath?: string;
|
|
65
71
|
}): Promise<{
|
|
66
72
|
stdout: string;
|
|
67
73
|
stderr: string;
|
|
@@ -167,7 +167,7 @@ export async function getFfmpegPath() {
|
|
|
167
167
|
*/
|
|
168
168
|
export async function runFfmpeg(args, options = {}) {
|
|
169
169
|
const { execFile } = await import("node:child_process");
|
|
170
|
-
const ffmpegPath = await getFfmpegPath();
|
|
170
|
+
const ffmpegPath = options.binaryPath ?? (await getFfmpegPath());
|
|
171
171
|
const timeoutMs = options.timeoutMs ?? FFMPEG_FRAME_TIMEOUT_MS;
|
|
172
172
|
const maxBuffer = options.maxBuffer ?? FFMPEG_FRAME_MAX_BUFFER;
|
|
173
173
|
return new Promise((resolve, reject) => {
|
|
@@ -2,20 +2,34 @@
|
|
|
2
2
|
* File Extension Constants
|
|
3
3
|
* Centralized file extension definitions organized by category for file processing
|
|
4
4
|
*
|
|
5
|
+
* The media/document/data/archive categories are derived from
|
|
6
|
+
* {@link FILE_TYPE_REGISTRY} rather than typed out here — they used to be a
|
|
7
|
+
* second hand-maintained list that drifted from both the registry-equivalent
|
|
8
|
+
* MIME map and the detector, so formats appeared "supported" in one place and
|
|
9
|
+
* were invisible in another. Source-code and text categories, which carry no
|
|
10
|
+
* modality, are still enumerated below.
|
|
11
|
+
*
|
|
5
12
|
* @module processors/config/fileTypes
|
|
6
13
|
*/
|
|
7
14
|
/**
|
|
8
|
-
* Image file extensions supported by the platform
|
|
15
|
+
* Image file extensions supported by the platform.
|
|
16
|
+
* Includes `.svg`, which is processed as sanitized markup rather than raster.
|
|
9
17
|
*/
|
|
10
|
-
export declare const IMAGE_EXTENSIONS: readonly [
|
|
18
|
+
export declare const IMAGE_EXTENSIONS: readonly string[];
|
|
11
19
|
/**
|
|
12
|
-
*
|
|
20
|
+
* Image extensions every vision-capable provider accepts as-is.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately NOT derived from the registry: this is the intersection of what
|
|
23
|
+
* OpenAI, Anthropic, Google and Bedrock all accept, not the set NeuroLink can
|
|
24
|
+
* identify. Anything else the registry knows about (BMP, TIFF, AVIF, ICO,
|
|
25
|
+
* HEIC/HEIF) is transcoded to PNG before dispatch — see
|
|
26
|
+
* `adapters/imageFormatSupport.ts`.
|
|
13
27
|
*/
|
|
14
28
|
export declare const AI_VISION_EXTENSIONS: readonly [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
|
15
29
|
/**
|
|
16
30
|
* Document file extensions for office documents and PDFs
|
|
17
31
|
*/
|
|
18
|
-
export declare const DOCUMENT_EXTENSIONS: readonly [
|
|
32
|
+
export declare const DOCUMENT_EXTENSIONS: readonly string[];
|
|
19
33
|
/**
|
|
20
34
|
* PDF document extensions
|
|
21
35
|
*/
|
|
@@ -57,9 +71,12 @@ export declare const CSV_EXTENSIONS: readonly [".csv"];
|
|
|
57
71
|
*/
|
|
58
72
|
export declare const YAML_EXTENSIONS: readonly [".yaml", ".yml"];
|
|
59
73
|
/**
|
|
60
|
-
* All data format extensions combined
|
|
74
|
+
* All data format extensions combined.
|
|
75
|
+
*
|
|
76
|
+
* The tabular formats (.csv/.tsv) come from the registry; .json/.xml/.yaml are
|
|
77
|
+
* text formats with no modality of their own and stay listed here.
|
|
61
78
|
*/
|
|
62
|
-
export declare const DATA_EXTENSIONS: readonly [
|
|
79
|
+
export declare const DATA_EXTENSIONS: readonly string[];
|
|
63
80
|
/**
|
|
64
81
|
* Plain text file extensions
|
|
65
82
|
*/
|
|
@@ -173,15 +190,15 @@ export declare const CONFIG_EXTENSIONS: readonly [".env", ".ini", ".toml", ".cfg
|
|
|
173
190
|
/**
|
|
174
191
|
* Archive and compressed file extensions
|
|
175
192
|
*/
|
|
176
|
-
export declare const ARCHIVE_EXTENSIONS: readonly [
|
|
193
|
+
export declare const ARCHIVE_EXTENSIONS: readonly string[];
|
|
177
194
|
/**
|
|
178
195
|
* Video file extensions
|
|
179
196
|
*/
|
|
180
|
-
export declare const VIDEO_EXTENSIONS: readonly [
|
|
197
|
+
export declare const VIDEO_EXTENSIONS: readonly string[];
|
|
181
198
|
/**
|
|
182
199
|
* Audio file extensions
|
|
183
200
|
*/
|
|
184
|
-
export declare const AUDIO_EXTENSIONS: readonly [
|
|
201
|
+
export declare const AUDIO_EXTENSIONS: readonly string[];
|
|
185
202
|
/**
|
|
186
203
|
* Design and graphics file extensions
|
|
187
204
|
*/
|
|
@@ -198,16 +215,16 @@ export declare const EXECUTABLE_EXTENSIONS: readonly [".exe", ".dll", ".so", ".d
|
|
|
198
215
|
* File extensions grouped by category for easy access
|
|
199
216
|
*/
|
|
200
217
|
export declare const FILE_EXTENSIONS: {
|
|
201
|
-
readonly IMAGES: readonly [
|
|
218
|
+
readonly IMAGES: readonly string[];
|
|
202
219
|
readonly AI_VISION: readonly [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
|
203
|
-
readonly DOCUMENTS: readonly [
|
|
220
|
+
readonly DOCUMENTS: readonly string[];
|
|
204
221
|
readonly PDF: readonly [".pdf"];
|
|
205
222
|
readonly WORD: readonly [".docx", ".doc"];
|
|
206
223
|
readonly EXCEL: readonly [".xlsx", ".xls"];
|
|
207
224
|
readonly POWERPOINT: readonly [".pptx", ".ppt"];
|
|
208
225
|
readonly OPENDOCUMENT: readonly [".odt", ".ods", ".odp"];
|
|
209
226
|
readonly RTF: readonly [".rtf"];
|
|
210
|
-
readonly DATA: readonly [
|
|
227
|
+
readonly DATA: readonly string[];
|
|
211
228
|
readonly JSON: readonly [".json"];
|
|
212
229
|
readonly XML: readonly [".xml"];
|
|
213
230
|
readonly CSV: readonly [".csv"];
|
|
@@ -254,9 +271,9 @@ export declare const FILE_EXTENSIONS: {
|
|
|
254
271
|
};
|
|
255
272
|
readonly ALL_CODE: readonly [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".pyw", ".pyi", ".java", ".kt", ".kts", ".go", ".rs", ".c", ".h", ".cpp", ".hpp", ".cc", ".cxx", ".hxx", ".cs", ".rb", ".rake", ".php", ".phtml", ".php3", ".php4", ".php5", ".phps", ".sh", ".bash", ".zsh", ".fish", ".ksh", ".pl", ".pm", ".pod", ".t", ".lua", ".sql", ".swift", ".dart", ".m", ".mm", ".scala", ".sc", ".hs", ".lhs", ".ex", ".exs", ".erl", ".hrl", ".clj", ".cljs", ".cljc", ".edn", ".fs", ".fsx", ".fsi", ".ml", ".mli", ".lisp", ".lsp", ".cl", ".scm", ".ss", ".groovy", ".gvy", ".gy", ".gsh", ".ps1", ".psm1", ".psd1", ".r", ".rmd", ".jl", ".nim", ".nims", ".zig", ".v", ".cr", ".d", ".asm", ".s", ".f", ".f90", ".f95", ".f03", ".for", ".cob", ".cbl", ".cobol", ".pas", ".pp", ".p", ".ada", ".adb", ".ads", ".vue", ".svelte", ".hbs", ".handlebars", ".ejs", ".pug", ".jade", ".css", ".scss", ".sass", ".less", ".styl", ".stylus", ".dockerfile", ".mk"];
|
|
256
273
|
readonly CONFIG: readonly [".env", ".ini", ".toml", ".cfg", ".conf", ".config", ".properties", ".editorconfig", ".gitignore", ".gitattributes", ".npmrc", ".yarnrc", ".prettierrc", ".eslintrc", ".babelrc"];
|
|
257
|
-
readonly ARCHIVES: readonly [
|
|
258
|
-
readonly VIDEO: readonly [
|
|
259
|
-
readonly AUDIO: readonly [
|
|
274
|
+
readonly ARCHIVES: readonly string[];
|
|
275
|
+
readonly VIDEO: readonly string[];
|
|
276
|
+
readonly AUDIO: readonly string[];
|
|
260
277
|
readonly DESIGN: readonly [".psd", ".psb", ".ai", ".sketch", ".fig", ".xd"];
|
|
261
278
|
readonly DATABASE: readonly [".db", ".sqlite", ".sqlite3", ".mdb", ".accdb"];
|
|
262
279
|
readonly EXECUTABLE: readonly [".exe", ".dll", ".so", ".dylib", ".app", ".bat", ".cmd", ".vbs", ".ps1", ".msi", ".dmg", ".bin"];
|
|
@@ -2,31 +2,32 @@
|
|
|
2
2
|
* File Extension Constants
|
|
3
3
|
* Centralized file extension definitions organized by category for file processing
|
|
4
4
|
*
|
|
5
|
+
* The media/document/data/archive categories are derived from
|
|
6
|
+
* {@link FILE_TYPE_REGISTRY} rather than typed out here — they used to be a
|
|
7
|
+
* second hand-maintained list that drifted from both the registry-equivalent
|
|
8
|
+
* MIME map and the detector, so formats appeared "supported" in one place and
|
|
9
|
+
* were invisible in another. Source-code and text categories, which carry no
|
|
10
|
+
* modality, are still enumerated below.
|
|
11
|
+
*
|
|
5
12
|
* @module processors/config/fileTypes
|
|
6
13
|
*/
|
|
14
|
+
import { extensionsForModality } from "./fileTypeRegistry.js";
|
|
7
15
|
// =============================================================================
|
|
8
16
|
// IMAGE FILE EXTENSIONS
|
|
9
17
|
// =============================================================================
|
|
10
18
|
/**
|
|
11
|
-
* Image file extensions supported by the platform
|
|
19
|
+
* Image file extensions supported by the platform.
|
|
20
|
+
* Includes `.svg`, which is processed as sanitized markup rather than raster.
|
|
12
21
|
*/
|
|
13
|
-
export const IMAGE_EXTENSIONS =
|
|
14
|
-
".jpg",
|
|
15
|
-
".jpeg",
|
|
16
|
-
".png",
|
|
17
|
-
".gif",
|
|
18
|
-
".webp",
|
|
19
|
-
".svg",
|
|
20
|
-
".bmp",
|
|
21
|
-
".tiff",
|
|
22
|
-
".tif",
|
|
23
|
-
".avif",
|
|
24
|
-
".heic",
|
|
25
|
-
".heif",
|
|
26
|
-
".ico",
|
|
27
|
-
];
|
|
22
|
+
export const IMAGE_EXTENSIONS = extensionsForModality("image");
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Image extensions every vision-capable provider accepts as-is.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately NOT derived from the registry: this is the intersection of what
|
|
27
|
+
* OpenAI, Anthropic, Google and Bedrock all accept, not the set NeuroLink can
|
|
28
|
+
* identify. Anything else the registry knows about (BMP, TIFF, AVIF, ICO,
|
|
29
|
+
* HEIC/HEIF) is transcoded to PNG before dispatch — see
|
|
30
|
+
* `adapters/imageFormatSupport.ts`.
|
|
30
31
|
*/
|
|
31
32
|
export const AI_VISION_EXTENSIONS = [
|
|
32
33
|
".jpg",
|
|
@@ -41,19 +42,7 @@ export const AI_VISION_EXTENSIONS = [
|
|
|
41
42
|
/**
|
|
42
43
|
* Document file extensions for office documents and PDFs
|
|
43
44
|
*/
|
|
44
|
-
export const DOCUMENT_EXTENSIONS =
|
|
45
|
-
".pdf",
|
|
46
|
-
".docx",
|
|
47
|
-
".doc",
|
|
48
|
-
".xlsx",
|
|
49
|
-
".xls",
|
|
50
|
-
".pptx",
|
|
51
|
-
".ppt",
|
|
52
|
-
".odt",
|
|
53
|
-
".ods",
|
|
54
|
-
".odp",
|
|
55
|
-
".rtf",
|
|
56
|
-
];
|
|
45
|
+
export const DOCUMENT_EXTENSIONS = extensionsForModality("document");
|
|
57
46
|
/**
|
|
58
47
|
* PDF document extensions
|
|
59
48
|
*/
|
|
@@ -98,14 +87,17 @@ export const CSV_EXTENSIONS = [".csv"];
|
|
|
98
87
|
*/
|
|
99
88
|
export const YAML_EXTENSIONS = [".yaml", ".yml"];
|
|
100
89
|
/**
|
|
101
|
-
* All data format extensions combined
|
|
90
|
+
* All data format extensions combined.
|
|
91
|
+
*
|
|
92
|
+
* The tabular formats (.csv/.tsv) come from the registry; .json/.xml/.yaml are
|
|
93
|
+
* text formats with no modality of their own and stay listed here.
|
|
102
94
|
*/
|
|
103
95
|
export const DATA_EXTENSIONS = [
|
|
104
96
|
".json",
|
|
105
97
|
".xml",
|
|
106
|
-
".csv",
|
|
107
98
|
".yaml",
|
|
108
99
|
".yml",
|
|
100
|
+
...extensionsForModality("data"),
|
|
109
101
|
];
|
|
110
102
|
// =============================================================================
|
|
111
103
|
// TEXT FILE EXTENSIONS
|
|
@@ -346,49 +338,18 @@ export const CONFIG_EXTENSIONS = [
|
|
|
346
338
|
/**
|
|
347
339
|
* Archive and compressed file extensions
|
|
348
340
|
*/
|
|
349
|
-
export const ARCHIVE_EXTENSIONS =
|
|
350
|
-
".zip",
|
|
351
|
-
".tar",
|
|
352
|
-
".gz",
|
|
353
|
-
".tgz",
|
|
354
|
-
".tar.gz",
|
|
355
|
-
".tar.bz2",
|
|
356
|
-
".bz2",
|
|
357
|
-
".rar",
|
|
358
|
-
".7z",
|
|
359
|
-
".xz",
|
|
360
|
-
];
|
|
341
|
+
export const ARCHIVE_EXTENSIONS = extensionsForModality("archive");
|
|
361
342
|
// =============================================================================
|
|
362
343
|
// MULTIMEDIA FILE EXTENSIONS
|
|
363
344
|
// =============================================================================
|
|
364
345
|
/**
|
|
365
346
|
* Video file extensions
|
|
366
347
|
*/
|
|
367
|
-
export const VIDEO_EXTENSIONS =
|
|
368
|
-
".mp4",
|
|
369
|
-
".avi",
|
|
370
|
-
".mov",
|
|
371
|
-
".mkv",
|
|
372
|
-
".webm",
|
|
373
|
-
".flv",
|
|
374
|
-
".wmv",
|
|
375
|
-
".m4v",
|
|
376
|
-
".mpg",
|
|
377
|
-
".mpeg",
|
|
378
|
-
];
|
|
348
|
+
export const VIDEO_EXTENSIONS = extensionsForModality("video");
|
|
379
349
|
/**
|
|
380
350
|
* Audio file extensions
|
|
381
351
|
*/
|
|
382
|
-
export const AUDIO_EXTENSIONS =
|
|
383
|
-
".mp3",
|
|
384
|
-
".wav",
|
|
385
|
-
".aac",
|
|
386
|
-
".flac",
|
|
387
|
-
".ogg",
|
|
388
|
-
".m4a",
|
|
389
|
-
".wma",
|
|
390
|
-
".opus",
|
|
391
|
-
];
|
|
352
|
+
export const AUDIO_EXTENSIONS = extensionsForModality("audio");
|
|
392
353
|
// =============================================================================
|
|
393
354
|
// DESIGN FILE EXTENSIONS
|
|
394
355
|
// =============================================================================
|