@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 CHANGED
@@ -1,8 +1,12 @@
1
- ## [12.19.1](https://github.com/juspay/neurolink/compare/v12.19.0...v12.19.1) (2026-09-21)
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
- - **(proxy):** pin each bridged Codex conversation to one prompt cache ([1ec34a9](https://github.com/juspay/neurolink/commit/1ec34a940ced3ba5d35ccaaee5141928ffe03e8f))
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 universal AI integration platform that unifies 40 AI providers under one consistent API, across three inference types: `generate`, `stream`, and `decide`.** A curated 64-model registry (7 providers, 132 aliases) backs model 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.
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 integrate AI into any application. Whether you're building with OpenAI, Anthropic, Google, AWS Bedrock, Azure, or any of our 40 supported providers, NeuroLink gives you a single, consistent interface that works everywhere. `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.
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. One API spans all 40 providers, 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 — 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.
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 40 AI providers. You'll need at least one API key to get started:
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 all 40 providers
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
- **40 providers unified under one API** - Switch providers with a single parameter change. 39 serve `generate`/`stream`; 1 (TypeSafe Jev) 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.
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 40 providers
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 all 40 AI providers
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 40 provider support and automatic PR/issue commenting.
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** | 40 providers with unified interface |
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** | 40 providers with automatic fallback, cost-aware routing, `providerFallback` policy, `modelChain` config. |
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 as PNG,
119
- // and would also skip the ffmpeg backend — the one that actually handles
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
- // 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.
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
- const outputPath = join(workDir, `${randomUUID()}.png`);
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 PNG rather
173
- // than ffmpeg erroring on a missing output-sequence pattern.
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
- "-c:v",
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 { name: "sharp", run: () => transcodeWithSharp(buffer) };
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} → image/png via ${backend.name} ` +
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: "image/png", converted: true };
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 PNG — sending the ` +
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 };