@juspay/neurolink 13.8.16 → 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 +83 -8
- 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/expressBody.d.ts +33 -0
- package/dist/server/utils/expressBody.js +353 -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 +255 -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 +75 -52
- package/package.json +171 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound trust for the proxy listeners and the SDK-mounted proxy routes.
|
|
3
|
+
*
|
|
4
|
+
* Both pool engines spend the operator's subscriptions on behalf of whoever
|
|
5
|
+
* reaches the port. On loopback that is the operator; anywhere else it is
|
|
6
|
+
* anyone. This module holds the three decisions that keep that honest:
|
|
7
|
+
*
|
|
8
|
+
* - **Bind policy.** A listener may bind off-loopback only when something
|
|
9
|
+
* authenticates callers: the share gate, an inbound token, or the operator's
|
|
10
|
+
* explicit `--allow-unauthenticated`.
|
|
11
|
+
* - **Inbound token.** `NEUROLINK_PROXY_INBOUND_TOKEN` (or `--inbound-token`)
|
|
12
|
+
* is a bearer every caller must present, compared in constant time. A share
|
|
13
|
+
* token is an alternative credential, judged by the share gate.
|
|
14
|
+
* - **Operator-only surfaces.** `/accounts`, `/limits` and `/status` name the
|
|
15
|
+
* pool's accounts; they are refused to borrowers and, when a token is
|
|
16
|
+
* configured, to anyone without it.
|
|
17
|
+
*
|
|
18
|
+
* @module proxy/inboundAuth
|
|
19
|
+
*/
|
|
20
|
+
import type { ProxyBindPolicyDecision, ProxyBindPolicyInput, ProxyInboundAuthOptions, ProxyShareRefusalResponse } from "../types/index.js";
|
|
21
|
+
/** Dedicated header, so the inbound token never collides with a client credential. */
|
|
22
|
+
export declare const INBOUND_TOKEN_HEADER = "x-neurolink-inbound-token";
|
|
23
|
+
/** Set on every refusal so a client can tell "token required" from "bad request". */
|
|
24
|
+
export declare const INBOUND_AUTH_HEADER = "x-neurolink-inbound-auth";
|
|
25
|
+
export declare const INBOUND_TOKEN_ENV = "NEUROLINK_PROXY_INBOUND_TOKEN";
|
|
26
|
+
export declare const ALLOW_UNAUTHENTICATED_ENV = "NEUROLINK_PROXY_ALLOW_UNAUTHENTICATED";
|
|
27
|
+
/**
|
|
28
|
+
* Compare two secrets without leaking where they diverge. Hand-rolled rather
|
|
29
|
+
* than `crypto.timingSafeEqual` so the proxy routes stay loadable from a build
|
|
30
|
+
* whose `node:crypto` stub does not carry it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function secretsMatch(expected: string, presented: string): boolean;
|
|
33
|
+
/** The inbound token in force: an explicit value wins over the environment. */
|
|
34
|
+
export declare function resolveInboundToken(explicit?: string): string | undefined;
|
|
35
|
+
/** `NEUROLINK_PROXY_ALLOW_UNAUTHENTICATED` as a boolean. */
|
|
36
|
+
export declare function isUnauthenticatedBindAllowedByEnv(): boolean;
|
|
37
|
+
/** Loopback addresses in every spelling a bind flag accepts. */
|
|
38
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* May the proxy bind to `host`?
|
|
41
|
+
*
|
|
42
|
+
* Loopback always may. Anything else needs the share gate, an inbound token,
|
|
43
|
+
* or the operator's explicit acknowledgement — and the acknowledgement still
|
|
44
|
+
* earns a warning, because the subscriptions are then one port scan away.
|
|
45
|
+
*/
|
|
46
|
+
export declare function evaluateProxyBindPolicy(input: ProxyBindPolicyInput): ProxyBindPolicyDecision;
|
|
47
|
+
/** True when the request presents the configured inbound token. */
|
|
48
|
+
export declare function hasValidInboundToken(headers: Record<string, string | undefined>, token: string | undefined): boolean;
|
|
49
|
+
/** The 401 an unauthenticated caller receives, in the Anthropic error shape. */
|
|
50
|
+
export declare function buildInboundAuthRefusal(): ProxyShareRefusalResponse;
|
|
51
|
+
/**
|
|
52
|
+
* Decide an inbound request against the configured token.
|
|
53
|
+
*
|
|
54
|
+
* A request carrying a share token is handed to the share gate instead: a
|
|
55
|
+
* borrower holds a grant, not the operator's token, and the gate is what
|
|
56
|
+
* judges a grant. Everything else must present the token.
|
|
57
|
+
*/
|
|
58
|
+
export declare function authorizeInboundRequest(headers: Record<string, string | undefined>, token: string | undefined): {
|
|
59
|
+
ok: true;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
response: ProxyShareRefusalResponse;
|
|
63
|
+
};
|
|
64
|
+
/** `Response` form of the refusal, for route factories whose adapters pass one through. */
|
|
65
|
+
export declare function inboundAuthRefusalResponse(): Response;
|
|
66
|
+
/**
|
|
67
|
+
* Say, loudly and once per engine, that a proxy route group was mounted with
|
|
68
|
+
* nothing authenticating its callers. `logger.always` on purpose: the ordinary
|
|
69
|
+
* warn level is invisible outside debug runs, and an open proxy is exactly the
|
|
70
|
+
* thing an operator must not be able to miss. Suppressed when the caller
|
|
71
|
+
* recorded the decision (`allowUnauthenticated`), which the CLI does after its
|
|
72
|
+
* own bind check.
|
|
73
|
+
*/
|
|
74
|
+
export declare function warnIfProxyRoutesUnauthenticated(engine: string, options: ProxyInboundAuthOptions | undefined, token: string | undefined): void;
|
|
75
|
+
/** Test isolation only. */
|
|
76
|
+
export declare function resetInboundAuthWarningsForTests(): void;
|
|
77
|
+
/** Paths that stay open on an authenticated listener: liveness and self-authenticating peers. */
|
|
78
|
+
export declare function isInboundAuthExemptPath(path: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* How many pooled accounts one inbound request may be tried against.
|
|
81
|
+
*
|
|
82
|
+
* Without a cap a single request fans out to every account in the pool — N
|
|
83
|
+
* upstream posts, N× quota — on any rotation trigger. Default 3;
|
|
84
|
+
* `NEUROLINK_PROXY_MAX_ACCOUNTS_PER_REQUEST=0` restores the unbounded
|
|
85
|
+
* behaviour.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MAX_ACCOUNTS_PER_REQUEST_ENV = "NEUROLINK_PROXY_MAX_ACCOUNTS_PER_REQUEST";
|
|
88
|
+
export declare const DEFAULT_MAX_ACCOUNTS_PER_REQUEST = 3;
|
|
89
|
+
export declare function resolveMaxAccountsPerRequest(): number;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound trust for the proxy listeners and the SDK-mounted proxy routes.
|
|
3
|
+
*
|
|
4
|
+
* Both pool engines spend the operator's subscriptions on behalf of whoever
|
|
5
|
+
* reaches the port. On loopback that is the operator; anywhere else it is
|
|
6
|
+
* anyone. This module holds the three decisions that keep that honest:
|
|
7
|
+
*
|
|
8
|
+
* - **Bind policy.** A listener may bind off-loopback only when something
|
|
9
|
+
* authenticates callers: the share gate, an inbound token, or the operator's
|
|
10
|
+
* explicit `--allow-unauthenticated`.
|
|
11
|
+
* - **Inbound token.** `NEUROLINK_PROXY_INBOUND_TOKEN` (or `--inbound-token`)
|
|
12
|
+
* is a bearer every caller must present, compared in constant time. A share
|
|
13
|
+
* token is an alternative credential, judged by the share gate.
|
|
14
|
+
* - **Operator-only surfaces.** `/accounts`, `/limits` and `/status` name the
|
|
15
|
+
* pool's accounts; they are refused to borrowers and, when a token is
|
|
16
|
+
* configured, to anyone without it.
|
|
17
|
+
*
|
|
18
|
+
* @module proxy/inboundAuth
|
|
19
|
+
*/
|
|
20
|
+
import { logger } from "../utils/logger.js";
|
|
21
|
+
import { extractShareToken } from "./shareGate.js";
|
|
22
|
+
/** Dedicated header, so the inbound token never collides with a client credential. */
|
|
23
|
+
export const INBOUND_TOKEN_HEADER = "x-neurolink-inbound-token";
|
|
24
|
+
/** Set on every refusal so a client can tell "token required" from "bad request". */
|
|
25
|
+
export const INBOUND_AUTH_HEADER = "x-neurolink-inbound-auth";
|
|
26
|
+
export const INBOUND_TOKEN_ENV = "NEUROLINK_PROXY_INBOUND_TOKEN";
|
|
27
|
+
export const ALLOW_UNAUTHENTICATED_ENV = "NEUROLINK_PROXY_ALLOW_UNAUTHENTICATED";
|
|
28
|
+
/**
|
|
29
|
+
* Compare two secrets without leaking where they diverge. Hand-rolled rather
|
|
30
|
+
* than `crypto.timingSafeEqual` so the proxy routes stay loadable from a build
|
|
31
|
+
* whose `node:crypto` stub does not carry it.
|
|
32
|
+
*/
|
|
33
|
+
export function secretsMatch(expected, presented) {
|
|
34
|
+
if (expected.length !== presented.length || expected.length === 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
let difference = 0;
|
|
38
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
39
|
+
difference |= expected.charCodeAt(index) ^ presented.charCodeAt(index);
|
|
40
|
+
}
|
|
41
|
+
return difference === 0;
|
|
42
|
+
}
|
|
43
|
+
function envFlagIsOn(name) {
|
|
44
|
+
const raw = (process.env[name] ?? "").trim().toLowerCase();
|
|
45
|
+
return raw === "1" || raw === "true" || raw === "on" || raw === "yes";
|
|
46
|
+
}
|
|
47
|
+
/** The inbound token in force: an explicit value wins over the environment. */
|
|
48
|
+
export function resolveInboundToken(explicit) {
|
|
49
|
+
const candidate = explicit?.trim() || process.env[INBOUND_TOKEN_ENV]?.trim();
|
|
50
|
+
return candidate ? candidate : undefined;
|
|
51
|
+
}
|
|
52
|
+
/** `NEUROLINK_PROXY_ALLOW_UNAUTHENTICATED` as a boolean. */
|
|
53
|
+
export function isUnauthenticatedBindAllowedByEnv() {
|
|
54
|
+
return envFlagIsOn(ALLOW_UNAUTHENTICATED_ENV);
|
|
55
|
+
}
|
|
56
|
+
/** Loopback addresses in every spelling a bind flag accepts. */
|
|
57
|
+
export function isLoopbackHost(host) {
|
|
58
|
+
const trimmed = host
|
|
59
|
+
.trim()
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/^\[|\]$/g, "");
|
|
62
|
+
if (trimmed === "localhost" || trimmed === "::1") {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (/^0{1,4}(:0{1,4}){6}:0{0,3}1$/.test(trimmed)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (trimmed.startsWith("::ffff:")) {
|
|
69
|
+
return isLoopbackHost(trimmed.slice("::ffff:".length));
|
|
70
|
+
}
|
|
71
|
+
const octets = trimmed.split(".");
|
|
72
|
+
return (octets.length === 4 &&
|
|
73
|
+
octets[0] === "127" &&
|
|
74
|
+
octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* May the proxy bind to `host`?
|
|
78
|
+
*
|
|
79
|
+
* Loopback always may. Anything else needs the share gate, an inbound token,
|
|
80
|
+
* or the operator's explicit acknowledgement — and the acknowledgement still
|
|
81
|
+
* earns a warning, because the subscriptions are then one port scan away.
|
|
82
|
+
*/
|
|
83
|
+
export function evaluateProxyBindPolicy(input) {
|
|
84
|
+
if (isLoopbackHost(input.host)) {
|
|
85
|
+
return { allowed: true };
|
|
86
|
+
}
|
|
87
|
+
if (input.inboundToken || input.requireGrant) {
|
|
88
|
+
return { allowed: true };
|
|
89
|
+
}
|
|
90
|
+
if (input.allowUnauthenticated) {
|
|
91
|
+
return {
|
|
92
|
+
allowed: true,
|
|
93
|
+
warning: `Binding ${input.host} with no inbound authentication: anyone who reaches ` +
|
|
94
|
+
`this port can spend every pooled subscription. --allow-unauthenticated ` +
|
|
95
|
+
`acknowledges that; put an authenticating reverse proxy in front.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
allowed: false,
|
|
100
|
+
reason: `Refusing to bind ${input.host}: it is not a loopback address and nothing ` +
|
|
101
|
+
`would authenticate inbound callers. Set ${INBOUND_TOKEN_ENV} (or pass ` +
|
|
102
|
+
`--inbound-token <secret>), gate the port with NEUROLINK_PROXY_REQUIRE_GRANT=1, ` +
|
|
103
|
+
`or pass --allow-unauthenticated to accept an open proxy.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function presentedInboundToken(headers) {
|
|
107
|
+
const dedicated = headers[INBOUND_TOKEN_HEADER];
|
|
108
|
+
if (dedicated?.trim()) {
|
|
109
|
+
return dedicated.trim();
|
|
110
|
+
}
|
|
111
|
+
const authorization = headers.authorization ?? headers.Authorization;
|
|
112
|
+
if (!authorization) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const trimmed = authorization.trim();
|
|
116
|
+
return trimmed.toLowerCase().startsWith("bearer ")
|
|
117
|
+
? trimmed.slice("bearer ".length).trim()
|
|
118
|
+
: undefined;
|
|
119
|
+
}
|
|
120
|
+
/** True when the request presents the configured inbound token. */
|
|
121
|
+
export function hasValidInboundToken(headers, token) {
|
|
122
|
+
if (!token) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const presented = presentedInboundToken(headers);
|
|
126
|
+
return presented !== undefined && secretsMatch(token, presented);
|
|
127
|
+
}
|
|
128
|
+
/** The 401 an unauthenticated caller receives, in the Anthropic error shape. */
|
|
129
|
+
export function buildInboundAuthRefusal() {
|
|
130
|
+
return {
|
|
131
|
+
status: 401,
|
|
132
|
+
headers: { [INBOUND_AUTH_HEADER]: "required" },
|
|
133
|
+
body: {
|
|
134
|
+
type: "error",
|
|
135
|
+
error: {
|
|
136
|
+
type: "authentication_error",
|
|
137
|
+
message: "This proxy requires an inbound token. Send it as " +
|
|
138
|
+
`${INBOUND_TOKEN_HEADER} or as Authorization: Bearer <token>.`,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Decide an inbound request against the configured token.
|
|
145
|
+
*
|
|
146
|
+
* A request carrying a share token is handed to the share gate instead: a
|
|
147
|
+
* borrower holds a grant, not the operator's token, and the gate is what
|
|
148
|
+
* judges a grant. Everything else must present the token.
|
|
149
|
+
*/
|
|
150
|
+
export function authorizeInboundRequest(headers, token) {
|
|
151
|
+
if (!token) {
|
|
152
|
+
return { ok: true };
|
|
153
|
+
}
|
|
154
|
+
if (hasValidInboundToken(headers, token)) {
|
|
155
|
+
return { ok: true };
|
|
156
|
+
}
|
|
157
|
+
if (extractShareToken(headers) !== undefined) {
|
|
158
|
+
return { ok: true };
|
|
159
|
+
}
|
|
160
|
+
return { ok: false, response: buildInboundAuthRefusal() };
|
|
161
|
+
}
|
|
162
|
+
/** `Response` form of the refusal, for route factories whose adapters pass one through. */
|
|
163
|
+
export function inboundAuthRefusalResponse() {
|
|
164
|
+
const refusal = buildInboundAuthRefusal();
|
|
165
|
+
return new Response(JSON.stringify(refusal.body), {
|
|
166
|
+
status: refusal.status,
|
|
167
|
+
headers: { "content-type": "application/json", ...refusal.headers },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const warnedEngines = new Set();
|
|
171
|
+
/**
|
|
172
|
+
* Say, loudly and once per engine, that a proxy route group was mounted with
|
|
173
|
+
* nothing authenticating its callers. `logger.always` on purpose: the ordinary
|
|
174
|
+
* warn level is invisible outside debug runs, and an open proxy is exactly the
|
|
175
|
+
* thing an operator must not be able to miss. Suppressed when the caller
|
|
176
|
+
* recorded the decision (`allowUnauthenticated`), which the CLI does after its
|
|
177
|
+
* own bind check.
|
|
178
|
+
*/
|
|
179
|
+
export function warnIfProxyRoutesUnauthenticated(engine, options, token) {
|
|
180
|
+
if (token || options?.allowUnauthenticated || warnedEngines.has(engine)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
warnedEngines.add(engine);
|
|
184
|
+
logger.always(`[proxy] SECURITY: the ${engine} proxy routes were mounted with no inbound ` +
|
|
185
|
+
`authentication. Anyone who can reach them spends the pooled subscriptions. ` +
|
|
186
|
+
`Set ${INBOUND_TOKEN_ENV}, pass inboundAuth.token, or bind loopback only ` +
|
|
187
|
+
`and set inboundAuth.allowUnauthenticated to acknowledge this.`);
|
|
188
|
+
}
|
|
189
|
+
/** Test isolation only. */
|
|
190
|
+
export function resetInboundAuthWarningsForTests() {
|
|
191
|
+
warnedEngines.clear();
|
|
192
|
+
}
|
|
193
|
+
/** Paths that stay open on an authenticated listener: liveness and self-authenticating peers. */
|
|
194
|
+
export function isInboundAuthExemptPath(path) {
|
|
195
|
+
return (path === "/health" ||
|
|
196
|
+
path === "/internal/update-control" ||
|
|
197
|
+
path.startsWith("/peer/"));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* How many pooled accounts one inbound request may be tried against.
|
|
201
|
+
*
|
|
202
|
+
* Without a cap a single request fans out to every account in the pool — N
|
|
203
|
+
* upstream posts, N× quota — on any rotation trigger. Default 3;
|
|
204
|
+
* `NEUROLINK_PROXY_MAX_ACCOUNTS_PER_REQUEST=0` restores the unbounded
|
|
205
|
+
* behaviour.
|
|
206
|
+
*/
|
|
207
|
+
export const MAX_ACCOUNTS_PER_REQUEST_ENV = "NEUROLINK_PROXY_MAX_ACCOUNTS_PER_REQUEST";
|
|
208
|
+
export const DEFAULT_MAX_ACCOUNTS_PER_REQUEST = 3;
|
|
209
|
+
export function resolveMaxAccountsPerRequest() {
|
|
210
|
+
const raw = process.env[MAX_ACCOUNTS_PER_REQUEST_ENV]?.trim();
|
|
211
|
+
if (raw === undefined || raw === "") {
|
|
212
|
+
return DEFAULT_MAX_ACCOUNTS_PER_REQUEST;
|
|
213
|
+
}
|
|
214
|
+
const parsed = Number(raw);
|
|
215
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
216
|
+
return DEFAULT_MAX_ACCOUNTS_PER_REQUEST;
|
|
217
|
+
}
|
|
218
|
+
return parsed === 0 ? Number.POSITIVE_INFINITY : Math.floor(parsed);
|
|
219
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { RefreshableAccount, RefreshResult, TokenPersistTarget } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* An upstream refresh error body is logged for diagnosis, never verbatim: a
|
|
4
|
+
* token endpoint can echo the grant it rejected, and anything that looks like
|
|
5
|
+
* a credential is masked before the line is written.
|
|
6
|
+
*/
|
|
7
|
+
export declare function redactRefreshErrorBody(body: string): string;
|
|
2
8
|
export declare function needsRefresh(account: RefreshableAccount): boolean;
|
|
3
9
|
export declare function needsBackgroundRefresh(account: RefreshableAccount): boolean;
|
|
4
10
|
export declare function needsInlineRefresh(account: RefreshableAccount): boolean;
|
|
@@ -17,6 +17,18 @@ const SUCCESS_CACHE_MS = 60_000;
|
|
|
17
17
|
// slow token-store decrypt can never stall (or abort) a live token refresh.
|
|
18
18
|
const PEEK_TOKENS_TIMEOUT_MS = 2_000;
|
|
19
19
|
const refreshesInFlight = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* An upstream refresh error body is logged for diagnosis, never verbatim: a
|
|
22
|
+
* token endpoint can echo the grant it rejected, and anything that looks like
|
|
23
|
+
* a credential is masked before the line is written.
|
|
24
|
+
*/
|
|
25
|
+
export function redactRefreshErrorBody(body) {
|
|
26
|
+
return body
|
|
27
|
+
.replace(/("?(?:access_token|refresh_token|id_token|client_secret|code)"?\s*[:=]\s*"?)[^",\s]+/gi, "$1[REDACTED]")
|
|
28
|
+
.replace(/\bsk-ant-[A-Za-z0-9_-]+/g, "sk-ant-[REDACTED]")
|
|
29
|
+
.replace(/\b[A-Za-z0-9_-]{64,}\b/g, "[REDACTED]")
|
|
30
|
+
.slice(0, 300);
|
|
31
|
+
}
|
|
20
32
|
function expiresWithin(account, bufferMs) {
|
|
21
33
|
return !!(account.expiresAt &&
|
|
22
34
|
account.expiresAt <= Date.now() + bufferMs &&
|
|
@@ -69,7 +81,7 @@ async function performTokenRefresh(account) {
|
|
|
69
81
|
const errorBody = await resp.text();
|
|
70
82
|
logger.warn(`[token-refresh] failed for ${account.label} at ${redactUrlForError(url)}`, {
|
|
71
83
|
status: resp.status,
|
|
72
|
-
error: errorBody
|
|
84
|
+
error: redactRefreshErrorBody(errorBody),
|
|
73
85
|
});
|
|
74
86
|
const failure = {
|
|
75
87
|
success: false,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Suppressed versions expire after 24 hours.
|
|
8
8
|
*/
|
|
9
9
|
import fs from "fs";
|
|
10
|
+
import { ensurePrivateDirectorySync } from "../auth/secureFiles.js";
|
|
10
11
|
import os from "os";
|
|
11
12
|
import path from "path";
|
|
12
13
|
import { randomUUID } from "node:crypto";
|
|
@@ -39,10 +40,9 @@ function resolveStatePath(overridePath) {
|
|
|
39
40
|
* Ensure the parent directory of the given file path exists.
|
|
40
41
|
*/
|
|
41
42
|
function ensureParentDir(filePath) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
43
|
+
// `~/.neurolink` holds credential material; whoever creates it first must
|
|
44
|
+
// create it owner-only, and re-assert the mode when it already exists.
|
|
45
|
+
ensurePrivateDirectorySync(path.dirname(filePath));
|
|
46
46
|
}
|
|
47
47
|
/** Reject malformed persisted failure metadata before it reaches status output. */
|
|
48
48
|
function isValidLastFailure(value) {
|
|
@@ -88,12 +88,6 @@ export declare class PDFLoader implements DocumentLoader {
|
|
|
88
88
|
private loadPdfParser;
|
|
89
89
|
private parsePageRange;
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Web page loader
|
|
93
|
-
*
|
|
94
|
-
* Fetches and extracts content from web pages.
|
|
95
|
-
* Supports basic HTML parsing without external dependencies.
|
|
96
|
-
*/
|
|
97
91
|
export declare class WebLoader implements DocumentLoader {
|
|
98
92
|
private defaultUserAgent;
|
|
99
93
|
load(source: string, options?: WebLoaderOptions): Promise<MDocument>;
|
|
@@ -27,6 +27,8 @@ import { existsSync } from "fs";
|
|
|
27
27
|
import { readFile } from "fs/promises";
|
|
28
28
|
import { basename, extname } from "path";
|
|
29
29
|
import { logger } from "../../utils/logger.js";
|
|
30
|
+
import { downloadAttachment } from "../../utils/attachmentPolicy.js";
|
|
31
|
+
import { redactUrlForError } from "../../utils/logSanitize.js";
|
|
30
32
|
import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
|
|
31
33
|
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
32
34
|
import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, dedupeColumnNames, sanitizeColumnName, } from "../../utils/csvProcessor.js";
|
|
@@ -351,6 +353,8 @@ export class PDFLoader {
|
|
|
351
353
|
* Fetches and extracts content from web pages.
|
|
352
354
|
* Supports basic HTML parsing without external dependencies.
|
|
353
355
|
*/
|
|
356
|
+
/** 25 MiB — the default cap on a fetched page. */
|
|
357
|
+
const WEB_LOADER_MAX_BYTES = 25 * 1024 * 1024;
|
|
354
358
|
export class WebLoader {
|
|
355
359
|
defaultUserAgent = "Mozilla/5.0 (compatible; NeuroLink/1.0; +https://github.com/juspay/neurolink)";
|
|
356
360
|
async load(source, options) {
|
|
@@ -358,23 +362,25 @@ export class WebLoader {
|
|
|
358
362
|
throw new Error(`Invalid URL: ${source}`);
|
|
359
363
|
}
|
|
360
364
|
logger.debug("[WebLoader] Fetching URL", {
|
|
361
|
-
url: source,
|
|
365
|
+
url: redactUrlForError(source),
|
|
362
366
|
timeout: options?.timeout,
|
|
363
367
|
});
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
+
// Through the attachment policy: HTTPS to a public host unless the policy
|
|
369
|
+
// says otherwise, DNS-pinned, redirects re-validated per hop with the
|
|
370
|
+
// caller's headers dropped, and the body bounded while it streams rather
|
|
371
|
+
// than read whole with `text()`.
|
|
372
|
+
const downloaded = await downloadAttachment(source, {
|
|
373
|
+
policy: options?.attachmentPolicy,
|
|
374
|
+
maxBytes: options?.maxBytes ?? WEB_LOADER_MAX_BYTES,
|
|
375
|
+
label: "web page",
|
|
376
|
+
timeoutMs: options?.timeout,
|
|
368
377
|
headers: {
|
|
369
378
|
"User-Agent": options?.userAgent || this.defaultUserAgent,
|
|
370
379
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
371
380
|
...options?.headers,
|
|
372
381
|
},
|
|
373
382
|
});
|
|
374
|
-
|
|
375
|
-
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
|
|
376
|
-
}
|
|
377
|
-
const html = await response.text();
|
|
383
|
+
const html = downloaded.buffer.toString("utf-8");
|
|
378
384
|
let content = html;
|
|
379
385
|
// Extract main content if requested
|
|
380
386
|
if (options?.extractMainContent) {
|
|
@@ -388,7 +394,7 @@ export class WebLoader {
|
|
|
388
394
|
source,
|
|
389
395
|
url: source,
|
|
390
396
|
fetchedAt: new Date().toISOString(),
|
|
391
|
-
contentType:
|
|
397
|
+
contentType: downloaded.contentType || "text/html",
|
|
392
398
|
...options?.metadata,
|
|
393
399
|
},
|
|
394
400
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RAGConfig, VectorQueryResult, RAGPreparedTool } from "../types/index.js";
|
|
1
|
+
import type { AttachmentPolicy, RAGConfig, VectorQueryResult, RAGPreparedTool } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Generate deterministic embeddings for chunks.
|
|
4
4
|
* Combines character-frequency (40%) with word-level hash features (60%)
|
|
@@ -30,5 +30,5 @@ declare function diversifyResults(results: VectorQueryResult[], topK: number): V
|
|
|
30
30
|
* is the default, and the fallback when a configured embedding provider fails).
|
|
31
31
|
* The returned tool rejects a query over the same length the same way.
|
|
32
32
|
*/
|
|
33
|
-
export declare function prepareRAGTool(ragConfig: RAGConfig, fallbackProvider?: string): Promise<RAGPreparedTool>;
|
|
33
|
+
export declare function prepareRAGTool(ragConfig: RAGConfig, fallbackProvider?: string, attachmentPolicy?: AttachmentPolicy): Promise<RAGPreparedTool>;
|
|
34
34
|
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
2
1
|
import { extname, resolve } from "path";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
|
|
@@ -6,12 +5,19 @@ import { logger } from "../utils/logger.js";
|
|
|
6
5
|
import { redactUrlForError } from "../utils/logSanitize.js";
|
|
7
6
|
import { ErrorFactory } from "../utils/errorHandling.js";
|
|
8
7
|
import { ImageProcessor } from "../utils/imageProcessor.js";
|
|
8
|
+
import { readAttachmentFile, resolveAttachmentPath, resolveAttachmentPolicy, } from "../utils/attachmentPolicy.js";
|
|
9
9
|
import { createChunker } from "./ChunkerFactory.js";
|
|
10
10
|
import { createVectorQueryTool, InMemoryVectorStore, } from "./retrieval/vectorQueryTool.js";
|
|
11
11
|
import { ImageLoader } from "./document/imageLoader.js";
|
|
12
12
|
import { IMAGE_EXTENSIONS } from "../processors/config/index.js";
|
|
13
13
|
import { withSpan } from "../telemetry/withSpan.js";
|
|
14
14
|
import { tracers } from "../telemetry/tracers.js";
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for one `rag.files` text source: 50 MiB. A file is chunked and
|
|
17
|
+
* embedded in full, so a larger one is a mistake rather than a document;
|
|
18
|
+
* `attachments.maxBytes` lowers it.
|
|
19
|
+
*/
|
|
20
|
+
const RAG_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
15
21
|
/**
|
|
16
22
|
* Maps file extensions to recommended chunking strategies
|
|
17
23
|
*/
|
|
@@ -173,7 +179,7 @@ function diversifyResults(results, topK) {
|
|
|
173
179
|
* is the default, and the fallback when a configured embedding provider fails).
|
|
174
180
|
* The returned tool rejects a query over the same length the same way.
|
|
175
181
|
*/
|
|
176
|
-
export async function prepareRAGTool(ragConfig, fallbackProvider) {
|
|
182
|
+
export async function prepareRAGTool(ragConfig, fallbackProvider, attachmentPolicy = resolveAttachmentPolicy()) {
|
|
177
183
|
const span = SpanSerializer.createSpan(SpanType.RAG, "rag.prepare", {
|
|
178
184
|
"rag.operation": "prepare",
|
|
179
185
|
"rag.files_count": ragConfig.files?.length ?? 0,
|
|
@@ -182,7 +188,7 @@ export async function prepareRAGTool(ragConfig, fallbackProvider) {
|
|
|
182
188
|
});
|
|
183
189
|
const startTime = Date.now();
|
|
184
190
|
try {
|
|
185
|
-
const result = await _prepareRAGToolInner(ragConfig, fallbackProvider);
|
|
191
|
+
const result = await _prepareRAGToolInner(ragConfig, fallbackProvider, attachmentPolicy);
|
|
186
192
|
span.durationMs = Date.now() - startTime;
|
|
187
193
|
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
|
|
188
194
|
endedSpan.attributes = {
|
|
@@ -202,7 +208,7 @@ export async function prepareRAGTool(ragConfig, fallbackProvider) {
|
|
|
202
208
|
throw error;
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
|
-
async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
211
|
+
async function _prepareRAGToolInner(ragConfig, fallbackProvider, attachmentPolicy) {
|
|
206
212
|
const { files, strategy: userStrategy, chunkSize = 1000, chunkOverlap = 200, topK: userTopK = 5, toolName = "search_knowledge_base", toolDescription = "REQUIRED: Search through pre-loaded local documents to find relevant information. Use this tool FIRST before any web search or other tools. This searches an indexed knowledge base of documents the user has provided.", embeddingProvider, embeddingModel, } = ragConfig;
|
|
207
213
|
if (!files || files.length === 0) {
|
|
208
214
|
throw new Error("RAG config requires at least one file path in 'files'");
|
|
@@ -248,10 +254,6 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
248
254
|
continue;
|
|
249
255
|
}
|
|
250
256
|
const resolvedPath = resolve(filePath);
|
|
251
|
-
if (!existsSync(resolvedPath)) {
|
|
252
|
-
logger.warn(`[RAG] File not found, skipping: ${resolvedPath}`);
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
257
|
// Detect image files by extension
|
|
256
258
|
const ext = extname(resolvedPath).toLowerCase();
|
|
257
259
|
if (ext === ".svg") {
|
|
@@ -260,15 +262,38 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
260
262
|
continue;
|
|
261
263
|
}
|
|
262
264
|
if (IMAGE_EXTENSIONS.includes(ext)) {
|
|
263
|
-
|
|
265
|
+
// The same containment as the text branch below; ImageLoader then reads
|
|
266
|
+
// the real path this returned. A path outside the roots is skipped with
|
|
267
|
+
// the same warning as an unreadable one, so the tool keeps working on
|
|
268
|
+
// the files it may read.
|
|
269
|
+
try {
|
|
270
|
+
const real = await resolveAttachmentPath(resolvedPath, attachmentPolicy, "RAG image");
|
|
271
|
+
imageFiles.push({ path: real, ext });
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
logger.warn(`[RAG] Failed to read image: ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
275
|
+
}
|
|
264
276
|
continue;
|
|
265
277
|
}
|
|
266
278
|
try {
|
|
267
|
-
|
|
279
|
+
// Roots containment when configured, O_NOFOLLOW open of the real path,
|
|
280
|
+
// regular files only, and a bounded read — where this was a bare
|
|
281
|
+
// `readFileSync` of whatever string arrived.
|
|
282
|
+
const read = await readAttachmentFile(resolvedPath, {
|
|
283
|
+
policy: attachmentPolicy,
|
|
284
|
+
maxBytes: RAG_FILE_MAX_BYTES,
|
|
285
|
+
label: "RAG file",
|
|
286
|
+
});
|
|
287
|
+
const content = read.buffer.toString("utf-8");
|
|
268
288
|
const strategy = userStrategy || detectStrategy(resolvedPath);
|
|
269
289
|
fileContents.push({ path: resolvedPath, content, strategy });
|
|
270
290
|
}
|
|
271
291
|
catch (error) {
|
|
292
|
+
const code = error?.code;
|
|
293
|
+
if (code === "ENOENT") {
|
|
294
|
+
logger.warn(`[RAG] File not found, skipping: ${resolvedPath}`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
272
297
|
logger.warn(`[RAG] Failed to read file: ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
298
|
}
|
|
274
299
|
}
|
|
@@ -39,6 +39,8 @@ export declare function createVectorQueryTool(config: VectorQueryToolConfig, vec
|
|
|
39
39
|
*/
|
|
40
40
|
export declare class InMemoryVectorStore implements VectorStore {
|
|
41
41
|
private vectors;
|
|
42
|
+
/** Vetted `$regex` patterns: the compiled expression, or null when refused. */
|
|
43
|
+
private readonly regexFilterCache;
|
|
42
44
|
/**
|
|
43
45
|
* Add vectors to an index
|
|
44
46
|
*/
|
|
@@ -65,6 +67,11 @@ export declare class InMemoryVectorStore implements VectorStore {
|
|
|
65
67
|
* Check if metadata matches filter
|
|
66
68
|
*/
|
|
67
69
|
private matchesFilter;
|
|
70
|
+
/**
|
|
71
|
+
* Evaluate a `$regex` filter operator. Patterns are vetted once per store
|
|
72
|
+
* and the verdict cached, so a refused pattern costs one analysis.
|
|
73
|
+
*/
|
|
74
|
+
private matchesRegexFilter;
|
|
68
75
|
/**
|
|
69
76
|
* Calculate cosine similarity between two vectors
|
|
70
77
|
*/
|