@juspay/neurolink 10.11.2 → 10.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/audioFormatSupport.d.ts +53 -0
  3. package/dist/adapters/audioFormatSupport.js +200 -0
  4. package/dist/browser/neurolink.min.js +399 -398
  5. package/dist/cli/commands/auth.d.ts +8 -1
  6. package/dist/cli/commands/auth.js +185 -6
  7. package/dist/cli/factories/authCommandFactory.js +7 -1
  8. package/dist/lib/adapters/audioFormatSupport.d.ts +53 -0
  9. package/dist/lib/adapters/audioFormatSupport.js +201 -0
  10. package/dist/lib/processors/archive/ArchiveProcessor.d.ts +37 -0
  11. package/dist/lib/processors/archive/ArchiveProcessor.js +347 -32
  12. package/dist/lib/providers/googleAiStudio/client.d.ts +17 -0
  13. package/dist/lib/providers/googleAiStudio/client.js +45 -19
  14. package/dist/lib/providers/googleNativeGemini3/utils.d.ts +22 -1
  15. package/dist/lib/providers/googleNativeGemini3/utils.js +54 -0
  16. package/dist/lib/providers/googleVertex/client.js +3 -0
  17. package/dist/lib/proxy/accountQuota.d.ts +6 -0
  18. package/dist/lib/proxy/accountQuota.js +19 -2
  19. package/dist/lib/proxy/accountUsage.d.ts +45 -0
  20. package/dist/lib/proxy/accountUsage.js +289 -0
  21. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +15 -1
  22. package/dist/lib/server/routes/claudeProxyRoutes.js +166 -0
  23. package/dist/lib/types/cli.d.ts +12 -0
  24. package/dist/lib/types/file.d.ts +41 -0
  25. package/dist/lib/types/generate.d.ts +12 -1
  26. package/dist/lib/types/processor.d.ts +20 -1
  27. package/dist/lib/types/providers.d.ts +7 -0
  28. package/dist/lib/types/proxy.d.ts +101 -0
  29. package/dist/lib/utils/fileDetector.d.ts +27 -0
  30. package/dist/lib/utils/fileDetector.js +130 -7
  31. package/dist/lib/utils/imageProcessor.js +31 -0
  32. package/dist/lib/utils/messageBuilder.d.ts +0 -9
  33. package/dist/lib/utils/messageBuilder.js +380 -56
  34. package/dist/processors/archive/ArchiveProcessor.d.ts +37 -0
  35. package/dist/processors/archive/ArchiveProcessor.js +347 -32
  36. package/dist/providers/googleAiStudio/client.d.ts +17 -0
  37. package/dist/providers/googleAiStudio/client.js +45 -19
  38. package/dist/providers/googleNativeGemini3/utils.d.ts +22 -1
  39. package/dist/providers/googleNativeGemini3/utils.js +54 -0
  40. package/dist/providers/googleVertex/client.js +3 -0
  41. package/dist/proxy/accountQuota.d.ts +6 -0
  42. package/dist/proxy/accountQuota.js +19 -2
  43. package/dist/proxy/accountUsage.d.ts +45 -0
  44. package/dist/proxy/accountUsage.js +288 -0
  45. package/dist/server/routes/claudeProxyRoutes.d.ts +15 -1
  46. package/dist/server/routes/claudeProxyRoutes.js +166 -0
  47. package/dist/types/cli.d.ts +12 -0
  48. package/dist/types/file.d.ts +41 -0
  49. package/dist/types/generate.d.ts +12 -1
  50. package/dist/types/processor.d.ts +20 -1
  51. package/dist/types/providers.d.ts +7 -0
  52. package/dist/types/proxy.d.ts +101 -0
  53. package/dist/utils/fileDetector.d.ts +27 -0
  54. package/dist/utils/fileDetector.js +130 -7
  55. package/dist/utils/imageProcessor.js +31 -0
  56. package/dist/utils/messageBuilder.d.ts +0 -9
  57. package/dist/utils/messageBuilder.js +380 -56
  58. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [10.12.0](https://github.com/juspay/neurolink/compare/v10.11.3...v10.12.0) (2026-08-13)
2
+
3
+ ### Features
4
+
5
+ - **(proxy):** manual limits refresh with dynamic quota windows ([e5c9fed](https://github.com/juspay/neurolink/commit/e5c9fed0c570a4583d8e83e04d09785e3e126d56))
6
+
7
+ ## [10.11.3](https://github.com/juspay/neurolink/compare/v10.11.2...v10.11.3) (2026-08-12)
8
+
9
+ ### Bug Fixes
10
+
11
+ - **(multimodal):** deliver file content to the model instead of describing it ([6c2b2e6](https://github.com/juspay/neurolink/commit/6c2b2e6f96fcb0ab251256d905560e2761a7c8c0))
12
+
1
13
  ## [10.11.2](https://github.com/juspay/neurolink/compare/v10.11.1...v10.11.2) (2026-08-12)
2
14
 
3
15
  ### Bug Fixes
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Native audio delivery to providers that can listen.
3
+ *
4
+ * ## The gap this closes
5
+ *
6
+ * Until this module existed, attaching an audio file produced a message
7
+ * containing only a metadata block:
8
+ *
9
+ * ## Audio File: "recording.mp3"
10
+ * Duration: 19s | Codec: MPEG 2 Layer 3 | Bitrate: 32 kbps |
11
+ * Sample Rate: 22050 Hz | Channels: 1 (Mono)
12
+ *
13
+ * No audio bytes were ever handed to the provider. Every question about what
14
+ * the recording *says* — transcribe this, who is speaking, what was agreed —
15
+ * was answered from a description of the file, and Gemini has accepted inline
16
+ * audio the whole time.
17
+ *
18
+ * The failure was invisible for an instructive reason: that metadata block
19
+ * answers precisely the questions a test is most tempted to ask. "How long is
20
+ * this audio?" and "what sample rate is it?" both succeed with no audio
21
+ * attached, so a suite built on them reports working audio support. It took an
22
+ * end-to-end test asking for a spoken word to expose it.
23
+ *
24
+ * ## Provider scope
25
+ *
26
+ * Deliberately a capability map rather than "send audio to everyone". A
27
+ * provider that cannot accept an audio part responds with an opaque HTTP 400,
28
+ * which is worse than the metadata summary it would otherwise have received —
29
+ * so an unlisted provider keeps the existing text-only behaviour and loses
30
+ * nothing.
31
+ *
32
+ * @module adapters/audioFormatSupport
33
+ */
34
+ import type { AudioConversionResult } from "../types/index.js";
35
+ /** Whether `provider` can be handed raw audio bytes. */
36
+ export declare function supportsNativeAudio(provider: string): boolean;
37
+ /** Whether `mimeType` must be re-encoded before a native provider will read it. */
38
+ export declare function needsAudioTranscode(mimeType: string): boolean;
39
+ /**
40
+ * Return audio bytes a native provider can read, transcoding when the source
41
+ * container is one it does not accept.
42
+ *
43
+ * Never throws for audio reasons. When conversion is impossible — no ffmpeg, an
44
+ * unreadable stream — the original bytes and MIME type come back with
45
+ * `converted: false`, and the caller falls back to the metadata summary. That
46
+ * keeps this from turning a previously-working (if limited) request into a
47
+ * failure.
48
+ *
49
+ * @param buffer - Raw audio bytes.
50
+ * @param mimeType - Detected MIME type of `buffer`.
51
+ * @param extension - Source extension, used so ffmpeg picks the right demuxer.
52
+ */
53
+ export declare function toProviderCompatibleAudio(buffer: Buffer, mimeType: string, extension: string): Promise<AudioConversionResult>;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Native audio delivery to providers that can listen.
3
+ *
4
+ * ## The gap this closes
5
+ *
6
+ * Until this module existed, attaching an audio file produced a message
7
+ * containing only a metadata block:
8
+ *
9
+ * ## Audio File: "recording.mp3"
10
+ * Duration: 19s | Codec: MPEG 2 Layer 3 | Bitrate: 32 kbps |
11
+ * Sample Rate: 22050 Hz | Channels: 1 (Mono)
12
+ *
13
+ * No audio bytes were ever handed to the provider. Every question about what
14
+ * the recording *says* — transcribe this, who is speaking, what was agreed —
15
+ * was answered from a description of the file, and Gemini has accepted inline
16
+ * audio the whole time.
17
+ *
18
+ * The failure was invisible for an instructive reason: that metadata block
19
+ * answers precisely the questions a test is most tempted to ask. "How long is
20
+ * this audio?" and "what sample rate is it?" both succeed with no audio
21
+ * attached, so a suite built on them reports working audio support. It took an
22
+ * end-to-end test asking for a spoken word to expose it.
23
+ *
24
+ * ## Provider scope
25
+ *
26
+ * Deliberately a capability map rather than "send audio to everyone". A
27
+ * provider that cannot accept an audio part responds with an opaque HTTP 400,
28
+ * which is worse than the metadata summary it would otherwise have received —
29
+ * so an unlisted provider keeps the existing text-only behaviour and loses
30
+ * nothing.
31
+ *
32
+ * @module adapters/audioFormatSupport
33
+ */
34
+ import { withTimeout } from "../utils/errorHandling.js";
35
+ import { logger } from "../utils/logger.js";
36
+ import { getFfmpegPath, runFfmpeg } from "./video/ffmpegAdapter.js";
37
+ /**
38
+ * Ceiling for one audio conversion.
39
+ *
40
+ * Longer than the image equivalent because a lossless hour-long WAV is a
41
+ * legitimate input and re-encoding it is not instant, but still bounded so a
42
+ * wedged decoder cannot hold a generation request open indefinitely.
43
+ */
44
+ const AUDIO_TRANSCODE_TIMEOUT_MS = 120_000;
45
+ /**
46
+ * Providers that accept inline audio parts.
47
+ *
48
+ * Google's Gemini models (both Vertex and AI Studio) take audio as `inlineData`
49
+ * alongside text. Other providers are omitted rather than assumed: OpenAI's
50
+ * audio models use a different request shape than the chat-completions path
51
+ * NeuroLink builds here, and sending an audio part to a provider that does not
52
+ * expect one converts a working (if limited) response into a hard failure.
53
+ */
54
+ const NATIVE_AUDIO_PROVIDERS = new Set([
55
+ "vertex",
56
+ "google-vertex",
57
+ "googlevertex",
58
+ "google-ai-studio",
59
+ "googleaistudio",
60
+ "google-ai",
61
+ "googleai",
62
+ "gemini",
63
+ ]);
64
+ /**
65
+ * Audio MIME types the native providers accept as-is.
66
+ *
67
+ * Gemini's documented set. Anything outside it is transcoded rather than
68
+ * rejected, because the container a user happens to have — a voice memo in
69
+ * CAF, a Windows recording in WMA — says nothing about whether the audio
70
+ * inside is useful.
71
+ */
72
+ const NATIVE_AUDIO_MIME_TYPES = new Set([
73
+ "audio/wav",
74
+ "audio/x-wav",
75
+ "audio/mpeg",
76
+ "audio/mp3",
77
+ "audio/aiff",
78
+ "audio/x-aiff",
79
+ "audio/aac",
80
+ "audio/ogg",
81
+ "audio/flac",
82
+ "audio/x-flac",
83
+ ]);
84
+ /** MIME type every transcode targets. Universally accepted and compact. */
85
+ const TRANSCODE_TARGET_MIME = "audio/mpeg";
86
+ /** Whether `provider` can be handed raw audio bytes. */
87
+ export function supportsNativeAudio(provider) {
88
+ return NATIVE_AUDIO_PROVIDERS.has(provider.toLowerCase().trim());
89
+ }
90
+ /** Whether `mimeType` must be re-encoded before a native provider will read it. */
91
+ export function needsAudioTranscode(mimeType) {
92
+ return !NATIVE_AUDIO_MIME_TYPES.has(normalizeAudioMime(mimeType));
93
+ }
94
+ function normalizeAudioMime(mimeType) {
95
+ return mimeType.split(";")[0].trim().toLowerCase();
96
+ }
97
+ /**
98
+ * Re-encode audio to MP3 with ffmpeg.
99
+ *
100
+ * Temp files rather than stdin: several of the containers that need converting
101
+ * (CAF, WavPack, AU) carry their metadata in a trailer or require seeking, and
102
+ * a piped stream leaves ffmpeg unable to find it. The directory is removed in
103
+ * `finally` whether or not the conversion succeeded.
104
+ *
105
+ * Node builtins are imported dynamically because the browser bundle stubs
106
+ * `node:fs/promises` without `mkdtemp`; nothing in a browser spawns ffmpeg, so
107
+ * the import belongs at the point of use.
108
+ */
109
+ async function transcodeToMp3(buffer, extension) {
110
+ const [{ randomUUID }, { mkdtemp, readFile, rm, writeFile }, { tmpdir }, { join },] = await Promise.all([
111
+ import("node:crypto"),
112
+ import("node:fs/promises"),
113
+ import("node:os"),
114
+ import("node:path"),
115
+ ]);
116
+ const workDir = await mkdtemp(join(tmpdir(), "neurolink-audio-"));
117
+ const inputPath = join(workDir, `${randomUUID()}${extension}`);
118
+ const outputPath = join(workDir, `${randomUUID()}.mp3`);
119
+ try {
120
+ await writeFile(inputPath, buffer);
121
+ await runFfmpeg([
122
+ "-y",
123
+ "-v",
124
+ "error",
125
+ "-i",
126
+ inputPath,
127
+ // Downmix and cap the rate: speech is the point, and a 48 kHz stereo
128
+ // re-encode of a mono voice memo triples the payload for nothing.
129
+ "-ac",
130
+ "1",
131
+ "-ar",
132
+ "16000",
133
+ "-c:a",
134
+ "libmp3lame",
135
+ "-q:a",
136
+ "4",
137
+ outputPath,
138
+ ],
139
+ // Without this the call inherits runFfmpeg's frame-extraction default of
140
+ // 30s, which is sized for pulling a single video frame — so the 120s
141
+ // ceiling above, chosen precisely because re-encoding a lossless
142
+ // hour-long WAV is not instant, could never be reached. ffmpeg killed the
143
+ // transcode at 30s and the outer race never got to run.
144
+ { timeoutMs: AUDIO_TRANSCODE_TIMEOUT_MS });
145
+ return await readFile(outputPath);
146
+ }
147
+ finally {
148
+ await rm(workDir, { recursive: true, force: true }).catch(() => undefined);
149
+ }
150
+ }
151
+ /**
152
+ * Return audio bytes a native provider can read, transcoding when the source
153
+ * container is one it does not accept.
154
+ *
155
+ * Never throws for audio reasons. When conversion is impossible — no ffmpeg, an
156
+ * unreadable stream — the original bytes and MIME type come back with
157
+ * `converted: false`, and the caller falls back to the metadata summary. That
158
+ * keeps this from turning a previously-working (if limited) request into a
159
+ * failure.
160
+ *
161
+ * @param buffer - Raw audio bytes.
162
+ * @param mimeType - Detected MIME type of `buffer`.
163
+ * @param extension - Source extension, used so ffmpeg picks the right demuxer.
164
+ */
165
+ export async function toProviderCompatibleAudio(buffer, mimeType, extension) {
166
+ const normalized = normalizeAudioMime(mimeType);
167
+ if (!needsAudioTranscode(normalized)) {
168
+ return { buffer, mimeType: normalized, converted: false };
169
+ }
170
+ // Resolving the binary first turns "ffmpeg is not installed" into one clear
171
+ // warning rather than a spawn error surfacing from inside the conversion.
172
+ const ffmpegAvailable = await getFfmpegPath()
173
+ .then(() => true)
174
+ .catch(() => false);
175
+ if (!ffmpegAvailable) {
176
+ logger.warn(`[audioFormatSupport] ${normalized} needs conversion before a provider can ` +
177
+ `read it, but ffmpeg is unavailable — falling back to a metadata-only ` +
178
+ `summary. Install ffmpeg (or set FFMPEG_PATH) to enable this format.`);
179
+ return { buffer, mimeType: normalized, converted: false };
180
+ }
181
+ try {
182
+ const converted = await withTimeout(transcodeToMp3(buffer, extension), AUDIO_TRANSCODE_TIMEOUT_MS, new Error(`audio transcode exceeded ${AUDIO_TRANSCODE_TIMEOUT_MS}ms`));
183
+ if (converted.length === 0) {
184
+ throw new Error("produced an empty audio stream");
185
+ }
186
+ logger.debug(`[audioFormatSupport] Transcoded ${normalized} → ${TRANSCODE_TARGET_MIME} ` +
187
+ `(${buffer.length} → ${converted.length} bytes) for native delivery`);
188
+ return {
189
+ buffer: converted,
190
+ mimeType: TRANSCODE_TARGET_MIME,
191
+ converted: true,
192
+ };
193
+ }
194
+ catch (error) {
195
+ logger.warn(`[audioFormatSupport] Could not convert ${normalized} for native delivery ` +
196
+ `— falling back to a metadata-only summary: ` +
197
+ `${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
198
+ return { buffer, mimeType: normalized, converted: false };
199
+ }
200
+ }