@juspay/neurolink 10.10.10 → 10.10.12

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.
@@ -45,13 +45,32 @@ const EXTENSION_TYPE_MAP = {
45
45
  // Image
46
46
  jpg: "image",
47
47
  jpeg: "image",
48
+ jpe: "image",
49
+ jfif: "image",
48
50
  png: "image",
51
+ apng: "image",
49
52
  gif: "image",
50
53
  webp: "image",
51
54
  bmp: "image",
52
55
  tiff: "image",
53
56
  tif: "image",
54
57
  avif: "image",
58
+ // Below this line the entries exist because this map now decides whether an
59
+ // image is processed eagerly (see isEagerMultimodalFile), not merely how its
60
+ // tokens are estimated. An extension missing here classifies as `undefined`
61
+ // and takes the lazy path, which drops the pixels — so a gap is a dropped
62
+ // photo, not a slightly-off estimate. HEIC is the one that matters most:
63
+ // it is what an iPhone writes by default.
64
+ heic: "image",
65
+ heif: "image",
66
+ ico: "image",
67
+ jp2: "image",
68
+ jpx: "image",
69
+ // NB: `svg` is deliberately absent here — it is mapped to its own "svg"
70
+ // routing type further down, and listing it twice is a duplicate key whose
71
+ // second entry silently wins. `isEagerMultimodalFile` accepts both types
72
+ // instead, so SVG takes the eager path without this map having to lie about
73
+ // which processor handles it.
55
74
  // Archive
56
75
  zip: "archive",
57
76
  tar: "archive",
@@ -101,6 +120,19 @@ function inferFileTypeFromBuffer(buf) {
101
120
  if (buf.length < 4) {
102
121
  return undefined;
103
122
  }
123
+ // SVG is markup and has no magic number, so every signature check below
124
+ // misses it and a raw SVG Buffer classified as `undefined` — which meant the
125
+ // lazy path, which previews markup away. Checked first because the sniff is
126
+ // a cheap look at the head and cannot collide with a binary signature.
127
+ //
128
+ // Deliberately a substring scan over the head rather than a prolog-stripping
129
+ // regex: the obvious pattern for skipping comments and a DOCTYPE is two
130
+ // nested quantifiers, which is a ReDoS waiting to happen inside what is
131
+ // supposed to be a cheap type check. Over-matching is harmless here — the
132
+ // only consequence of a false positive is that a file is processed eagerly.
133
+ if (buf.subarray(0, 1024).toString("latin1").includes("<svg")) {
134
+ return "svg";
135
+ }
104
136
  // PNG
105
137
  if (buf[0] === 0x89 &&
106
138
  buf[1] === 0x50 &&
@@ -863,7 +895,9 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
863
895
  try {
864
896
  // ─── Lazy file registration path ──────────────────────────────
865
897
  const fileSize = fileRegistry ? getFileSize(file) : 0;
866
- if (fileRegistry && fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX) {
898
+ if (fileRegistry &&
899
+ fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX &&
900
+ !isEagerMultimodalFile(file)) {
867
901
  const registered = await tryRegisterFileReference(file, fileSize, fileRegistry, fileIdx);
868
902
  if (registered) {
869
903
  logger.info(`[NEUROLINK] File lazily registered: ${filename} (${fileSize} bytes) — deferred processing`);
@@ -2027,6 +2061,105 @@ function getFileSource(file) {
2027
2061
  }
2028
2062
  return "buffer";
2029
2063
  }
2064
+ /**
2065
+ * Whether a file must be processed eagerly rather than lazily referenced.
2066
+ *
2067
+ * The lazy path registers a file and injects a short textual *preview* in place
2068
+ * of the file itself. Whether that is an acceptable trade depends entirely on
2069
+ * whether a description of the file can answer questions about it:
2070
+ *
2071
+ * pdf lazy -> preview carries the extracted text ✔ content survives
2072
+ * image lazy -> preview carries ~98 chars of prose ✘ the pixels are gone
2073
+ *
2074
+ * An image is the case where the bytes ARE the content: no prose summary
2075
+ * substitutes for them, so the model receives a description of a file instead
2076
+ * of the file. Measured end-to-end on `release`, asking "what number is
2077
+ * written in this image?":
2078
+ *
2079
+ * tiny.png 1.6 KB -> "7391" (under TINY_MAX, eager, correct)
2080
+ * big.png 11 KB -> NOTHING_RECEIVED (lazy, image never arrived)
2081
+ * big.jpg 19 KB -> NOTHING_RECEIVED
2082
+ *
2083
+ * 10 KB is far below any real photo, so this affected essentially every image
2084
+ * attached by path — an ordinary JPEG, not just unusual formats.
2085
+ *
2086
+ * Scoped to images deliberately, and audio deserves spelling out because the
2087
+ * reason is not the one it appears to be. An audio file's message contains only
2088
+ * a metadata block — duration, codec, sample rate — on BOTH paths; no audio
2089
+ * bytes are handed to the provider either way. Moving audio to the eager path
2090
+ * would therefore change nothing about what the model receives. That audio
2091
+ * content never reaches the model at all is a separate and pre-existing gap,
2092
+ * not something this threshold decision can repair, and it is easy to mistake
2093
+ * for working code because the metadata block answers exactly the questions
2094
+ * ("how long is it?", "what sample rate?") a test is most tempted to ask.
2095
+ * Video is left alone for the opposite reason: its frames are measurably
2096
+ * present in the message and a model reads them correctly.
2097
+ *
2098
+ * Note this costs no extra memory: `tryRegisterFileReference` already calls
2099
+ * `getFileBuffer()` and reads the whole file to register it. The lazy path was
2100
+ * never lazy about reading — only about processing — so the difference here is
2101
+ * simply whether the bytes survive.
2102
+ */
2103
+ function isEagerMultimodalFile(file) {
2104
+ if (typeof file === "string") {
2105
+ return isImageLikeType(inferFileTypeFromExtension(file));
2106
+ }
2107
+ if (Buffer.isBuffer(file)) {
2108
+ return isImageLikeType(inferFileTypeFromBuffer(file));
2109
+ }
2110
+ // A `FileWithMetadata` carries two independent declarations, and either one
2111
+ // alone is enough: the shape exists for Slack/Curator-style uploads that
2112
+ // arrive as bytes plus a mimetype, so its `filename` may be extensionless or
2113
+ // simply wrong. Reading them as a `??` chain meant the first *recognised*
2114
+ // name won outright — `upload.pdf` with `mimetype: "image/png"` classified as
2115
+ // a PDF and lost its pixels down the lazy path. Any declaration of an image
2116
+ // is therefore decisive.
2117
+ const declared = [
2118
+ inferFileTypeFromExtension(file.filename),
2119
+ inferFileTypeFromMimetype(file.mimetype),
2120
+ ];
2121
+ if (declared.some(isImageLikeType)) {
2122
+ return true;
2123
+ }
2124
+ // The buffer sniff is the last resort rather than a third vote, because it is
2125
+ // a substring scan: an HTML page with an inline `<svg>` icon in its head
2126
+ // would otherwise be pulled onto the eager path and sent in full, which is
2127
+ // the opposite of what the size tiers are for. It only speaks when nothing
2128
+ // else did.
2129
+ return declared.every((type) => type === undefined)
2130
+ ? isImageLikeType(inferFileTypeFromBuffer(file.buffer))
2131
+ : false;
2132
+ }
2133
+ /**
2134
+ * Whether a routing type should have its bytes preserved rather than previewed.
2135
+ *
2136
+ * "svg" is a separate routing type rather than a sub-case of "image" (it goes
2137
+ * to the sanitizer, not to a vision encoder), but it is still an image as far
2138
+ * as this decision is concerned: its markup IS its content, and previewing it
2139
+ * away leaves the model with nothing. Accepting both keeps this correct
2140
+ * whichever of the two type vocabularies the caller's map uses.
2141
+ */
2142
+ function isImageLikeType(type) {
2143
+ return type === "image" || type === "svg";
2144
+ }
2145
+ /**
2146
+ * Infer a routing type from a caller-declared mimetype.
2147
+ *
2148
+ * Only images matter here — this exists so the eager/lazy decision can read a
2149
+ * mimetype hint — and "application/octet-stream" is deliberately ignored,
2150
+ * because it is the opaque sentinel a caller sends when it knows nothing, not
2151
+ * a claim about content.
2152
+ */
2153
+ function inferFileTypeFromMimetype(mimetype) {
2154
+ if (!mimetype) {
2155
+ return undefined;
2156
+ }
2157
+ const normalized = mimetype.split(";")[0].trim().toLowerCase();
2158
+ if (normalized === "image/svg+xml") {
2159
+ return "svg";
2160
+ }
2161
+ return normalized.startsWith("image/") ? "image" : undefined;
2162
+ }
2030
2163
  /**
2031
2164
  * Try to register a file with the FileReferenceRegistry for lazy processing.
2032
2165
  * Returns true if registration succeeded, false if it failed (caller should
@@ -30,8 +30,14 @@ export declare class OpenAITTS implements TTSHandler {
30
30
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
31
31
  /**
32
32
  * Map TTSAudioFormat to OpenAI response_format.
33
- * OpenAI TTS supports: mp3, wav, opus (ogg maps to opus).
34
- * Unsupported formats are coerced to mp3 with a warning.
33
+ *
34
+ * OpenAI's /audio/speech accepts mp3, opus, aac, flac, wav and pcm. This map
35
+ * previously stopped at mp3/wav/ogg/opus/pcm16, so `flac` — a valid
36
+ * TTSAudioFormat *and* a real OpenAI response_format — was treated as
37
+ * unsupported and silently downgraded to mp3 (#479). A caller who asked for
38
+ * lossless got lossy, and the only signal was a warn-level log.
39
+ *
40
+ * Formats OpenAI genuinely cannot produce still coerce to mp3 with a warning.
35
41
  */
36
42
  private mapFormat;
37
43
  /**
@@ -212,8 +212,14 @@ export class OpenAITTS {
212
212
  }
213
213
  /**
214
214
  * Map TTSAudioFormat to OpenAI response_format.
215
- * OpenAI TTS supports: mp3, wav, opus (ogg maps to opus).
216
- * Unsupported formats are coerced to mp3 with a warning.
215
+ *
216
+ * OpenAI's /audio/speech accepts mp3, opus, aac, flac, wav and pcm. This map
217
+ * previously stopped at mp3/wav/ogg/opus/pcm16, so `flac` — a valid
218
+ * TTSAudioFormat *and* a real OpenAI response_format — was treated as
219
+ * unsupported and silently downgraded to mp3 (#479). A caller who asked for
220
+ * lossless got lossy, and the only signal was a warn-level log.
221
+ *
222
+ * Formats OpenAI genuinely cannot produce still coerce to mp3 with a warning.
217
223
  */
218
224
  mapFormat(format) {
219
225
  const formats = {
@@ -221,13 +227,19 @@ export class OpenAITTS {
221
227
  wav: "wav",
222
228
  ogg: "opus", // OpenAI uses opus for ogg
223
229
  opus: "opus",
230
+ // #479: flac is the entry this fix added. It is a first-class OpenAI
231
+ // response_format and was the only TTSAudioFormat member missing from
232
+ // this map, so it fell through to the mp3 coercion below. The issue also
233
+ // mentions aac, but aac is not a member of TTSAudioFormat and therefore
234
+ // cannot be requested — no mapping is needed or possible for it.
235
+ flac: "flac",
224
236
  // OpenAI's "pcm" is raw 16-bit signed LE @ 24kHz (no header) — maps to
225
237
  // canonical pcm16 in TTSResult.format. See effectiveFormat() below.
226
238
  pcm16: "pcm",
227
239
  };
228
240
  const mapped = formats[format];
229
241
  if (mapped === undefined) {
230
- logger.warn(`[OpenAITTSHandler] Unsupported format "${format}" — falling back to "mp3". Supported formats: mp3, wav, ogg, opus, pcm16.`);
242
+ logger.warn(`[OpenAITTSHandler] Unsupported format "${format}" — falling back to "mp3". Supported formats: mp3, wav, ogg, opus, flac, pcm16.`);
231
243
  return "mp3";
232
244
  }
233
245
  return mapped;
@@ -259,6 +271,8 @@ export class OpenAITTS {
259
271
  return "wav";
260
272
  case "opus":
261
273
  return "opus";
274
+ case "flac":
275
+ return "flac";
262
276
  // Raw PCM (16-bit signed LE @ 24kHz, no header) — keep semantics in
263
277
  // TTSResult.format so consumers don't write raw bytes to a .wav file.
264
278
  case "pcm":
@@ -45,13 +45,32 @@ const EXTENSION_TYPE_MAP = {
45
45
  // Image
46
46
  jpg: "image",
47
47
  jpeg: "image",
48
+ jpe: "image",
49
+ jfif: "image",
48
50
  png: "image",
51
+ apng: "image",
49
52
  gif: "image",
50
53
  webp: "image",
51
54
  bmp: "image",
52
55
  tiff: "image",
53
56
  tif: "image",
54
57
  avif: "image",
58
+ // Below this line the entries exist because this map now decides whether an
59
+ // image is processed eagerly (see isEagerMultimodalFile), not merely how its
60
+ // tokens are estimated. An extension missing here classifies as `undefined`
61
+ // and takes the lazy path, which drops the pixels — so a gap is a dropped
62
+ // photo, not a slightly-off estimate. HEIC is the one that matters most:
63
+ // it is what an iPhone writes by default.
64
+ heic: "image",
65
+ heif: "image",
66
+ ico: "image",
67
+ jp2: "image",
68
+ jpx: "image",
69
+ // NB: `svg` is deliberately absent here — it is mapped to its own "svg"
70
+ // routing type further down, and listing it twice is a duplicate key whose
71
+ // second entry silently wins. `isEagerMultimodalFile` accepts both types
72
+ // instead, so SVG takes the eager path without this map having to lie about
73
+ // which processor handles it.
55
74
  // Archive
56
75
  zip: "archive",
57
76
  tar: "archive",
@@ -101,6 +120,19 @@ function inferFileTypeFromBuffer(buf) {
101
120
  if (buf.length < 4) {
102
121
  return undefined;
103
122
  }
123
+ // SVG is markup and has no magic number, so every signature check below
124
+ // misses it and a raw SVG Buffer classified as `undefined` — which meant the
125
+ // lazy path, which previews markup away. Checked first because the sniff is
126
+ // a cheap look at the head and cannot collide with a binary signature.
127
+ //
128
+ // Deliberately a substring scan over the head rather than a prolog-stripping
129
+ // regex: the obvious pattern for skipping comments and a DOCTYPE is two
130
+ // nested quantifiers, which is a ReDoS waiting to happen inside what is
131
+ // supposed to be a cheap type check. Over-matching is harmless here — the
132
+ // only consequence of a false positive is that a file is processed eagerly.
133
+ if (buf.subarray(0, 1024).toString("latin1").includes("<svg")) {
134
+ return "svg";
135
+ }
104
136
  // PNG
105
137
  if (buf[0] === 0x89 &&
106
138
  buf[1] === 0x50 &&
@@ -863,7 +895,9 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
863
895
  try {
864
896
  // ─── Lazy file registration path ──────────────────────────────
865
897
  const fileSize = fileRegistry ? getFileSize(file) : 0;
866
- if (fileRegistry && fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX) {
898
+ if (fileRegistry &&
899
+ fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX &&
900
+ !isEagerMultimodalFile(file)) {
867
901
  const registered = await tryRegisterFileReference(file, fileSize, fileRegistry, fileIdx);
868
902
  if (registered) {
869
903
  logger.info(`[NEUROLINK] File lazily registered: ${filename} (${fileSize} bytes) — deferred processing`);
@@ -2027,6 +2061,105 @@ function getFileSource(file) {
2027
2061
  }
2028
2062
  return "buffer";
2029
2063
  }
2064
+ /**
2065
+ * Whether a file must be processed eagerly rather than lazily referenced.
2066
+ *
2067
+ * The lazy path registers a file and injects a short textual *preview* in place
2068
+ * of the file itself. Whether that is an acceptable trade depends entirely on
2069
+ * whether a description of the file can answer questions about it:
2070
+ *
2071
+ * pdf lazy -> preview carries the extracted text ✔ content survives
2072
+ * image lazy -> preview carries ~98 chars of prose ✘ the pixels are gone
2073
+ *
2074
+ * An image is the case where the bytes ARE the content: no prose summary
2075
+ * substitutes for them, so the model receives a description of a file instead
2076
+ * of the file. Measured end-to-end on `release`, asking "what number is
2077
+ * written in this image?":
2078
+ *
2079
+ * tiny.png 1.6 KB -> "7391" (under TINY_MAX, eager, correct)
2080
+ * big.png 11 KB -> NOTHING_RECEIVED (lazy, image never arrived)
2081
+ * big.jpg 19 KB -> NOTHING_RECEIVED
2082
+ *
2083
+ * 10 KB is far below any real photo, so this affected essentially every image
2084
+ * attached by path — an ordinary JPEG, not just unusual formats.
2085
+ *
2086
+ * Scoped to images deliberately, and audio deserves spelling out because the
2087
+ * reason is not the one it appears to be. An audio file's message contains only
2088
+ * a metadata block — duration, codec, sample rate — on BOTH paths; no audio
2089
+ * bytes are handed to the provider either way. Moving audio to the eager path
2090
+ * would therefore change nothing about what the model receives. That audio
2091
+ * content never reaches the model at all is a separate and pre-existing gap,
2092
+ * not something this threshold decision can repair, and it is easy to mistake
2093
+ * for working code because the metadata block answers exactly the questions
2094
+ * ("how long is it?", "what sample rate?") a test is most tempted to ask.
2095
+ * Video is left alone for the opposite reason: its frames are measurably
2096
+ * present in the message and a model reads them correctly.
2097
+ *
2098
+ * Note this costs no extra memory: `tryRegisterFileReference` already calls
2099
+ * `getFileBuffer()` and reads the whole file to register it. The lazy path was
2100
+ * never lazy about reading — only about processing — so the difference here is
2101
+ * simply whether the bytes survive.
2102
+ */
2103
+ function isEagerMultimodalFile(file) {
2104
+ if (typeof file === "string") {
2105
+ return isImageLikeType(inferFileTypeFromExtension(file));
2106
+ }
2107
+ if (Buffer.isBuffer(file)) {
2108
+ return isImageLikeType(inferFileTypeFromBuffer(file));
2109
+ }
2110
+ // A `FileWithMetadata` carries two independent declarations, and either one
2111
+ // alone is enough: the shape exists for Slack/Curator-style uploads that
2112
+ // arrive as bytes plus a mimetype, so its `filename` may be extensionless or
2113
+ // simply wrong. Reading them as a `??` chain meant the first *recognised*
2114
+ // name won outright — `upload.pdf` with `mimetype: "image/png"` classified as
2115
+ // a PDF and lost its pixels down the lazy path. Any declaration of an image
2116
+ // is therefore decisive.
2117
+ const declared = [
2118
+ inferFileTypeFromExtension(file.filename),
2119
+ inferFileTypeFromMimetype(file.mimetype),
2120
+ ];
2121
+ if (declared.some(isImageLikeType)) {
2122
+ return true;
2123
+ }
2124
+ // The buffer sniff is the last resort rather than a third vote, because it is
2125
+ // a substring scan: an HTML page with an inline `<svg>` icon in its head
2126
+ // would otherwise be pulled onto the eager path and sent in full, which is
2127
+ // the opposite of what the size tiers are for. It only speaks when nothing
2128
+ // else did.
2129
+ return declared.every((type) => type === undefined)
2130
+ ? isImageLikeType(inferFileTypeFromBuffer(file.buffer))
2131
+ : false;
2132
+ }
2133
+ /**
2134
+ * Whether a routing type should have its bytes preserved rather than previewed.
2135
+ *
2136
+ * "svg" is a separate routing type rather than a sub-case of "image" (it goes
2137
+ * to the sanitizer, not to a vision encoder), but it is still an image as far
2138
+ * as this decision is concerned: its markup IS its content, and previewing it
2139
+ * away leaves the model with nothing. Accepting both keeps this correct
2140
+ * whichever of the two type vocabularies the caller's map uses.
2141
+ */
2142
+ function isImageLikeType(type) {
2143
+ return type === "image" || type === "svg";
2144
+ }
2145
+ /**
2146
+ * Infer a routing type from a caller-declared mimetype.
2147
+ *
2148
+ * Only images matter here — this exists so the eager/lazy decision can read a
2149
+ * mimetype hint — and "application/octet-stream" is deliberately ignored,
2150
+ * because it is the opaque sentinel a caller sends when it knows nothing, not
2151
+ * a claim about content.
2152
+ */
2153
+ function inferFileTypeFromMimetype(mimetype) {
2154
+ if (!mimetype) {
2155
+ return undefined;
2156
+ }
2157
+ const normalized = mimetype.split(";")[0].trim().toLowerCase();
2158
+ if (normalized === "image/svg+xml") {
2159
+ return "svg";
2160
+ }
2161
+ return normalized.startsWith("image/") ? "image" : undefined;
2162
+ }
2030
2163
  /**
2031
2164
  * Try to register a file with the FileReferenceRegistry for lazy processing.
2032
2165
  * Returns true if registration succeeded, false if it failed (caller should
@@ -30,8 +30,14 @@ export declare class OpenAITTS implements TTSHandler {
30
30
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
31
31
  /**
32
32
  * Map TTSAudioFormat to OpenAI response_format.
33
- * OpenAI TTS supports: mp3, wav, opus (ogg maps to opus).
34
- * Unsupported formats are coerced to mp3 with a warning.
33
+ *
34
+ * OpenAI's /audio/speech accepts mp3, opus, aac, flac, wav and pcm. This map
35
+ * previously stopped at mp3/wav/ogg/opus/pcm16, so `flac` — a valid
36
+ * TTSAudioFormat *and* a real OpenAI response_format — was treated as
37
+ * unsupported and silently downgraded to mp3 (#479). A caller who asked for
38
+ * lossless got lossy, and the only signal was a warn-level log.
39
+ *
40
+ * Formats OpenAI genuinely cannot produce still coerce to mp3 with a warning.
35
41
  */
36
42
  private mapFormat;
37
43
  /**
@@ -212,8 +212,14 @@ export class OpenAITTS {
212
212
  }
213
213
  /**
214
214
  * Map TTSAudioFormat to OpenAI response_format.
215
- * OpenAI TTS supports: mp3, wav, opus (ogg maps to opus).
216
- * Unsupported formats are coerced to mp3 with a warning.
215
+ *
216
+ * OpenAI's /audio/speech accepts mp3, opus, aac, flac, wav and pcm. This map
217
+ * previously stopped at mp3/wav/ogg/opus/pcm16, so `flac` — a valid
218
+ * TTSAudioFormat *and* a real OpenAI response_format — was treated as
219
+ * unsupported and silently downgraded to mp3 (#479). A caller who asked for
220
+ * lossless got lossy, and the only signal was a warn-level log.
221
+ *
222
+ * Formats OpenAI genuinely cannot produce still coerce to mp3 with a warning.
217
223
  */
218
224
  mapFormat(format) {
219
225
  const formats = {
@@ -221,13 +227,19 @@ export class OpenAITTS {
221
227
  wav: "wav",
222
228
  ogg: "opus", // OpenAI uses opus for ogg
223
229
  opus: "opus",
230
+ // #479: flac is the entry this fix added. It is a first-class OpenAI
231
+ // response_format and was the only TTSAudioFormat member missing from
232
+ // this map, so it fell through to the mp3 coercion below. The issue also
233
+ // mentions aac, but aac is not a member of TTSAudioFormat and therefore
234
+ // cannot be requested — no mapping is needed or possible for it.
235
+ flac: "flac",
224
236
  // OpenAI's "pcm" is raw 16-bit signed LE @ 24kHz (no header) — maps to
225
237
  // canonical pcm16 in TTSResult.format. See effectiveFormat() below.
226
238
  pcm16: "pcm",
227
239
  };
228
240
  const mapped = formats[format];
229
241
  if (mapped === undefined) {
230
- logger.warn(`[OpenAITTSHandler] Unsupported format "${format}" — falling back to "mp3". Supported formats: mp3, wav, ogg, opus, pcm16.`);
242
+ logger.warn(`[OpenAITTSHandler] Unsupported format "${format}" — falling back to "mp3". Supported formats: mp3, wav, ogg, opus, flac, pcm16.`);
231
243
  return "mp3";
232
244
  }
233
245
  return mapped;
@@ -259,6 +271,8 @@ export class OpenAITTS {
259
271
  return "wav";
260
272
  case "opus":
261
273
  return "opus";
274
+ case "flac":
275
+ return "flac";
262
276
  // Raw PCM (16-bit signed LE @ 24kHz, no header) — keep semantics in
263
277
  // TTSResult.format so consumers don't write raw bytes to a .wav file.
264
278
  case "pcm":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.10.10",
3
+ "version": "10.10.12",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {