@juspay/neurolink 13.8.17 → 13.8.19
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 +5 -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/providers/openaiChatCompletionsClient.js +12 -3
- package/dist/proxy/accountLedger.js +6 -8
- package/dist/proxy/geminiFormat.d.ts +3 -11
- package/dist/proxy/geminiFormat.js +11 -2
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/openaiFormat.d.ts +3 -9
- package/dist/proxy/openaiFormat.js +21 -15
- package/dist/proxy/proxyTranslationEngine.d.ts +2 -6
- package/dist/proxy/proxyTranslationEngine.js +25 -14
- package/dist/proxy/routingPolicy.d.ts +3 -1
- package/dist/proxy/routingPolicy.js +7 -8
- 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/openaiCompatible.d.ts +1 -1
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +71 -16
- package/dist/types/proxyClient.d.ts +2 -0
- 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/retryAfter.d.ts +32 -0
- package/dist/utils/retryAfter.js +135 -11
- 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/dist/workflow/workflows/adaptiveWorkflow.d.ts +2 -2
- package/dist/workflow/workflows/adaptiveWorkflow.js +10 -10
- package/dist/workflow/workflows/consensusWorkflow.d.ts +4 -4
- package/dist/workflow/workflows/consensusWorkflow.js +12 -12
- package/dist/workflow/workflows/fallbackWorkflow.d.ts +3 -3
- package/dist/workflow/workflows/fallbackWorkflow.js +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.d.ts +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.js +31 -31
- package/docs-site/static/search-index.json +74 -52
- package/package.json +173 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Uses multi-strategy approach for reliable type identification
|
|
5
5
|
*/
|
|
6
6
|
import { open, readFile, realpath } from "fs/promises";
|
|
7
|
-
import { STATUS_CODES } from "node:http";
|
|
8
7
|
import { basename, isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
9
|
-
import {
|
|
8
|
+
import { AttachmentRefusedError, downloadAttachment, getAttachmentPolicy, readAttachmentFile, } from "./attachmentPolicy.js";
|
|
10
9
|
// Lazy-loaded processor singletons — avoids loading heavy media deps
|
|
11
10
|
// (mediabunny, fluent-ffmpeg, music-metadata, adm-zip) on every generate() call.
|
|
12
11
|
async function getVideoProcessor() {
|
|
@@ -33,7 +32,7 @@ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
|
|
|
33
32
|
import { logger } from "./logger.js";
|
|
34
33
|
import { looksLikeSvgMarkup } from "./markupSniff.js";
|
|
35
34
|
import { withTimeout } from "./errorHandling.js";
|
|
36
|
-
import { normalizeUrlForCache,
|
|
35
|
+
import { normalizeUrlForCache, sanitizeErrorCause } from "./logSanitize.js";
|
|
37
36
|
import { mimeHintToExtension, mimeHintToFileType, normalizeMimeHint, } from "./mimeTypeHints.js";
|
|
38
37
|
import { PDFProcessor } from "./pdfProcessor.js";
|
|
39
38
|
import { formatKeyframeTimestamp } from "./mediaDuration.js";
|
|
@@ -161,14 +160,58 @@ const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
|
|
|
161
160
|
* @param error - Error to check
|
|
162
161
|
* @returns True if error is retryable (transient network issue)
|
|
163
162
|
*/
|
|
163
|
+
/**
|
|
164
|
+
* The innermost `code` on an error's `cause` chain (bounded walk). Wrappers such
|
|
165
|
+
* as undici's "fetch failed" carry the Node transport code one level down.
|
|
166
|
+
*/
|
|
167
|
+
/** The deepest `Error` on a `cause` chain (bounded walk); the input when it has none. */
|
|
168
|
+
function innermostError(error) {
|
|
169
|
+
let cursor = error;
|
|
170
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
171
|
+
const next = cursor instanceof Error ? cursor.cause : undefined;
|
|
172
|
+
if (!(next instanceof Error)) {
|
|
173
|
+
return cursor;
|
|
174
|
+
}
|
|
175
|
+
cursor = next;
|
|
176
|
+
}
|
|
177
|
+
return cursor;
|
|
178
|
+
}
|
|
179
|
+
function innermostErrorCode(error) {
|
|
180
|
+
let found;
|
|
181
|
+
let cursor = error;
|
|
182
|
+
for (let depth = 0; depth < 6 && cursor instanceof Error; depth++) {
|
|
183
|
+
const code = cursor.code;
|
|
184
|
+
if (typeof code === "string") {
|
|
185
|
+
found = code;
|
|
186
|
+
}
|
|
187
|
+
cursor = cursor.cause;
|
|
188
|
+
}
|
|
189
|
+
return found;
|
|
190
|
+
}
|
|
164
191
|
function isRetryableNetworkError(error) {
|
|
165
192
|
if (!(error instanceof Error)) {
|
|
166
193
|
return false;
|
|
167
194
|
}
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
195
|
+
// `fetch()` (undici) reports a transport failure as a bare "fetch failed"
|
|
196
|
+
// TypeError and keeps the real reason — ECONNRESET, ETIMEDOUT, ... — on
|
|
197
|
+
// `cause`. The safe downloader wraps that again. Walk the chain (bounded) so
|
|
198
|
+
// the classification sees the innermost code and message, not the wrapper.
|
|
199
|
+
const chain = [];
|
|
200
|
+
let cursor = error;
|
|
201
|
+
while (cursor instanceof Error && chain.length < 6) {
|
|
202
|
+
chain.push(cursor);
|
|
203
|
+
cursor = cursor.cause;
|
|
204
|
+
}
|
|
205
|
+
const errorMessage = chain
|
|
206
|
+
.map((e) => e.message)
|
|
207
|
+
.join(" | ")
|
|
208
|
+
.toLowerCase();
|
|
209
|
+
// Extract error code from various error shapes, innermost first
|
|
210
|
+
const errorCode = chain
|
|
211
|
+
.map((e) => e.code)
|
|
212
|
+
.reverse()
|
|
213
|
+
.find((c) => typeof c === "string")
|
|
214
|
+
?.toUpperCase();
|
|
172
215
|
// Check for retryable network error codes
|
|
173
216
|
if (errorCode && RETRYABLE_ERROR_CODES.includes(errorCode)) {
|
|
174
217
|
return true;
|
|
@@ -669,7 +712,7 @@ export class FileDetector {
|
|
|
669
712
|
const confidenceThreshold = options?.confidenceThreshold ?? 80;
|
|
670
713
|
const strategies = [
|
|
671
714
|
new MagicBytesStrategy(),
|
|
672
|
-
new MimeTypeStrategy(responseMimeType),
|
|
715
|
+
new MimeTypeStrategy(responseMimeType, getAttachmentPolicy(options)),
|
|
673
716
|
new ExtensionStrategy(),
|
|
674
717
|
new ContentHeuristicStrategy(),
|
|
675
718
|
];
|
|
@@ -1606,55 +1649,27 @@ export class FileDetector {
|
|
|
1606
1649
|
static async loadUrl(url, options) {
|
|
1607
1650
|
const maxSize = options?.maxSize || 200 * 1024 * 1024; // 200MB default (matches Curator memory-safety cap)
|
|
1608
1651
|
const timeout = options?.timeout || FileDetector.DEFAULT_NETWORK_TIMEOUT;
|
|
1652
|
+
const policy = getAttachmentPolicy(options);
|
|
1609
1653
|
return withRetry(async () => {
|
|
1610
1654
|
try {
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
//
|
|
1614
|
-
//
|
|
1615
|
-
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1655
|
+
// `downloadAttachment` applies the instance's URL policy (HTTPS and
|
|
1656
|
+
// public hosts unless opened up), pins the connection to the vetted
|
|
1657
|
+
// addresses, re-validates every redirect hop and bounds the body
|
|
1658
|
+
// while it streams — a declared oversize response is refused at
|
|
1659
|
+
// the headers, an undeclared one as soon as it passes the cap.
|
|
1660
|
+
const downloaded = await downloadAttachment(url, {
|
|
1661
|
+
policy,
|
|
1662
|
+
maxBytes: maxSize,
|
|
1663
|
+
label: "attached URL",
|
|
1664
|
+
// One budget for headers and body, where the two undici timeouts
|
|
1665
|
+
// used to apply separately.
|
|
1666
|
+
timeoutMs: timeout * 2,
|
|
1621
1667
|
});
|
|
1622
|
-
|
|
1623
|
-
// #360: pair the code with its standard reason phrase (e.g. 404 →
|
|
1624
|
-
// "Not Found") so the message is readable rather than a bare
|
|
1625
|
-
// number. Sourced from Node's own `http.STATUS_CODES` table
|
|
1626
|
-
// rather than a hand-written map that could drift from it, and
|
|
1627
|
-
// falls back gracefully for a non-standard code that table
|
|
1628
|
-
// doesn't recognize. Query string / fragment stays stripped — a
|
|
1629
|
-
// presigned URL's token must not be echoed into a thrown error.
|
|
1630
|
-
const statusText = STATUS_CODES[response.statusCode];
|
|
1631
|
-
throw new Error(`HTTP ${response.statusCode}${statusText ? ` ${statusText}` : ""} fetching ${redactUrlForError(url)}`);
|
|
1632
|
-
}
|
|
1633
|
-
const contentType = response.headers["content-type"] || "";
|
|
1634
|
-
const declaredLength = Number(response.headers["content-length"]);
|
|
1635
|
-
if (Number.isFinite(declaredLength) && declaredLength > maxSize) {
|
|
1636
|
-
// `request()` resolves after response headers arrive. Close the
|
|
1637
|
-
// unread stream before throwing so a declared oversize response
|
|
1638
|
-
// never enters the body-reading loop or holds its connection.
|
|
1639
|
-
// Undici emits an abort error asynchronously when the stream is
|
|
1640
|
-
// destroyed, so consume that expected event before closing it.
|
|
1641
|
-
response.body.once("error", () => { });
|
|
1642
|
-
response.body.destroy();
|
|
1643
|
-
throw new Error(`File too large: ${formatFileSize(declaredLength)} (max: ${formatFileSize(maxSize)})`);
|
|
1644
|
-
}
|
|
1668
|
+
const contentType = downloaded.contentType;
|
|
1645
1669
|
// #323: cache the Content-Type from this GET so a subsequent detection
|
|
1646
1670
|
// of the same URL needs no HEAD.
|
|
1647
1671
|
setCachedUrlContentType(url, contentType, Date.now());
|
|
1648
|
-
|
|
1649
|
-
let totalSize = 0;
|
|
1650
|
-
for await (const chunk of response.body) {
|
|
1651
|
-
totalSize += chunk.length;
|
|
1652
|
-
if (totalSize > maxSize) {
|
|
1653
|
-
throw new Error(`File too large: ${formatFileSize(totalSize)} (max: ${formatFileSize(maxSize)})`);
|
|
1654
|
-
}
|
|
1655
|
-
chunks.push(chunk);
|
|
1656
|
-
}
|
|
1657
|
-
return { content: Buffer.concat(chunks), contentType };
|
|
1672
|
+
return { content: downloaded.buffer, contentType };
|
|
1658
1673
|
}
|
|
1659
1674
|
catch (error) {
|
|
1660
1675
|
// Node/undici DNS, TLS, and connect-timeout errors embed the full
|
|
@@ -1673,10 +1688,13 @@ export class FileDetector {
|
|
|
1673
1688
|
// string/object can just as easily carry the full URL), so there is
|
|
1674
1689
|
// no unconditional `throw error` fallback that would bypass
|
|
1675
1690
|
// redaction for that case.
|
|
1676
|
-
|
|
1691
|
+
// Read the transport code off the ORIGINAL chain before sanitizing:
|
|
1692
|
+
// undici's "fetch failed" TypeError keeps ECONNRESET/ETIMEDOUT on
|
|
1693
|
+
// `cause`, and the sanitized copy deliberately drops that chain.
|
|
1694
|
+
const code = innermostErrorCode(error);
|
|
1695
|
+
const cause = sanitizeErrorCause(innermostError(error));
|
|
1677
1696
|
const redacted = new Error(cause.message, { cause });
|
|
1678
1697
|
redacted.name = cause.name;
|
|
1679
|
-
const code = cause.code;
|
|
1680
1698
|
if (code !== undefined) {
|
|
1681
1699
|
redacted.code = code;
|
|
1682
1700
|
}
|
|
@@ -1709,6 +1727,38 @@ export class FileDetector {
|
|
|
1709
1727
|
// check and the open() call routes the read outside the sandbox even
|
|
1710
1728
|
// though validation passed (TOCTOU). With no sandbox configured there's no
|
|
1711
1729
|
// boundary to defend, so the original path is used as given.
|
|
1730
|
+
//
|
|
1731
|
+
// Without an `allowedBaseDir` the instance's attachment policy decides:
|
|
1732
|
+
// its `allowedRoots` (when configured) bound the read the same way, and
|
|
1733
|
+
// either way the file is opened O_NOFOLLOW on its real path, regular
|
|
1734
|
+
// files only, under the size cap — see `readAttachmentFile`.
|
|
1735
|
+
if (!options?.allowedBaseDir) {
|
|
1736
|
+
try {
|
|
1737
|
+
const read = await readAttachmentFile(filePath, {
|
|
1738
|
+
policy: getAttachmentPolicy(options),
|
|
1739
|
+
maxBytes: maxSize,
|
|
1740
|
+
label: "attached file",
|
|
1741
|
+
});
|
|
1742
|
+
return read.buffer;
|
|
1743
|
+
}
|
|
1744
|
+
catch (error) {
|
|
1745
|
+
if (error instanceof AttachmentRefusedError) {
|
|
1746
|
+
throw error;
|
|
1747
|
+
}
|
|
1748
|
+
// A failed open/stat (ENOENT, EACCES, ELOOP, …) embeds the full path
|
|
1749
|
+
// in its message. Throw a copy whose message and cause are redacted
|
|
1750
|
+
// to the basename, keeping the code so callers can still classify it.
|
|
1751
|
+
logger.debug("loadFromPath: read failed", { filePath, error });
|
|
1752
|
+
const cause = sanitizeErrorCause(error, { filePath });
|
|
1753
|
+
const failed = new Error(cause.message, { cause });
|
|
1754
|
+
failed.name = cause.name;
|
|
1755
|
+
const code = cause.code;
|
|
1756
|
+
if (code !== undefined) {
|
|
1757
|
+
failed.code = code;
|
|
1758
|
+
}
|
|
1759
|
+
throw failed;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1712
1762
|
let pathToOpen = filePath;
|
|
1713
1763
|
if (options?.allowedBaseDir) {
|
|
1714
1764
|
let base;
|
|
@@ -2352,8 +2402,10 @@ class MagicBytesStrategy {
|
|
|
2352
2402
|
*/
|
|
2353
2403
|
class MimeTypeStrategy {
|
|
2354
2404
|
responseMimeType;
|
|
2355
|
-
|
|
2405
|
+
attachmentPolicy;
|
|
2406
|
+
constructor(responseMimeType, attachmentPolicy) {
|
|
2356
2407
|
this.responseMimeType = responseMimeType;
|
|
2408
|
+
this.attachmentPolicy = attachmentPolicy;
|
|
2357
2409
|
}
|
|
2358
2410
|
async detect(input) {
|
|
2359
2411
|
if (this.responseMimeType !== undefined) {
|
|
@@ -2378,20 +2430,18 @@ class MimeTypeStrategy {
|
|
|
2378
2430
|
if (contentType === undefined) {
|
|
2379
2431
|
// Wrap the whole HEAD (request + body drain) in withTimeout so a stalled
|
|
2380
2432
|
// dump() can't hang detection, per the project's async-timeout guideline.
|
|
2433
|
+
// The same guard as the GET: a HEAD to a private or loopback target
|
|
2434
|
+
// is a request too, and the old pre-flight followed redirects to
|
|
2435
|
+
// wherever they pointed.
|
|
2381
2436
|
contentType = await withTimeout((async () => {
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
const { request } = await import("undici");
|
|
2387
|
-
const response = await request(input, {
|
|
2388
|
-
dispatcher: await redirectFollowingDispatcher(5),
|
|
2437
|
+
const head = await downloadAttachment(input, {
|
|
2438
|
+
policy: this.attachmentPolicy,
|
|
2439
|
+
maxBytes: 0,
|
|
2440
|
+
label: "attached URL",
|
|
2389
2441
|
method: "HEAD",
|
|
2390
|
-
|
|
2391
|
-
bodyTimeout: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
2442
|
+
timeoutMs: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
2392
2443
|
});
|
|
2393
|
-
|
|
2394
|
-
return response.headers["content-type"] || "";
|
|
2444
|
+
return head.contentType;
|
|
2395
2445
|
})(), FileDetector.DEFAULT_HEAD_TIMEOUT);
|
|
2396
2446
|
setCachedUrlContentType(input, contentType, now);
|
|
2397
2447
|
}
|
|
@@ -181,7 +181,17 @@ function deepUnwrapJsonStrings(value, depth = 0) {
|
|
|
181
181
|
if (r.changed) {
|
|
182
182
|
changed = true;
|
|
183
183
|
}
|
|
184
|
-
out[k] =
|
|
184
|
+
// Define, never assign: `out[k] = …` with a model-supplied key of
|
|
185
|
+
// `__proto__` invokes the prototype setter and swaps `out`'s prototype
|
|
186
|
+
// for the model's object, so fields hidden from `Object.keys` read back
|
|
187
|
+
// through property access. `JSON.parse` creates such a key as an own
|
|
188
|
+
// data property, and this keeps it one.
|
|
189
|
+
Object.defineProperty(out, k, {
|
|
190
|
+
value: r.value,
|
|
191
|
+
enumerable: true,
|
|
192
|
+
writable: true,
|
|
193
|
+
configurable: true,
|
|
194
|
+
});
|
|
185
195
|
}
|
|
186
196
|
return { value: changed ? out : value, changed };
|
|
187
197
|
}
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
* jina_ (Jina), fish- (Fish Audio)
|
|
16
16
|
* - Generic key=value pairs: api_key=…, access_token: …, secret_key=…
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* Whether `key` names a value that must never appear in a log — the same list
|
|
20
|
+
* {@link sanitizeRecord} masks, matched case-insensitively.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isSensitiveLogKey(key: string): boolean;
|
|
18
23
|
/**
|
|
19
24
|
* Truncate `text` to `maxLen` chars then replace embedded secrets with `***`.
|
|
20
25
|
*
|
|
@@ -85,6 +85,14 @@ const SENSITIVE_OBJECT_KEYS = [
|
|
|
85
85
|
// PII, not a secret — but it has no business in debug log dumps.
|
|
86
86
|
"userEmail",
|
|
87
87
|
];
|
|
88
|
+
/**
|
|
89
|
+
* Whether `key` names a value that must never appear in a log — the same list
|
|
90
|
+
* {@link sanitizeRecord} masks, matched case-insensitively.
|
|
91
|
+
*/
|
|
92
|
+
export function isSensitiveLogKey(key) {
|
|
93
|
+
const lower = key.toLowerCase();
|
|
94
|
+
return SENSITIVE_OBJECT_KEYS.some((name) => name.toLowerCase() === lower);
|
|
95
|
+
}
|
|
88
96
|
/**
|
|
89
97
|
* Truncate `text` to `maxLen` chars then replace embedded secrets with `***`.
|
|
90
98
|
*
|
package/dist/utils/logger.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - Tabular data display
|
|
15
15
|
*/
|
|
16
16
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
+
import { isSensitiveLogKey, sanitizeForLog, sanitizeRecord, } from "./logSanitize.js";
|
|
17
18
|
/**
|
|
18
19
|
* Identifies which NeuroLink instance is emitting, for the async operation
|
|
19
20
|
* currently on the stack. Populated by `runInInstanceScope()` — which the SDK
|
|
@@ -85,6 +86,69 @@ function cloneLogData(data) {
|
|
|
85
86
|
return data;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Per-string cap for central redaction. Deliberately far above anything a
|
|
91
|
+
* debug payload carries, so this pass redacts without truncating — the
|
|
92
|
+
* logger's "zero truncation" promise for debug data stands.
|
|
93
|
+
*/
|
|
94
|
+
const REDACTION_STRING_CAP = 1_000_000;
|
|
95
|
+
/** Depth past which a payload is handed to `sanitizeRecord` wholesale. */
|
|
96
|
+
const REDACTION_MAX_DEPTH = 32;
|
|
97
|
+
function isPlainObject(value) {
|
|
98
|
+
const proto = Object.getPrototypeOf(value);
|
|
99
|
+
return proto === Object.prototype || proto === null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Central redaction for `debug` / `info` payloads — the same key list and
|
|
103
|
+
* secret patterns as `sanitizeRecord`, applied to plain objects and arrays.
|
|
104
|
+
*
|
|
105
|
+
* `sanitizeRecord` itself is not used directly on the whole payload because
|
|
106
|
+
* its `Object.entries` walk flattens what debug data is full of: an `Error`
|
|
107
|
+
* becomes `{}` (message and stack gone), a `Buffer` becomes thousands of
|
|
108
|
+
* numeric keys, a `Date`/`Map`/`Set` becomes `{}`. Those are left as they are
|
|
109
|
+
* — `serializeData` and `structuredClone` already know how to carry them —
|
|
110
|
+
* and only plain containers are walked, with sensitive keys masked and
|
|
111
|
+
* string values scrubbed of bearer tokens and known key prefixes. An `Error`
|
|
112
|
+
* gets its message scrubbed in place of the instance.
|
|
113
|
+
*
|
|
114
|
+
* Cost: one recursive pass over objects a call site already chose to log,
|
|
115
|
+
* and only when `shouldLog(level)` was true. Nothing runs for the 99% of
|
|
116
|
+
* `logger.debug` calls that are filtered out.
|
|
117
|
+
*/
|
|
118
|
+
function redactLogData(data, depth = 0) {
|
|
119
|
+
if (data === null || data === undefined) {
|
|
120
|
+
return data;
|
|
121
|
+
}
|
|
122
|
+
if (typeof data === "string") {
|
|
123
|
+
return sanitizeForLog(data, REDACTION_STRING_CAP);
|
|
124
|
+
}
|
|
125
|
+
if (typeof data !== "object") {
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
if (data instanceof Error) {
|
|
129
|
+
const copy = new Error(sanitizeForLog(data.message, REDACTION_STRING_CAP));
|
|
130
|
+
copy.name = data.name;
|
|
131
|
+
copy.stack = data.stack
|
|
132
|
+
? sanitizeForLog(data.stack, REDACTION_STRING_CAP)
|
|
133
|
+
: undefined;
|
|
134
|
+
return copy;
|
|
135
|
+
}
|
|
136
|
+
if (depth >= REDACTION_MAX_DEPTH) {
|
|
137
|
+
return sanitizeRecord(data, REDACTION_STRING_CAP);
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(data)) {
|
|
140
|
+
return data.map((item) => redactLogData(item, depth + 1));
|
|
141
|
+
}
|
|
142
|
+
if (!isPlainObject(data)) {
|
|
143
|
+
// Buffer, Date, Map, Set, class instances: carried as they are.
|
|
144
|
+
return data;
|
|
145
|
+
}
|
|
146
|
+
const out = {};
|
|
147
|
+
for (const [key, value] of Object.entries(data)) {
|
|
148
|
+
out[key] = isSensitiveLogKey(key) ? "***" : redactLogData(value, depth + 1);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
88
152
|
class NeuroLinkLogger {
|
|
89
153
|
logLevel = "info";
|
|
90
154
|
logs = [];
|
|
@@ -379,10 +443,18 @@ class NeuroLinkLogger {
|
|
|
379
443
|
* @param message - The message text to log
|
|
380
444
|
* @param data - Optional additional context data to include
|
|
381
445
|
*/
|
|
382
|
-
log(level, message,
|
|
446
|
+
log(level, message, rawData) {
|
|
383
447
|
if (!this.shouldLog(level)) {
|
|
384
448
|
return;
|
|
385
449
|
}
|
|
450
|
+
// Central redaction: a `debug`/`info` payload is masked by key
|
|
451
|
+
// (`apiKey`, `authorization`, `credentials`, …) and scrubbed of
|
|
452
|
+
// bearer/known-prefix tokens before it is stored, emitted or printed, so
|
|
453
|
+
// the next `logger.debug("…", options)` cannot leak a credential into
|
|
454
|
+
// `getLogs()`, a `log-event` sink or the console. `warn`/`error` are
|
|
455
|
+
// left verbatim: they carry the diagnostics an operator is debugging
|
|
456
|
+
// with, and every credential-bearing path already redacts at source.
|
|
457
|
+
const data = level === "debug" || level === "info" ? redactLogData(rawData) : rawData;
|
|
386
458
|
const entry = {
|
|
387
459
|
level,
|
|
388
460
|
message,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GenerateOptions, MultimodalChatMessage, StreamOptions, TextGenerationOptions, VisionImageOutputFormat } from "../types/index.js";
|
|
1
|
+
import type { AttachmentPolicy, GenerateOptions, MultimodalChatMessage, StreamOptions, TextGenerationOptions, VisionImageOutputFormat } from "../types/index.js";
|
|
2
2
|
import type { ModelMessage, ToolHistoryRow, ToolReplayMode } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Type-safe conversion from MultimodalChatMessage[] to ModelMessage[]
|
|
@@ -100,4 +100,4 @@ export declare function buildMultimodalModelMessages(options: GenerateOptions, p
|
|
|
100
100
|
* Entries that are `http(s)` URLs are left alone — they have no bytes yet, and
|
|
101
101
|
* `processImageToBase64` converts them once they are downloaded.
|
|
102
102
|
*/
|
|
103
|
-
export declare function normalizeVisionImageFormats(input: GenerateOptions["input"], outputFormat?: VisionImageOutputFormat): Promise<void>;
|
|
103
|
+
export declare function normalizeVisionImageFormats(input: GenerateOptions["input"], outputFormat?: VisionImageOutputFormat, attachmentPolicy?: AttachmentPolicy): Promise<void>;
|