@juspay/neurolink 13.8.17 → 13.8.18
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 +2 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +78 -6
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +62 -3
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +206 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/docs-site/static/search-index.json +71 -49
- package/package.json +171 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
|
|
11
11
|
import { logger } from "../../utils/logger.js";
|
|
12
12
|
import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
|
|
13
|
-
import { MAX_AUDIO_BYTES
|
|
14
|
-
import {
|
|
13
|
+
import { MAX_AUDIO_BYTES } from "../../utils/sizeGuard.js";
|
|
14
|
+
import { safeDownload } from "../../utils/safeFetch.js";
|
|
15
15
|
const DEFAULT_BASE_URL = "https://public-api.beatoven.ai";
|
|
16
16
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
17
17
|
const POLL_INTERVAL_MS = 3_000;
|
|
@@ -263,26 +263,20 @@ export class BeatovenMusic {
|
|
|
263
263
|
return (await response.json());
|
|
264
264
|
}
|
|
265
265
|
async downloadTrack(url) {
|
|
266
|
+
// `safeDownload` validates the provider-returned URL (HTTPS, public host),
|
|
267
|
+
// pins the connection to the vetted addresses, refuses redirects and
|
|
268
|
+
// bounds the body while it streams — where this was a one-off URL check
|
|
269
|
+
// followed by a plain `fetch`, which left redirects and a rebinding
|
|
270
|
+
// resolver uncovered.
|
|
266
271
|
try {
|
|
267
|
-
await
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
272
|
-
message: `Beatoven track URL rejected: ${err instanceof Error ? err.message : String(err)}`,
|
|
273
|
-
category: ErrorCategory.VALIDATION,
|
|
274
|
-
severity: ErrorSeverity.HIGH,
|
|
275
|
-
retriable: false,
|
|
276
|
-
context: { url },
|
|
272
|
+
return await safeDownload(url, {
|
|
273
|
+
maxBytes: MAX_AUDIO_BYTES,
|
|
274
|
+
label: "Beatoven track",
|
|
275
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
277
276
|
});
|
|
278
277
|
}
|
|
279
|
-
const controller = new AbortController();
|
|
280
|
-
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
281
|
-
let response;
|
|
282
|
-
try {
|
|
283
|
-
response = await fetch(url, { signal: controller.signal });
|
|
284
|
-
}
|
|
285
278
|
catch (err) {
|
|
279
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
286
280
|
if (err instanceof Error && err.name === "AbortError") {
|
|
287
281
|
throw new MusicError({
|
|
288
282
|
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
@@ -293,41 +287,36 @@ export class BeatovenMusic {
|
|
|
293
287
|
originalError: err,
|
|
294
288
|
});
|
|
295
289
|
}
|
|
290
|
+
if (/rejected|permitted|blocked range|redirect/i.test(message)) {
|
|
291
|
+
throw new MusicError({
|
|
292
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
293
|
+
message: `Beatoven track URL rejected: ${message}`,
|
|
294
|
+
category: ErrorCategory.VALIDATION,
|
|
295
|
+
severity: ErrorSeverity.HIGH,
|
|
296
|
+
retriable: false,
|
|
297
|
+
context: { url },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (/too large|size cap/i.test(message)) {
|
|
301
|
+
throw new MusicError({
|
|
302
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
303
|
+
message: `Beatoven track download exceeded size limit: ${message}`,
|
|
304
|
+
category: ErrorCategory.EXECUTION,
|
|
305
|
+
severity: ErrorSeverity.HIGH,
|
|
306
|
+
retriable: false,
|
|
307
|
+
context: { url },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
const status = /HTTP (\d{3})/.exec(message)?.[1];
|
|
296
311
|
throw new MusicError({
|
|
297
312
|
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
298
|
-
message: `Beatoven track download
|
|
313
|
+
message: `Beatoven track download failed: ${message}`,
|
|
299
314
|
category: ErrorCategory.NETWORK,
|
|
300
315
|
severity: ErrorSeverity.MEDIUM,
|
|
301
|
-
retriable:
|
|
316
|
+
retriable: status === undefined || Number(status) >= 500,
|
|
302
317
|
originalError: err instanceof Error ? err : undefined,
|
|
303
318
|
context: { url },
|
|
304
319
|
});
|
|
305
320
|
}
|
|
306
|
-
finally {
|
|
307
|
-
clearTimeout(timeoutId);
|
|
308
|
-
}
|
|
309
|
-
if (!response.ok) {
|
|
310
|
-
throw new MusicError({
|
|
311
|
-
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
312
|
-
message: `Beatoven track download failed: ${response.status}`,
|
|
313
|
-
category: ErrorCategory.NETWORK,
|
|
314
|
-
severity: ErrorSeverity.MEDIUM,
|
|
315
|
-
retriable: response.status >= 500,
|
|
316
|
-
context: { status: response.status, url },
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
return await readBoundedBuffer(response, MAX_AUDIO_BYTES, "Beatoven track");
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
throw new MusicError({
|
|
324
|
-
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
325
|
-
message: `Beatoven track download exceeded size limit: ${err instanceof Error ? err.message : String(err)}`,
|
|
326
|
-
category: ErrorCategory.EXECUTION,
|
|
327
|
-
severity: ErrorSeverity.HIGH,
|
|
328
|
-
retriable: false,
|
|
329
|
-
context: { url },
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
321
|
}
|
|
333
322
|
}
|
|
@@ -13,8 +13,8 @@ import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
|
|
|
13
13
|
import { logger } from "../../utils/logger.js";
|
|
14
14
|
import { getReplicateAuth } from "../../adapters/replicate/auth.js";
|
|
15
15
|
import { downloadPredictionOutput, predict, } from "../../adapters/replicate/predictionLifecycle.js";
|
|
16
|
-
import { MAX_AUDIO_BYTES
|
|
17
|
-
import {
|
|
16
|
+
import { MAX_AUDIO_BYTES } from "../../utils/sizeGuard.js";
|
|
17
|
+
import { safeDownload } from "../../utils/safeFetch.js";
|
|
18
18
|
const DEFAULT_MODEL = "meta/musicgen:7be0f12c54a8d033a0fbd14418c9af98962da9a86f5ff7811f9b3423a1f0b7d7";
|
|
19
19
|
export class ReplicateMusic {
|
|
20
20
|
maxDurationSeconds = 30;
|
|
@@ -154,27 +154,18 @@ export class ReplicateMusic {
|
|
|
154
154
|
retriable: false,
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
throw new MusicError({
|
|
162
|
-
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
163
|
-
message: `Unsafe URL rejected: ${err instanceof Error ? err.message : String(err)}`,
|
|
164
|
-
category: ErrorCategory.VALIDATION,
|
|
165
|
-
severity: ErrorSeverity.HIGH,
|
|
166
|
-
retriable: false,
|
|
167
|
-
context: { url: input },
|
|
168
|
-
});
|
|
169
|
-
}
|
|
157
|
+
// `safeDownload`: URL vetted (HTTPS, public host), connection pinned to
|
|
158
|
+
// the vetted addresses, redirects refused, body bounded while streaming.
|
|
170
159
|
const FETCH_TIMEOUT_MS = 60_000;
|
|
171
|
-
const controller = new AbortController();
|
|
172
|
-
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
173
|
-
let r;
|
|
174
160
|
try {
|
|
175
|
-
|
|
161
|
+
return await safeDownload(input, {
|
|
162
|
+
maxBytes: MAX_AUDIO_BYTES,
|
|
163
|
+
label: "Replicate reference audio",
|
|
164
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
165
|
+
});
|
|
176
166
|
}
|
|
177
167
|
catch (err) {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
178
169
|
if (err instanceof Error && err.name === "AbortError") {
|
|
179
170
|
throw new MusicError({
|
|
180
171
|
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
@@ -184,31 +175,33 @@ export class ReplicateMusic {
|
|
|
184
175
|
retriable: true,
|
|
185
176
|
});
|
|
186
177
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
178
|
+
if (/rejected|permitted|blocked range|redirect/i.test(message)) {
|
|
179
|
+
throw new MusicError({
|
|
180
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
181
|
+
message: `Unsafe URL rejected: ${message}`,
|
|
182
|
+
category: ErrorCategory.VALIDATION,
|
|
183
|
+
severity: ErrorSeverity.HIGH,
|
|
184
|
+
retriable: false,
|
|
185
|
+
context: { url: input },
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (/too large|size cap/i.test(message)) {
|
|
189
|
+
throw new MusicError({
|
|
190
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
191
|
+
message: `Replicate reference audio too large: ${message}`,
|
|
192
|
+
category: ErrorCategory.VALIDATION,
|
|
193
|
+
severity: ErrorSeverity.HIGH,
|
|
194
|
+
retriable: false,
|
|
195
|
+
context: { url: input },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const status = /HTTP (\d{3})/.exec(message)?.[1];
|
|
193
199
|
throw new MusicError({
|
|
194
200
|
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
195
|
-
message: `Failed to fetch reference audio: ${input}: ${
|
|
201
|
+
message: `Failed to fetch reference audio: ${input}: ${message}`,
|
|
196
202
|
category: ErrorCategory.NETWORK,
|
|
197
203
|
severity: ErrorSeverity.MEDIUM,
|
|
198
|
-
retriable:
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
return await readBoundedBuffer(r, MAX_AUDIO_BYTES, "Replicate reference audio");
|
|
203
|
-
}
|
|
204
|
-
catch (err) {
|
|
205
|
-
throw new MusicError({
|
|
206
|
-
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
207
|
-
message: `Replicate reference audio too large: ${err instanceof Error ? err.message : String(err)}`,
|
|
208
|
-
category: ErrorCategory.VALIDATION,
|
|
209
|
-
severity: ErrorSeverity.HIGH,
|
|
210
|
-
retriable: false,
|
|
211
|
-
context: { url: input },
|
|
204
|
+
retriable: status === undefined || Number(status) >= 500,
|
|
212
205
|
});
|
|
213
206
|
}
|
|
214
207
|
}
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -194,6 +194,13 @@ export declare class NeuroLink {
|
|
|
194
194
|
private hitlManager?;
|
|
195
195
|
private _sessionCostUsd;
|
|
196
196
|
private fileRegistry;
|
|
197
|
+
/**
|
|
198
|
+
* What this instance may read or fetch when a call names a file path or a
|
|
199
|
+
* URL: resolved once from `config.attachments` and the
|
|
200
|
+
* `NEUROLINK_ATTACHMENT_*` variables, stamped onto every call's options so
|
|
201
|
+
* the loaders see the same answer. See `utils/attachmentPolicy.ts`.
|
|
202
|
+
*/
|
|
203
|
+
private readonly attachmentPolicy;
|
|
197
204
|
private cachedFileTools;
|
|
198
205
|
private memoryInstance?;
|
|
199
206
|
private memorySDKConfig?;
|
|
@@ -591,7 +598,15 @@ export declare class NeuroLink {
|
|
|
591
598
|
*/
|
|
592
599
|
private registerDirectToolsServerInternal;
|
|
593
600
|
/**
|
|
594
|
-
* Load MCP configuration
|
|
601
|
+
* Load the project MCP configuration, if this instance is allowed one.
|
|
602
|
+
*
|
|
603
|
+
* The working directory is not searched on its own. A file is loaded only
|
|
604
|
+
* when `mcp.configPath` names it, `mcp.trustProjectConfig` opts into
|
|
605
|
+
* `<cwd>/.mcp-config.json`, the CLI has registered its hash-pinned trust
|
|
606
|
+
* decision, or a host set `ProviderRegistry.setOptions({ enableManualMCP:
|
|
607
|
+
* true })` after construction (the constructor resets it to `false`, the
|
|
608
|
+
* SDK default). Otherwise this is a no-op and only servers added through
|
|
609
|
+
* `addExternalMCPServer` / `addInMemoryMCPServer` exist.
|
|
595
610
|
*/
|
|
596
611
|
private loadMCPConfigurationInternal;
|
|
597
612
|
/**
|
package/dist/neurolink.js
CHANGED
|
@@ -6,12 +6,18 @@ import { beginOllamaDiagnostic, captureOllamaDiagnosticContext, completeOllamaDi
|
|
|
6
6
|
* Enhanced AI provider system with natural MCP tool access.
|
|
7
7
|
* Uses real MCP infrastructure for tool discovery and execution.
|
|
8
8
|
*/
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// The SDK no longer reads `<cwd>/.env` as a side effect of being imported:
|
|
10
|
+
// a library loaded inside an untrusted checkout would pick up that
|
|
11
|
+
// checkout's `*_BASE_URL` overrides and send the host's real keys wherever
|
|
12
|
+
// they pointed. The CLI still loads `.env` at startup (it is interactive and
|
|
13
|
+
// the directory is the user's); an SDK host that wants the same calls
|
|
14
|
+
// `loadDotenv()` from the package root, or starts the process with
|
|
15
|
+
// `NEUROLINK_DOTENV=1` in the real environment, which is honoured here.
|
|
16
|
+
import { isSdkDotenvOptIn, loadProcessDotenv, } from "./utils/dotenvBootstrap.js";
|
|
17
|
+
import { resolveProjectMcpConfigPath } from "./mcp/projectConfigTrust.js";
|
|
18
|
+
if (isSdkDotenvOptIn()) {
|
|
19
|
+
await loadProcessDotenv({ caller: "sdk" });
|
|
20
|
+
}
|
|
15
21
|
import { SpanKind, SpanStatusCode, context, trace, } from "@opentelemetry/api";
|
|
16
22
|
import { getPropagatedAttributesFromContext } from "@langfuse/core";
|
|
17
23
|
import { LangfuseOtelSpanAttributes } from "@langfuse/tracing";
|
|
@@ -43,6 +49,7 @@ import { createToolEventPayload } from "./core/toolEvents.js";
|
|
|
43
49
|
import { ProviderFactory } from "./factories/providerFactory.js";
|
|
44
50
|
import { ProviderRegistry } from "./factories/providerRegistry.js";
|
|
45
51
|
import { FileReferenceRegistry } from "./files/fileReferenceRegistry.js";
|
|
52
|
+
import { resolveAttachmentPolicy } from "./utils/attachmentPolicy.js";
|
|
46
53
|
import { createFileTools } from "./files/fileTools.js";
|
|
47
54
|
import { HITLManager, DEFAULT_ALLOW_MODIFICATION } from "./hitl/hitlManager.js";
|
|
48
55
|
import { ToolCallBatcher } from "./mcp/batching/index.js";
|
|
@@ -796,6 +803,13 @@ export class NeuroLink {
|
|
|
796
803
|
_sessionCostUsd = 0;
|
|
797
804
|
// File Reference Registry for lazy on-demand file processing
|
|
798
805
|
fileRegistry;
|
|
806
|
+
/**
|
|
807
|
+
* What this instance may read or fetch when a call names a file path or a
|
|
808
|
+
* URL: resolved once from `config.attachments` and the
|
|
809
|
+
* `NEUROLINK_ATTACHMENT_*` variables, stamped onto every call's options so
|
|
810
|
+
* the loaders see the same answer. See `utils/attachmentPolicy.ts`.
|
|
811
|
+
*/
|
|
812
|
+
attachmentPolicy;
|
|
799
813
|
// Cached file tools to avoid redundant createFileTools() calls per generate/stream
|
|
800
814
|
cachedFileTools = null;
|
|
801
815
|
// Memory instance and config
|
|
@@ -1294,6 +1308,7 @@ export class NeuroLink {
|
|
|
1294
1308
|
this.toolRegistry.setFileToolRootResolver(() => this.resolveFileToolRoots(undefined));
|
|
1295
1309
|
}
|
|
1296
1310
|
this.fileRegistry = new FileReferenceRegistry();
|
|
1311
|
+
this.attachmentPolicy = resolveAttachmentPolicy(config?.attachments);
|
|
1297
1312
|
this.observabilityConfig = config?.observability;
|
|
1298
1313
|
if (this.observabilityConfig) {
|
|
1299
1314
|
ensureTelemetry(this.observabilityConfig);
|
|
@@ -2743,12 +2758,32 @@ Current user's request: ${currentInput}`;
|
|
|
2743
2758
|
}
|
|
2744
2759
|
}
|
|
2745
2760
|
/**
|
|
2746
|
-
* Load MCP configuration
|
|
2761
|
+
* Load the project MCP configuration, if this instance is allowed one.
|
|
2762
|
+
*
|
|
2763
|
+
* The working directory is not searched on its own. A file is loaded only
|
|
2764
|
+
* when `mcp.configPath` names it, `mcp.trustProjectConfig` opts into
|
|
2765
|
+
* `<cwd>/.mcp-config.json`, the CLI has registered its hash-pinned trust
|
|
2766
|
+
* decision, or a host set `ProviderRegistry.setOptions({ enableManualMCP:
|
|
2767
|
+
* true })` after construction (the constructor resets it to `false`, the
|
|
2768
|
+
* SDK default). Otherwise this is a no-op and only servers added through
|
|
2769
|
+
* `addExternalMCPServer` / `addInMemoryMCPServer` exist.
|
|
2747
2770
|
*/
|
|
2748
2771
|
async loadMCPConfigurationInternal() {
|
|
2749
2772
|
try {
|
|
2750
|
-
const
|
|
2751
|
-
|
|
2773
|
+
const resolution = await resolveProjectMcpConfigPath({
|
|
2774
|
+
configPath: this.mcpEnhancementsConfig?.configPath,
|
|
2775
|
+
trustProjectConfig: this.mcpEnhancementsConfig?.trustProjectConfig,
|
|
2776
|
+
enableManualMCP: ProviderRegistry.getOptions().enableManualMCP,
|
|
2777
|
+
});
|
|
2778
|
+
if (!resolution) {
|
|
2779
|
+
mcpLogger.debug("[NeuroLink] No project MCP configuration to load: the working directory is not searched unless mcp.configPath, mcp.trustProjectConfig or a CLI trust decision names a file");
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
mcpLogger.debug("[NeuroLink] Loading project MCP configuration", {
|
|
2783
|
+
path: resolution.path,
|
|
2784
|
+
source: resolution.source,
|
|
2785
|
+
});
|
|
2786
|
+
const configResult = await this.externalServerManager.loadMCPConfiguration(resolution.path, { parallel: true });
|
|
2752
2787
|
mcpLogger.debug("[NeuroLink] MCP configuration loaded successfully", {
|
|
2753
2788
|
serversLoaded: configResult.serversLoaded,
|
|
2754
2789
|
errors: configResult.errors.length,
|
|
@@ -3339,6 +3374,14 @@ Current user's request: ${currentInput}`;
|
|
|
3339
3374
|
catch (error) {
|
|
3340
3375
|
logger.warn("[NeuroLink] Memory condenser shutdown failed:", error);
|
|
3341
3376
|
}
|
|
3377
|
+
// Persisted attachments live under a private tmpdir; remove them with
|
|
3378
|
+
// the instance instead of leaving uploads behind for the exit sweep.
|
|
3379
|
+
try {
|
|
3380
|
+
await this.fileRegistry.clear();
|
|
3381
|
+
}
|
|
3382
|
+
catch (error) {
|
|
3383
|
+
logger.warn("[NeuroLink] File registry cleanup failed:", error);
|
|
3384
|
+
}
|
|
3342
3385
|
try {
|
|
3343
3386
|
await this.releaseTelemetryHold();
|
|
3344
3387
|
logger.debug("[NeuroLink] OpenTelemetry shutdown completed");
|
|
@@ -4709,7 +4752,7 @@ Current user's request: ${currentInput}`;
|
|
|
4709
4752
|
if (options.rag?.files?.length) {
|
|
4710
4753
|
try {
|
|
4711
4754
|
const { prepareRAGTool } = await import("./rag/ragIntegration.js");
|
|
4712
|
-
const ragResult = await prepareRAGTool(options.rag, options.provider);
|
|
4755
|
+
const ragResult = await prepareRAGTool(options.rag, options.provider, this.attachmentPolicy);
|
|
4713
4756
|
if (!options.tools) {
|
|
4714
4757
|
options.tools = {};
|
|
4715
4758
|
}
|
|
@@ -4822,6 +4865,7 @@ Current user's request: ${currentInput}`;
|
|
|
4822
4865
|
tts: options.tts,
|
|
4823
4866
|
stt: options.stt,
|
|
4824
4867
|
fileRegistry: this.fileRegistry,
|
|
4868
|
+
attachmentPolicy: this.attachmentPolicy,
|
|
4825
4869
|
timeout: options.timeout,
|
|
4826
4870
|
turnTimeoutMs: options.turnTimeoutMs,
|
|
4827
4871
|
stallTimeoutMs: options.stallTimeoutMs,
|
|
@@ -7779,6 +7823,7 @@ Current user's request: ${currentInput}`;
|
|
|
7779
7823
|
}
|
|
7780
7824
|
const originalPrompt = options.input?.text ?? "";
|
|
7781
7825
|
options.fileRegistry = this.fileRegistry;
|
|
7826
|
+
options.attachmentPolicy = this.attachmentPolicy;
|
|
7782
7827
|
await this.validateStreamRequestOptions(options, startTime);
|
|
7783
7828
|
const workflowResult = await this.maybeHandleWorkflowStreamRequest({
|
|
7784
7829
|
options,
|
|
@@ -9053,7 +9098,7 @@ Current user's request: ${currentInput}`;
|
|
|
9053
9098
|
if (options.rag?.files?.length) {
|
|
9054
9099
|
try {
|
|
9055
9100
|
const { prepareRAGTool } = await import("./rag/ragIntegration.js");
|
|
9056
|
-
const ragResult = await prepareRAGTool(options.rag, options.provider);
|
|
9101
|
+
const ragResult = await prepareRAGTool(options.rag, options.provider, this.attachmentPolicy);
|
|
9057
9102
|
// Inject the RAG tool into the tools record
|
|
9058
9103
|
if (!options.tools) {
|
|
9059
9104
|
options.tools = {};
|
|
@@ -14661,6 +14706,7 @@ Current user's request: ${currentInput}`;
|
|
|
14661
14706
|
transcribe: (audio, provider, options) => STTProcessor.transcribe(audio, provider, options, credentials),
|
|
14662
14707
|
resolveProvider,
|
|
14663
14708
|
getHandler,
|
|
14709
|
+
attachmentPolicy: this.attachmentPolicy,
|
|
14664
14710
|
};
|
|
14665
14711
|
}
|
|
14666
14712
|
/**
|
|
@@ -15888,6 +15934,13 @@ Current user's request: ${currentInput}`;
|
|
|
15888
15934
|
catch (error) {
|
|
15889
15935
|
cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
15890
15936
|
}
|
|
15937
|
+
// 0c. Persisted attachments go with the instance (private tmpdir).
|
|
15938
|
+
try {
|
|
15939
|
+
await this.fileRegistry.clear();
|
|
15940
|
+
}
|
|
15941
|
+
catch (error) {
|
|
15942
|
+
logger.warn("[NeuroLink] File registry cleanup failed:", error);
|
|
15943
|
+
}
|
|
15891
15944
|
// 1. Flush and shutdown OpenTelemetry
|
|
15892
15945
|
try {
|
|
15893
15946
|
logger.debug("[NeuroLink] Flushing and shutting down OpenTelemetry...");
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import * as path from "path";
|
|
39
39
|
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
|
|
40
40
|
import { isDecompressionBoundExceeded, readZipEntryWithinLimit, } from "./zipEntryReader.js";
|
|
41
|
+
import { indexZipEntriesByName } from "./zipEntryIndex.js";
|
|
41
42
|
import { SIZE_LIMITS_MB } from "../config/index.js";
|
|
42
43
|
import { FileErrorCode } from "../errors/index.js";
|
|
43
44
|
// =============================================================================
|
|
@@ -641,8 +642,17 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
641
642
|
};
|
|
642
643
|
}
|
|
643
644
|
let cumulativeUncompressedSize = 0;
|
|
645
|
+
// A name that appears twice is one listing with two possible contents:
|
|
646
|
+
// the first occurrence is what every reader here opens, and the
|
|
647
|
+
// repeat is reported rather than listed as if it were a second file.
|
|
648
|
+
const seenNames = new Set();
|
|
644
649
|
for (const entry of zipEntries) {
|
|
645
650
|
const entryName = entry.entryName;
|
|
651
|
+
if (seenNames.has(entryName)) {
|
|
652
|
+
securityWarnings.push(`Duplicate entry name "${entryName}" - only the first occurrence is read; the repeat is skipped`);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
seenNames.add(entryName);
|
|
646
656
|
// Security: path traversal check
|
|
647
657
|
if (this.hasPathTraversal(entryName)) {
|
|
648
658
|
securityWarnings.push(`Path traversal detected in entry: "${entryName}" - entry skipped`);
|
|
@@ -1337,6 +1347,10 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
1337
1347
|
try {
|
|
1338
1348
|
const AdmZip = (await import("adm-zip")).default;
|
|
1339
1349
|
const zip = new AdmZip(buffer);
|
|
1350
|
+
// Read from the iterated entries, first occurrence per name — the same
|
|
1351
|
+
// entry the listing described. `zip.getEntry(name)` resolves the LAST
|
|
1352
|
+
// duplicate and would open a different member than the one listed.
|
|
1353
|
+
const { byName } = indexZipEntriesByName(zip.getEntries());
|
|
1340
1354
|
// Filter to extractable text-based entries within size limits
|
|
1341
1355
|
const candidates = entries
|
|
1342
1356
|
.filter((e) => {
|
|
@@ -1364,7 +1378,7 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
1364
1378
|
break;
|
|
1365
1379
|
}
|
|
1366
1380
|
try {
|
|
1367
|
-
const zipEntry =
|
|
1381
|
+
const zipEntry = byName.get(entry.name);
|
|
1368
1382
|
if (!zipEntry) {
|
|
1369
1383
|
continue;
|
|
1370
1384
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-wins lookup over a ZIP's central directory.
|
|
3
|
+
*
|
|
4
|
+
* A ZIP may carry two entries with the same name. adm-zip's `getEntry(name)`
|
|
5
|
+
* resolves the LAST one, while `getEntries()` lists them in directory order,
|
|
6
|
+
* so a processor that lists with one call and reads with the other can show
|
|
7
|
+
* the user a listing of one file and the contents of another. Every reader
|
|
8
|
+
* here indexes the iterated entries once and reads from that index, so the
|
|
9
|
+
* entry it describes is the entry it opens; a repeated name is reported to
|
|
10
|
+
* the caller rather than silently resolved either way.
|
|
11
|
+
*
|
|
12
|
+
* @module processors/archive/zipEntryIndex
|
|
13
|
+
*/
|
|
14
|
+
import type { NamedZipEntry } from "../../types/index.js";
|
|
15
|
+
/**
|
|
16
|
+
* Index `entries` by name, keeping the FIRST occurrence of each. Returns the
|
|
17
|
+
* index and the names that occurred more than once, in first-seen order.
|
|
18
|
+
*/
|
|
19
|
+
export declare function indexZipEntriesByName<T extends NamedZipEntry>(entries: readonly T[]): {
|
|
20
|
+
byName: Map<string, T>;
|
|
21
|
+
duplicates: string[];
|
|
22
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-wins lookup over a ZIP's central directory.
|
|
3
|
+
*
|
|
4
|
+
* A ZIP may carry two entries with the same name. adm-zip's `getEntry(name)`
|
|
5
|
+
* resolves the LAST one, while `getEntries()` lists them in directory order,
|
|
6
|
+
* so a processor that lists with one call and reads with the other can show
|
|
7
|
+
* the user a listing of one file and the contents of another. Every reader
|
|
8
|
+
* here indexes the iterated entries once and reads from that index, so the
|
|
9
|
+
* entry it describes is the entry it opens; a repeated name is reported to
|
|
10
|
+
* the caller rather than silently resolved either way.
|
|
11
|
+
*
|
|
12
|
+
* @module processors/archive/zipEntryIndex
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Index `entries` by name, keeping the FIRST occurrence of each. Returns the
|
|
16
|
+
* index and the names that occurred more than once, in first-seen order.
|
|
17
|
+
*/
|
|
18
|
+
export function indexZipEntriesByName(entries) {
|
|
19
|
+
const byName = new Map();
|
|
20
|
+
const duplicates = [];
|
|
21
|
+
const flagged = new Set();
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (byName.has(entry.entryName)) {
|
|
24
|
+
if (!flagged.has(entry.entryName)) {
|
|
25
|
+
flagged.add(entry.entryName);
|
|
26
|
+
duplicates.push(entry.entryName);
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
byName.set(entry.entryName, entry);
|
|
31
|
+
}
|
|
32
|
+
return { byName, duplicates };
|
|
33
|
+
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import "../../services/server/ai/observability/instrumentation.js";
|
|
47
47
|
import { FileErrorCode } from "../errors/index.js";
|
|
48
|
-
import type { BatchProcessingSummary, FileInfo, FileProcessingError, ProcessorFileProcessingResult, FileProcessorConfig, ProcessorOperationResult, ProcessedFileBase, ProcessOptions } from "../../types/index.js";
|
|
48
|
+
import type { AttachmentPolicy, BatchProcessingSummary, FileInfo, FileProcessingError, ProcessorFileProcessingResult, FileProcessorConfig, ProcessorOperationResult, ProcessedFileBase, ProcessOptions } from "../../types/index.js";
|
|
49
49
|
/**
|
|
50
50
|
* Marker on the error raised when a processor refuses content for exceeding
|
|
51
51
|
* the size limit *after* the download — a ZIP entry that expands past the
|
|
@@ -192,32 +192,20 @@ export declare abstract class BaseFileProcessor<T extends ProcessedFileBase> {
|
|
|
192
192
|
/**
|
|
193
193
|
* Download file from URL with authentication.
|
|
194
194
|
*
|
|
195
|
+
* Goes through `downloadAttachment`, which applies the attachment policy
|
|
196
|
+
* (HTTPS and public hosts unless the instance says otherwise), pins the
|
|
197
|
+
* connection to the vetted addresses, re-validates every redirect hop and
|
|
198
|
+
* bounds the body while it streams. `authHeaders` ride on the first request
|
|
199
|
+
* only — they are never replayed to a redirect target.
|
|
200
|
+
*
|
|
195
201
|
* @param url - URL to download from
|
|
196
202
|
* @param authHeaders - Optional authentication headers
|
|
197
203
|
* @param timeout - Optional timeout override
|
|
204
|
+
* @param attachmentPolicy - Policy for the fetch; the environment default when omitted
|
|
198
205
|
* @returns Downloaded file content as Buffer
|
|
199
206
|
* @throws Error if download fails
|
|
200
207
|
*/
|
|
201
|
-
protected downloadFile(url: string, authHeaders?: Record<string, string>, timeout?: number): Promise<Buffer>;
|
|
202
|
-
/**
|
|
203
|
-
* Read a response body, refusing it the moment it passes `maxBytes`.
|
|
204
|
-
*
|
|
205
|
-
* `response.arrayBuffer()` buffers whatever the server sends before anyone
|
|
206
|
-
* can object, so the size check further up only ever ran against a body that
|
|
207
|
-
* had already been paid for in full. Metering the stream makes the ceiling
|
|
208
|
-
* the configured limit instead of the server's choice, and cancelling the
|
|
209
|
-
* reader stops the transfer rather than merely stopping our interest in it.
|
|
210
|
-
*
|
|
211
|
-
* Content-Length is deliberately not trusted as the bound — it is a hint that
|
|
212
|
-
* can be absent or a lie — but it is worth honouring when present, because it
|
|
213
|
-
* refuses the honest oversized case without reading a byte.
|
|
214
|
-
*
|
|
215
|
-
* @param response - Fetch response whose body should be read
|
|
216
|
-
* @param maxBytes - Ceiling for the body, in bytes
|
|
217
|
-
* @returns The body as a Buffer
|
|
218
|
-
* @throws When the body exceeds `maxBytes`
|
|
219
|
-
*/
|
|
220
|
-
private readBodyWithinLimit;
|
|
208
|
+
protected downloadFile(url: string, authHeaders?: Record<string, string>, timeout?: number, attachmentPolicy?: AttachmentPolicy): Promise<Buffer>;
|
|
221
209
|
/**
|
|
222
210
|
* Download file with retry logic for transient failures.
|
|
223
211
|
*
|