@juspay/neurolink 12.19.1 → 12.20.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 -2
- package/README.md +12 -12
- package/dist/adapters/imageFormatSupport.d.ts +5 -2
- package/dist/adapters/imageFormatSupport.js +47 -24
- package/dist/browser/neurolink.min.js +363 -363
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/core/modules/MessageBuilder.js +2 -0
- package/dist/neurolink.js +1 -0
- package/dist/processors/config/fileExtensions.d.ts +8 -1
- package/dist/processors/config/fileExtensions.js +8 -1
- package/dist/proxy/vertexAnthropicFallback.d.ts +0 -1
- package/dist/proxy/vertexAnthropicFallback.js +30 -0
- package/dist/rag/pipeline/RAGPipeline.js +29 -0
- package/dist/rag/ragIntegration.js +21 -1
- package/dist/types/file.d.ts +10 -0
- package/dist/types/generate.d.ts +27 -1
- package/dist/types/stream.d.ts +13 -1
- package/dist/utils/imageProcessor.js +151 -6
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +23 -13
- package/dist/utils/multimodalOptionsBuilder.d.ts +3 -0
- package/dist/utils/multimodalOptionsBuilder.js +1 -0
- package/docs/changelog.md +14 -5
- package/docs-site/static/search-index.json +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
## [12.
|
|
1
|
+
## [12.20.0](https://github.com/juspay/neurolink/compare/v12.19.2...v12.20.0) (2026-09-22)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(images):** let a caller choose the transcode output format ([f536fd0](https://github.com/juspay/neurolink/commit/f536fd0916f9e688c723cf679e05f8d995d12e9f))
|
|
2
6
|
|
|
3
7
|
### Bug Fixes
|
|
4
8
|
|
|
5
|
-
- **(
|
|
9
|
+
- **(rag):** keep SVG out of the raster image path and scope the sanitized-markup claim ([0716f94](https://github.com/juspay/neurolink/commit/0716f944d097b2a8944e4036a72fccf3f7c636b6))
|
|
6
10
|
|
|
7
11
|
## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
|
|
8
12
|
|
package/README.md
CHANGED
|
@@ -39,17 +39,17 @@ const decision = await pipe.tryDecide({
|
|
|
39
39
|
// decision?.answers.autoApprove.probability -> 0.91
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
**[→ Docs](https://docs.neurolink.ink) · [→ Quick Start](https://docs.neurolink.ink/docs/getting-started/quick-start) · [→ npm](https://www.npmjs.com/package/@juspay/neurolink)**
|
|
42
|
+
**[→ Docs](https://docs.neurolink.ink) · [→ Quick Start](https://docs.neurolink.ink/docs/getting-started/quick-start) · [→ npm](https://www.npmjs.com/package/@juspay/neurolink) · [→ Blog](https://blog.neurolink.ink)**
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
46
|
## 🧠 What is NeuroLink?
|
|
47
47
|
|
|
48
|
-
**NeuroLink is the
|
|
48
|
+
**NeuroLink is the pipe layer of an AI nervous system.** Providers — OpenAI, Anthropic, Google, AWS, Azure, Mistral, local runtimes like Ollama, and dozens more — are the neurons: each generates a different kind of intelligence, at a different cost and latency. NeuroLink is the vascular layer that carries that intelligence, as a stream, to the applications — the organs — that consume it, across three inference types: `generate` and `stream` produce text, `decide` produces a calibrated `boolean`/`choice`/`score` judgment instead. A curated model registry (64 models, 132 aliases) backs metadata, routing, and context-window checks out of the box, and hundreds more models are reachable through aggregator providers — 100+ via LiteLLM, 300+ via OpenRouter.
|
|
49
49
|
|
|
50
|
-
Extracted from production systems at Juspay, NeuroLink provides a practical, TypeScript-first way to
|
|
50
|
+
Extracted from production systems at Juspay, NeuroLink provides a practical, TypeScript-first way to plug any application into that nervous system. Switch which neuron answers a request with a single parameter change — OpenAI, Anthropic, Google, AWS Bedrock, Azure, a local runtime, or any provider you add. `decide` is the third inference type — a typed, calibrated judgment instead of text — for the model-routing and gating decisions `generate`/`stream` were never meant to make, powered by a purpose-built decision model (TypeSafe Jev) rather than a general-purpose LLM: routing decisions land in ~400ms for about $0.00002, instead of a full generation call.
|
|
51
51
|
|
|
52
|
-
**Why NeuroLink?** Three genuine inference types, not one dressed up three ways — `generate` and `stream` produce text; `decide` produces a calibrated `boolean`/`choice`/`score` judgment, and which types a provider serves is declared per-provider via `inferenceKinds` rather than inferred from behavior.
|
|
52
|
+
**Why NeuroLink?** Three genuine inference types, not one dressed up three ways — `generate` and `stream` produce text; `decide` produces a calibrated `boolean`/`choice`/`score` judgment, and which types a provider serves is declared per-provider via `inferenceKinds` rather than inferred from behavior. Every neuron plugs into the same pipe, including 3 fully local runtimes (Ollama, LM Studio, llama.cpp) with per-request credential overrides, and MCP support covers all 4 transports (stdio, HTTP, SSE, WebSocket). Every AI-driven optimization the pipe performs — model routing, context compaction, tool selection — fails open: no key configured behaves exactly like NeuroLink without it, and routing uses asymmetric confidence thresholds (upgrade at 0.3, downgrade at 0.6) rather than a single cutoff, because a wrong downgrade costs more than a wrong upgrade. Switch providers with a single parameter change, leverage built-in tools plus any MCP-compliant tool server, deploy with confidence using enterprise features like Redis memory and multi-provider failover, and optimize costs automatically with intelligent routing. Use it via our professional CLI or TypeScript SDK—whichever fits your workflow.
|
|
53
53
|
|
|
54
54
|
**Where we're headed:** We're building for the future of AI—edge-first execution and continuous streaming architectures that make AI practically free and universally available. **[Read our vision →](docs/about/vision.md)**
|
|
55
55
|
|
|
@@ -410,7 +410,7 @@ npx @juspay/neurolink --help
|
|
|
410
410
|
|
|
411
411
|
### Configuration
|
|
412
412
|
|
|
413
|
-
NeuroLink works with
|
|
413
|
+
NeuroLink works with every major AI provider — and local runtimes that need no API key at all. You'll need at least one to get started:
|
|
414
414
|
|
|
415
415
|
**Option 1: Interactive Setup (Recommended)**
|
|
416
416
|
|
|
@@ -597,7 +597,7 @@ const result = await neurolink.generate({
|
|
|
597
597
|
### Next Steps
|
|
598
598
|
|
|
599
599
|
- **[Complete Documentation](https://docs.neurolink.ink)** - Comprehensive guides and API reference
|
|
600
|
-
- **[Provider Setup Guide](docs/getting-started/provider-setup.md)** - Configure
|
|
600
|
+
- **[Provider Setup Guide](docs/getting-started/provider-setup.md)** - Configure any provider
|
|
601
601
|
- **[SDK API Reference](docs/sdk/api-reference.md)** - Full TypeScript API documentation
|
|
602
602
|
- **[CLI Command Reference](docs/cli/commands.md)** - Complete CLI documentation
|
|
603
603
|
- **[Example Projects](docs/examples/index.md)** - Real-world integration examples
|
|
@@ -629,7 +629,7 @@ NeuroLink is a comprehensive AI development platform. Every feature below is shi
|
|
|
629
629
|
|
|
630
630
|
### 🤖 AI Provider Integration
|
|
631
631
|
|
|
632
|
-
**
|
|
632
|
+
**Every provider neuron behind one API** - Switch providers with a single parameter change. Nearly all serve `generate`/`stream`; TypeSafe Jev alone serves `decide`. Tool support: 29 native tool-calling, 3 model-dependent, 8 that serve no tools at all (embedding-, media- and decision-only). 3 are fully local runtimes (Ollama, LM Studio, llama.cpp) and 4 need zero configuration to start (those three plus LiteLLM) — no cloud account, no API key. 9 providers (OpenAI, Google AI Studio, Google Vertex, Amazon Bedrock, Cohere, Ollama, LiteLLM, Voyage, Jina) expose `embed()`/`embedMany()` natively for RAG and custom vector search.
|
|
633
633
|
|
|
634
634
|
| Provider | Models | Free Tier | Tool Support | Status | Documentation |
|
|
635
635
|
| --------------------- | -------------------------------------------------------------------------- | --------------- | ------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -668,7 +668,7 @@ NeuroLink is a comprehensive AI development platform. Every feature below is shi
|
|
|
668
668
|
**Decision-only provider:** **TypeSafe Jev** (`TYPESAFE_API_KEY`) does not appear in the table above because it does not serve `generate`/`stream` — it is the first provider for the `decide` inference type. See [Decide: Calibrated Judgments, Not Text](#decide-calibrated-judgments-not-text).
|
|
669
669
|
|
|
670
670
|
**[📖 Provider Comparison Guide](docs/reference/provider-comparison.md)** - Detailed feature matrix and selection criteria
|
|
671
|
-
**[🔬 Provider Feature Compatibility](docs/reference/provider-feature-compatibility.md)** - Test-based compatibility reference for all 19 features across
|
|
671
|
+
**[🔬 Provider Feature Compatibility](docs/reference/provider-feature-compatibility.md)** - Test-based compatibility reference for all 19 features across every provider
|
|
672
672
|
|
|
673
673
|
---
|
|
674
674
|
|
|
@@ -854,7 +854,7 @@ neurolink generate "Describe what happens" --file ./demo.mp4
|
|
|
854
854
|
- **ProcessorRegistry** - Priority-based processor selection with fallback
|
|
855
855
|
- **OWASP Security** - HTML/SVG sanitization prevents XSS attacks
|
|
856
856
|
- **Auto-detection** - FileDetector identifies file types by extension and content
|
|
857
|
-
- **Provider-agnostic** - All processors work across
|
|
857
|
+
- **Provider-agnostic** - All processors work across every AI provider
|
|
858
858
|
|
|
859
859
|
**[📖 File Processors Guide](docs/features/file-processors.md)** - Complete reference for all file types
|
|
860
860
|
|
|
@@ -984,7 +984,7 @@ node your-app.js
|
|
|
984
984
|
|
|
985
985
|
### 🤖 GitHub Action
|
|
986
986
|
|
|
987
|
-
Run AI-powered workflows directly in GitHub Actions with
|
|
987
|
+
Run AI-powered workflows directly in GitHub Actions with support for every provider and automatic PR/issue commenting.
|
|
988
988
|
|
|
989
989
|
```yaml
|
|
990
990
|
- uses: juspay/neurolink@v1
|
|
@@ -996,7 +996,7 @@ Run AI-powered workflows directly in GitHub Actions with 40 provider support and
|
|
|
996
996
|
|
|
997
997
|
| Feature | Description |
|
|
998
998
|
| ---------------------- | ----------------------------------------------------------------------------------------- |
|
|
999
|
-
| **Multi-Provider** |
|
|
999
|
+
| **Multi-Provider** | Every provider behind one unified interface |
|
|
1000
1000
|
| **PR/Issue Comments** | Auto-post AI responses with intelligent updates |
|
|
1001
1001
|
| **Multimodal Support** | Attach images, PDFs, CSVs, Excel, Word, JSON, YAML, XML, HTML, SVG, code files to prompts |
|
|
1002
1002
|
| **Cost Tracking** | Built-in analytics and quality evaluation |
|
|
@@ -1189,7 +1189,7 @@ Full command and API breakdown lives in [`docs/cli/commands.md`](docs/cli/comman
|
|
|
1189
1189
|
|
|
1190
1190
|
| Capability | Highlights |
|
|
1191
1191
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1192
|
-
| **Provider unification** |
|
|
1192
|
+
| **Provider unification** | Every provider neuron behind one API, with automatic fallback, cost-aware routing, `providerFallback` policy, `modelChain` config. |
|
|
1193
1193
|
| **Decision inference** | Third inference type (`decide`) alongside generate/stream: calibrated `boolean`/`choice`/`score` judgments via TypeSafe Jev, ~400ms flat, ~$0.00002/decision. Used internally for model routing, context budgeting, relevance compaction and tool routing; per-query RAG planning is opt-in via `RAGPipeline`. |
|
|
1194
1194
|
| **Multimodal pipeline** | Stream images + CSV data + PDF documents across providers with local/remote assets. Auto-detection for mixed file types. |
|
|
1195
1195
|
| **Voice pipeline** | TTS (6 providers: Google, OpenAI, ElevenLabs, Azure, Fish Audio, Cartesia) + STT (4 providers) + realtime voice APIs (OpenAI Realtime, Gemini Live). |
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* @module adapters/imageFormatSupport
|
|
21
21
|
*/
|
|
22
|
-
import type { ImageWithAltText, VisionImageConversion } from "../types/index.js";
|
|
22
|
+
import type { ImageWithAltText, VisionImageConversion, VisionImageOutputFormat } from "../types/index.js";
|
|
23
23
|
/**
|
|
24
24
|
* MIME types every vision-capable provider accepts as-is.
|
|
25
25
|
*
|
|
@@ -57,8 +57,11 @@ export declare function needsVisionTranscode(mimeType: string): boolean;
|
|
|
57
57
|
*
|
|
58
58
|
* @param buffer - Raw image bytes.
|
|
59
59
|
* @param mimeType - Detected MIME type of `buffer`.
|
|
60
|
+
* @param outputFormat - Transcode target. Defaults to `"png"` — see the
|
|
61
|
+
* module header for why that stays the default for every caller who
|
|
62
|
+
* doesn't opt into `"jpeg"` explicitly.
|
|
60
63
|
*/
|
|
61
|
-
export declare function toVisionCompatibleImage(buffer: Buffer, mimeType: string): Promise<VisionImageConversion>;
|
|
64
|
+
export declare function toVisionCompatibleImage(buffer: Buffer, mimeType: string, outputFormat?: VisionImageOutputFormat): Promise<VisionImageConversion>;
|
|
62
65
|
/**
|
|
63
66
|
* Unwrap an `input.images` entry to its payload.
|
|
64
67
|
*
|
|
@@ -23,7 +23,12 @@ import { extensionForMimeType } from "../processors/config/fileTypeRegistry.js";
|
|
|
23
23
|
import { withTimeout } from "../utils/errorHandling.js";
|
|
24
24
|
import { logger } from "../utils/logger.js";
|
|
25
25
|
import { tryImport } from "../utils/tryImport.js";
|
|
26
|
-
import { getFfmpegPath, runFfmpeg } from "./video/ffmpegAdapter.js";
|
|
26
|
+
import { getFfmpegPath, JPEG_QUALITY, runFfmpeg, } from "./video/ffmpegAdapter.js";
|
|
27
|
+
/**
|
|
28
|
+
* Default transcode target, and the one every existing caller keeps getting:
|
|
29
|
+
* see the module header for why PNG rather than JPEG is the right default.
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_VISION_IMAGE_OUTPUT_FORMAT = "png";
|
|
27
32
|
/**
|
|
28
33
|
* Per-backend ceiling for one image conversion.
|
|
29
34
|
*
|
|
@@ -106,7 +111,7 @@ export function needsVisionTranscode(mimeType) {
|
|
|
106
111
|
* only — actual HEVC-coded HEIC fails inside libheif, and BMP, ICO and
|
|
107
112
|
* JPEG 2000 are not compiled in at all. Those fall through to ffmpeg below.
|
|
108
113
|
*/
|
|
109
|
-
async function transcodeWithSharp(buffer) {
|
|
114
|
+
async function transcodeWithSharp(buffer, outputFormat) {
|
|
110
115
|
const sharpModule = await tryImport("sharp", "Image format conversion for vision providers");
|
|
111
116
|
// A shape guard, not a crash guard: an absent or malformed sharp already
|
|
112
117
|
// fails safely, because `tryImport` throws a named install error and any
|
|
@@ -115,9 +120,9 @@ async function transcodeWithSharp(buffer) {
|
|
|
115
120
|
// failure list instead of "sharpModule.default is not a function".
|
|
116
121
|
//
|
|
117
122
|
// It deliberately throws rather than returning `buffer`: returning the input
|
|
118
|
-
// would report a successful conversion and relabel the original bytes
|
|
119
|
-
// and would also skip the ffmpeg backend — the one
|
|
120
|
-
// HEIC, BMP, ICO and JPEG 2000.
|
|
123
|
+
// would report a successful conversion and relabel the original bytes under
|
|
124
|
+
// the requested format, and would also skip the ffmpeg backend — the one
|
|
125
|
+
// that actually handles HEIC, BMP, ICO and JPEG 2000.
|
|
121
126
|
if (typeof sharpModule?.default !== "function") {
|
|
122
127
|
throw new Error("the installed sharp package does not expose a callable default export");
|
|
123
128
|
}
|
|
@@ -125,9 +130,15 @@ async function transcodeWithSharp(buffer) {
|
|
|
125
130
|
// a multi-page TIFF) from being flattened into one tall strip — the first
|
|
126
131
|
// frame is what a vision model should receive.
|
|
127
132
|
const pipeline = sharpModule.default(buffer, { pages: 1 });
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
if (outputFormat === "jpeg") {
|
|
134
|
+
// The instance shape is checked as well as the factory: a build that
|
|
135
|
+
// exports a callable but returns something without `.jpeg()` would
|
|
136
|
+
// otherwise fail as an opaque TypeError inside the backend loop.
|
|
137
|
+
if (typeof pipeline?.jpeg !== "function") {
|
|
138
|
+
throw new Error("the installed sharp package returned a pipeline without a jpeg() encoder");
|
|
139
|
+
}
|
|
140
|
+
return pipeline.jpeg().toBuffer();
|
|
141
|
+
}
|
|
131
142
|
if (typeof pipeline?.png !== "function") {
|
|
132
143
|
throw new Error("the installed sharp package returned a pipeline without a png() encoder");
|
|
133
144
|
}
|
|
@@ -151,7 +162,7 @@ async function transcodeWithSharp(buffer) {
|
|
|
151
162
|
* `mkdtemp`. This whole path is server-only — nothing in a browser is going to
|
|
152
163
|
* spawn ffmpeg — so the import belongs where it is used.
|
|
153
164
|
*/
|
|
154
|
-
async function transcodeWithFfmpeg(buffer, extension, binaryPath) {
|
|
165
|
+
async function transcodeWithFfmpeg(buffer, extension, outputFormat, binaryPath) {
|
|
155
166
|
const [{ randomUUID }, { mkdtemp, readFile, rm, writeFile }, { tmpdir }, { join },] = await Promise.all([
|
|
156
167
|
import("node:crypto"),
|
|
157
168
|
import("node:fs/promises"),
|
|
@@ -160,7 +171,13 @@ async function transcodeWithFfmpeg(buffer, extension, binaryPath) {
|
|
|
160
171
|
]);
|
|
161
172
|
const workDir = await mkdtemp(join(tmpdir(), "neurolink-img-"));
|
|
162
173
|
const inputPath = join(workDir, `${randomUUID()}${extension}`);
|
|
163
|
-
|
|
174
|
+
// "mjpeg" is ffmpeg's codec name for a JPEG image2 stream; "png" doubles as
|
|
175
|
+
// both the codec name and the output extension.
|
|
176
|
+
const outputExtension = outputFormat === "jpeg" ? ".jpg" : ".png";
|
|
177
|
+
const codecArgs = outputFormat === "jpeg"
|
|
178
|
+
? ["-c:v", "mjpeg", "-q:v", JPEG_QUALITY]
|
|
179
|
+
: ["-c:v", "png"];
|
|
180
|
+
const outputPath = join(workDir, `${randomUUID()}${outputExtension}`);
|
|
164
181
|
try {
|
|
165
182
|
await writeFile(inputPath, buffer);
|
|
166
183
|
await runFfmpeg([
|
|
@@ -169,14 +186,13 @@ async function transcodeWithFfmpeg(buffer, extension, binaryPath) {
|
|
|
169
186
|
"error",
|
|
170
187
|
"-i",
|
|
171
188
|
inputPath,
|
|
172
|
-
// Take a single frame so a multi-image container yields one
|
|
173
|
-
// than ffmpeg erroring on a missing
|
|
189
|
+
// Take a single frame so a multi-image container yields one output
|
|
190
|
+
// image rather than ffmpeg erroring on a missing sequence pattern.
|
|
174
191
|
"-frames:v",
|
|
175
192
|
"1",
|
|
176
193
|
"-f",
|
|
177
194
|
"image2",
|
|
178
|
-
|
|
179
|
-
"png",
|
|
195
|
+
...codecArgs,
|
|
180
196
|
outputPath,
|
|
181
197
|
], binaryPath ? { binaryPath } : {});
|
|
182
198
|
return await readFile(outputPath);
|
|
@@ -194,17 +210,20 @@ async function transcodeWithFfmpeg(buffer, extension, binaryPath) {
|
|
|
194
210
|
* so when the resolved binary is not already the system one it is retried
|
|
195
211
|
* explicitly rather than reporting a photo as unsupported.
|
|
196
212
|
*/
|
|
197
|
-
async function* transcodeBackends(buffer, extension) {
|
|
198
|
-
yield {
|
|
213
|
+
async function* transcodeBackends(buffer, extension, outputFormat) {
|
|
214
|
+
yield {
|
|
215
|
+
name: "sharp",
|
|
216
|
+
run: () => transcodeWithSharp(buffer, outputFormat),
|
|
217
|
+
};
|
|
199
218
|
const resolved = await getFfmpegPath().catch(() => "ffmpeg");
|
|
200
219
|
yield {
|
|
201
220
|
name: "ffmpeg",
|
|
202
|
-
run: () => transcodeWithFfmpeg(buffer, extension),
|
|
221
|
+
run: () => transcodeWithFfmpeg(buffer, extension, outputFormat),
|
|
203
222
|
};
|
|
204
223
|
if (resolved !== "ffmpeg") {
|
|
205
224
|
yield {
|
|
206
225
|
name: "system ffmpeg",
|
|
207
|
-
run: () => transcodeWithFfmpeg(buffer, extension, "ffmpeg"),
|
|
226
|
+
run: () => transcodeWithFfmpeg(buffer, extension, outputFormat, "ffmpeg"),
|
|
208
227
|
};
|
|
209
228
|
}
|
|
210
229
|
}
|
|
@@ -220,15 +239,19 @@ async function* transcodeBackends(buffer, extension) {
|
|
|
220
239
|
*
|
|
221
240
|
* @param buffer - Raw image bytes.
|
|
222
241
|
* @param mimeType - Detected MIME type of `buffer`.
|
|
242
|
+
* @param outputFormat - Transcode target. Defaults to `"png"` — see the
|
|
243
|
+
* module header for why that stays the default for every caller who
|
|
244
|
+
* doesn't opt into `"jpeg"` explicitly.
|
|
223
245
|
*/
|
|
224
|
-
export async function toVisionCompatibleImage(buffer, mimeType) {
|
|
246
|
+
export async function toVisionCompatibleImage(buffer, mimeType, outputFormat = DEFAULT_VISION_IMAGE_OUTPUT_FORMAT) {
|
|
225
247
|
if (!needsVisionTranscode(mimeType)) {
|
|
226
248
|
return { buffer, mimeType, converted: false };
|
|
227
249
|
}
|
|
228
250
|
const normalized = mimeType.split(";")[0].trim().toLowerCase();
|
|
229
251
|
const extension = extensionForMimeType(normalized) ?? ".bin";
|
|
252
|
+
const targetMimeType = outputFormat === "jpeg" ? "image/jpeg" : "image/png";
|
|
230
253
|
const failures = [];
|
|
231
|
-
for await (const backend of transcodeBackends(buffer, extension)) {
|
|
254
|
+
for await (const backend of transcodeBackends(buffer, extension, outputFormat)) {
|
|
232
255
|
try {
|
|
233
256
|
// Both backends can stall — sharp on a malformed stream, ffmpeg on a
|
|
234
257
|
// container it half-understands — and this runs inline on the request
|
|
@@ -238,16 +261,16 @@ export async function toVisionCompatibleImage(buffer, mimeType) {
|
|
|
238
261
|
if (converted.length === 0) {
|
|
239
262
|
throw new Error("produced an empty image");
|
|
240
263
|
}
|
|
241
|
-
logger.debug(`[imageFormatSupport] Transcoded ${normalized} →
|
|
264
|
+
logger.debug(`[imageFormatSupport] Transcoded ${normalized} → ${targetMimeType} via ${backend.name} ` +
|
|
242
265
|
`(${buffer.length} → ${converted.length} bytes) for vision compatibility`);
|
|
243
|
-
return { buffer: converted, mimeType:
|
|
266
|
+
return { buffer: converted, mimeType: targetMimeType, converted: true };
|
|
244
267
|
}
|
|
245
268
|
catch (error) {
|
|
246
269
|
failures.push(`${backend.name}: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
247
270
|
}
|
|
248
271
|
}
|
|
249
|
-
logger.warn(`[imageFormatSupport] Could not transcode ${normalized} to
|
|
250
|
-
`original bytes, which most vision providers will reject. Install a full ` +
|
|
272
|
+
logger.warn(`[imageFormatSupport] Could not transcode ${normalized} to ${targetMimeType} — sending ` +
|
|
273
|
+
`the original bytes, which most vision providers will reject. Install a full ` +
|
|
251
274
|
`ffmpeg build (or set FFMPEG_PATH to one) to enable this format. ` +
|
|
252
275
|
`Tried ${failures.join("; ")}`);
|
|
253
276
|
return { buffer, mimeType, converted: false };
|