@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
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
import { logger } from "../../utils/logger.js";
|
|
7
7
|
import { AlreadyRunningError, ServerAdapterError, ServerStopError, wrapError, } from "../errors.js";
|
|
8
8
|
import { BaseServerAdapter } from "../abstract/baseServerAdapter.js";
|
|
9
|
+
import { resolveClientAddress } from "../utils/clientAddress.js";
|
|
10
|
+
import { shapeRouteError } from "../utils/clientError.js";
|
|
11
|
+
import { pathMatchesAny } from "../utils/pathMatch.js";
|
|
9
12
|
import { isErrorResponse } from "../utils/validation.js";
|
|
10
13
|
import { isRawBodyContentType, isWebResponse, parseByteLimit, RawBodyTooLargeError, readRawBody, readWebResponse, } from "../utils/rawBody.js";
|
|
11
14
|
/**
|
|
@@ -20,6 +23,8 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
20
23
|
rateLimitStore = new Map();
|
|
21
24
|
rateLimitCleanupInterval;
|
|
22
25
|
sockets = new Set();
|
|
26
|
+
// The rate limiter counts nothing it cannot attribute, and says so once.
|
|
27
|
+
warnedNoClientAddress = false;
|
|
23
28
|
// Middleware from registerMiddleware(). It runs through one dispatcher that
|
|
24
29
|
// is mounted ahead of the router: app.use() after the router is mounted
|
|
25
30
|
// would only ever see requests no route matched.
|
|
@@ -72,12 +77,15 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
72
77
|
});
|
|
73
78
|
const adapterError = error instanceof ServerAdapterError ? error : undefined;
|
|
74
79
|
const statusCode = adapterError?.getHttpStatus() || err.status || err.statusCode || 500;
|
|
80
|
+
// A 4xx carries a message this server or Koa composed; any 5xx's
|
|
81
|
+
// text stays in the log.
|
|
82
|
+
const serverSide = statusCode >= 500;
|
|
75
83
|
ctx.status = statusCode;
|
|
76
84
|
ctx.body = {
|
|
77
85
|
error: {
|
|
78
86
|
code: adapterError?.code ??
|
|
79
|
-
(
|
|
80
|
-
message:
|
|
87
|
+
(serverSide ? "INTERNAL_ERROR" : `HTTP_${statusCode}`),
|
|
88
|
+
message: serverSide ? "An internal error occurred" : err.message,
|
|
81
89
|
},
|
|
82
90
|
metadata: {
|
|
83
91
|
requestId,
|
|
@@ -92,10 +100,13 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
92
100
|
const cors = corsModule.default;
|
|
93
101
|
this.app.use(cors({
|
|
94
102
|
origin: (ctx) => {
|
|
95
|
-
|
|
103
|
+
// The wildcard is sent as the literal "*": reflecting the
|
|
104
|
+
// request's Origin instead would grant any page a credentialed
|
|
105
|
+
// allowance the wildcard itself cannot carry.
|
|
96
106
|
if (this.config.cors.origins.includes("*")) {
|
|
97
|
-
return
|
|
107
|
+
return "*";
|
|
98
108
|
}
|
|
109
|
+
const origin = ctx.request.headers.origin ?? "";
|
|
99
110
|
return this.config.cors.origins.includes(origin) ? origin : "";
|
|
100
111
|
},
|
|
101
112
|
allowMethods: this.config.cors.methods?.join(","),
|
|
@@ -165,18 +176,38 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
165
176
|
createRateLimiter() {
|
|
166
177
|
return async (ctx, next) => {
|
|
167
178
|
// Skip rate limiting for excluded paths
|
|
168
|
-
if (this.config.rateLimit.skipPaths) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
if (pathMatchesAny(ctx.path, this.config.rateLimit.skipPaths)) {
|
|
180
|
+
await next();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// The TCP peer, or behind a declared proxy the client it reports. A
|
|
184
|
+
// header alone used to be the key, so a rotating X-Forwarded-For escaped
|
|
185
|
+
// the limit; a request with no attributable address is let through
|
|
186
|
+
// rather than pooled with every other such request.
|
|
187
|
+
const key = this.config.rateLimit.keyGenerator
|
|
188
|
+
? this.config.rateLimit.keyGenerator(this.createContext({
|
|
189
|
+
requestId: ctx.state.requestId,
|
|
190
|
+
method: ctx.method,
|
|
191
|
+
path: ctx.path,
|
|
192
|
+
headers: ctx.headers,
|
|
193
|
+
query: ctx.query,
|
|
194
|
+
params: {},
|
|
195
|
+
remoteAddress: ctx.req.socket?.remoteAddress,
|
|
196
|
+
}))
|
|
197
|
+
: resolveClientAddress({
|
|
198
|
+
remoteAddress: ctx.req.socket?.remoteAddress,
|
|
199
|
+
forwardedFor: ctx.get("x-forwarded-for") || undefined,
|
|
200
|
+
trustProxy: this.config.rateLimit.trustProxy,
|
|
201
|
+
trustedProxies: this.config.rateLimit.trustedProxies,
|
|
202
|
+
});
|
|
203
|
+
if (key === undefined) {
|
|
204
|
+
if (!this.warnedNoClientAddress) {
|
|
205
|
+
this.warnedNoClientAddress = true;
|
|
206
|
+
logger.alwaysStderr("[KoaAdapter] A request arrived with no peer address, so the rate limiter cannot count it; configure rateLimit.keyGenerator to limit by another key.");
|
|
174
207
|
}
|
|
208
|
+
await next();
|
|
209
|
+
return;
|
|
175
210
|
}
|
|
176
|
-
const key = ctx.get("x-forwarded-for") ||
|
|
177
|
-
ctx.get("x-real-ip") ||
|
|
178
|
-
ctx.ip ||
|
|
179
|
-
"unknown";
|
|
180
211
|
const now = Date.now();
|
|
181
212
|
const windowMs = this.config.rateLimit.windowMs;
|
|
182
213
|
const maxRequests = this.config.rateLimit.maxRequests;
|
|
@@ -256,12 +287,24 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
256
287
|
query: ctx.query,
|
|
257
288
|
params: ctx.params,
|
|
258
289
|
body: ctx.request.body,
|
|
290
|
+
remoteAddress: ctx.req.socket?.remoteAddress,
|
|
259
291
|
});
|
|
260
292
|
// Copy response headers from middleware (stored in ctx.state by middleware)
|
|
261
293
|
if (ctx.state.responseHeaders) {
|
|
262
294
|
serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
|
|
263
295
|
}
|
|
264
296
|
this.restoreCarriedContext(serverCtx, ctx.state.carriedContext);
|
|
297
|
+
// The handler learns of a client that went away, and a request over
|
|
298
|
+
// `config.timeout` is answered 504 and its work aborted. Koa applied no
|
|
299
|
+
// request timeout at all.
|
|
300
|
+
const abort = this.attachRequestAbort(serverCtx, {
|
|
301
|
+
onClientClose: (listener) => ctx.req.on("close", () => {
|
|
302
|
+
if (!ctx.res.writableFinished) {
|
|
303
|
+
listener();
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
applyTimeout: !route.streaming?.enabled,
|
|
307
|
+
});
|
|
265
308
|
// Emit request event
|
|
266
309
|
this.emit("request", {
|
|
267
310
|
requestId,
|
|
@@ -269,39 +312,68 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
269
312
|
path: serverCtx.path,
|
|
270
313
|
timestamp: new Date(),
|
|
271
314
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// Execute handler
|
|
277
|
-
const result = await route.handler(serverCtx);
|
|
278
|
-
const duration = Date.now() - startTime;
|
|
279
|
-
// A route that answers in its own wire format returns a web Response.
|
|
280
|
-
if (isWebResponse(result)) {
|
|
281
|
-
const raw = await readWebResponse(result);
|
|
282
|
-
this.emit("response", {
|
|
283
|
-
requestId,
|
|
284
|
-
statusCode: raw.status,
|
|
285
|
-
duration,
|
|
286
|
-
timestamp: new Date(),
|
|
287
|
-
});
|
|
288
|
-
for (const [key, value] of Object.entries({
|
|
289
|
-
...serverCtx.responseHeaders,
|
|
290
|
-
...raw.headers,
|
|
291
|
-
})) {
|
|
292
|
-
ctx.set(key, value);
|
|
315
|
+
try {
|
|
316
|
+
// Handle streaming if configured
|
|
317
|
+
if (route.streaming?.enabled) {
|
|
318
|
+
return await this.handleStreamingResponse(ctx, serverCtx, route);
|
|
293
319
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
320
|
+
// Execute handler
|
|
321
|
+
const result = abort.deadline
|
|
322
|
+
? await Promise.race([route.handler(serverCtx), abort.deadline])
|
|
323
|
+
: await route.handler(serverCtx);
|
|
324
|
+
const duration = Date.now() - startTime;
|
|
325
|
+
// A route that answers in its own wire format returns a web Response.
|
|
326
|
+
if (isWebResponse(result)) {
|
|
327
|
+
const raw = await readWebResponse(result);
|
|
328
|
+
this.emit("response", {
|
|
329
|
+
requestId,
|
|
330
|
+
statusCode: raw.status,
|
|
331
|
+
duration,
|
|
332
|
+
timestamp: new Date(),
|
|
333
|
+
});
|
|
334
|
+
for (const [key, value] of Object.entries({
|
|
335
|
+
...serverCtx.responseHeaders,
|
|
336
|
+
...raw.headers,
|
|
337
|
+
})) {
|
|
338
|
+
ctx.set(key, value);
|
|
339
|
+
}
|
|
340
|
+
ctx.status = raw.status;
|
|
341
|
+
ctx.body = raw.body;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Check if result is an error response
|
|
345
|
+
if (isErrorResponse(result)) {
|
|
346
|
+
const statusCode = result.httpStatus ?? 500;
|
|
347
|
+
// Emit response event with error status
|
|
348
|
+
this.emit("response", {
|
|
349
|
+
requestId,
|
|
350
|
+
statusCode,
|
|
351
|
+
duration,
|
|
352
|
+
timestamp: new Date(),
|
|
353
|
+
});
|
|
354
|
+
// Apply response headers from middleware and handler
|
|
355
|
+
if (serverCtx.responseHeaders) {
|
|
356
|
+
for (const [key, value] of Object.entries(serverCtx.responseHeaders)) {
|
|
357
|
+
ctx.set(key, value);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Return error response with proper status code
|
|
361
|
+
ctx.status = statusCode;
|
|
362
|
+
ctx.body = {
|
|
363
|
+
error: result.error,
|
|
364
|
+
metadata: {
|
|
365
|
+
...result.metadata,
|
|
366
|
+
requestId,
|
|
367
|
+
timestamp: new Date().toISOString(),
|
|
368
|
+
duration,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
// Emit response event
|
|
302
374
|
this.emit("response", {
|
|
303
375
|
requestId,
|
|
304
|
-
statusCode,
|
|
376
|
+
statusCode: 200,
|
|
305
377
|
duration,
|
|
306
378
|
timestamp: new Date(),
|
|
307
379
|
});
|
|
@@ -311,41 +383,44 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
311
383
|
ctx.set(key, value);
|
|
312
384
|
}
|
|
313
385
|
}
|
|
314
|
-
// Return
|
|
315
|
-
ctx.status = statusCode;
|
|
386
|
+
// Return formatted response
|
|
316
387
|
ctx.body = {
|
|
317
|
-
|
|
388
|
+
data: result,
|
|
318
389
|
metadata: {
|
|
319
|
-
...result.metadata,
|
|
320
390
|
requestId,
|
|
321
391
|
timestamp: new Date().toISOString(),
|
|
322
392
|
duration,
|
|
323
393
|
},
|
|
324
394
|
};
|
|
325
|
-
return;
|
|
326
395
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
396
|
+
catch (error) {
|
|
397
|
+
// The exception's text stays in the log under this request id; the
|
|
398
|
+
// client gets a fixed message for its category.
|
|
399
|
+
const shaped = shapeRouteError("KoaAdapter", requestId, error);
|
|
400
|
+
this.emitRequestError({
|
|
401
|
+
requestId,
|
|
402
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
403
|
+
timestamp: new Date(),
|
|
404
|
+
});
|
|
405
|
+
this.emit("response", {
|
|
406
|
+
requestId,
|
|
407
|
+
statusCode: shaped.status,
|
|
408
|
+
duration: Date.now() - startTime,
|
|
409
|
+
timestamp: new Date(),
|
|
410
|
+
});
|
|
411
|
+
if (ctx.res.headersSent) {
|
|
412
|
+
ctx.res.end();
|
|
413
|
+
return;
|
|
338
414
|
}
|
|
415
|
+
ctx.status = shaped.status;
|
|
416
|
+
ctx.body = {
|
|
417
|
+
error: { code: shaped.code, message: shaped.message },
|
|
418
|
+
metadata: { requestId, timestamp: new Date().toISOString() },
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
abort.release();
|
|
339
423
|
}
|
|
340
|
-
// Return formatted response
|
|
341
|
-
ctx.body = {
|
|
342
|
-
data: result,
|
|
343
|
-
metadata: {
|
|
344
|
-
requestId,
|
|
345
|
-
timestamp: new Date().toISOString(),
|
|
346
|
-
duration,
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
424
|
});
|
|
350
425
|
}
|
|
351
426
|
/**
|
|
@@ -382,9 +457,13 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
382
457
|
stream.end();
|
|
383
458
|
}
|
|
384
459
|
catch (error) {
|
|
460
|
+
const shaped = shapeRouteError("KoaAdapter", serverCtx.requestId, error);
|
|
385
461
|
stream.write(`event: error\n`);
|
|
386
462
|
stream.write(`data: ${JSON.stringify({
|
|
387
|
-
error:
|
|
463
|
+
error: shaped.message,
|
|
464
|
+
code: shaped.code,
|
|
465
|
+
status: shaped.status,
|
|
466
|
+
requestId: serverCtx.requestId,
|
|
388
467
|
})}\n\n`);
|
|
389
468
|
stream.end();
|
|
390
469
|
}
|
|
@@ -395,13 +474,11 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
395
474
|
registerFrameworkMiddleware(middleware) {
|
|
396
475
|
this.registeredMiddleware.push(async (ctx, next) => {
|
|
397
476
|
// Skip excluded paths
|
|
398
|
-
if (
|
|
477
|
+
if (pathMatchesAny(ctx.path, middleware.excludePaths)) {
|
|
399
478
|
return next();
|
|
400
479
|
}
|
|
401
480
|
// Check if path matches
|
|
402
|
-
|
|
403
|
-
const matches = paths.some((p) => ctx.path.startsWith(p) || p === "*");
|
|
404
|
-
if (!matches) {
|
|
481
|
+
if (!pathMatchesAny(ctx.path, middleware.paths || ["/"])) {
|
|
405
482
|
return next();
|
|
406
483
|
}
|
|
407
484
|
// Initialize response headers storage in ctx.state if not present
|
|
@@ -417,6 +494,7 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
417
494
|
query: ctx.query,
|
|
418
495
|
params: ctx.params,
|
|
419
496
|
body: ctx.request.body,
|
|
497
|
+
remoteAddress: ctx.req.socket?.remoteAddress,
|
|
420
498
|
});
|
|
421
499
|
// Copy existing response headers to context
|
|
422
500
|
serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
|
|
@@ -470,6 +548,7 @@ export class KoaServerAdapter extends BaseServerAdapter {
|
|
|
470
548
|
this.isRunning = true;
|
|
471
549
|
this.startTime = new Date();
|
|
472
550
|
this.lifecycleState = "running";
|
|
551
|
+
this.warnIfSensitiveRoutesUnauthenticated();
|
|
473
552
|
// Track connections for graceful shutdown
|
|
474
553
|
this.server?.on("connection", (socket) => {
|
|
475
554
|
const connectionId = `conn-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export { createAgentRoutes, createAllRoutes, createHealthRoutes, createMCPRoutes
|
|
|
42
42
|
export { BaseDataStreamWriter, createDataStreamResponse, createDataStreamWriter, createNDJSONHeaders, createSSEHeaders, DataStreamResponse, formatSSEEvent, pipeAsyncIterableToDataStream, WebStreamWriter, } from "./streaming/index.js";
|
|
43
43
|
export { ErrorCategory, ErrorSeverity, ServerAdapterErrorCode, } from "../types/index.js";
|
|
44
44
|
export { createStreamRedactor, redactStreamChunk } from "./utils/redaction.js";
|
|
45
|
+
export { isLoopbackAddress, resolveClientAddress, } from "./utils/clientAddress.js";
|
|
46
|
+
export { clientFacingError } from "./utils/clientError.js";
|
|
45
47
|
export { AgentExecuteRequestSchema, createErrorResponse, ServerNameParamSchema, SessionIdParamSchema, ToolArgumentsSchema, ToolExecuteRequestSchema, ToolNameParamSchema, ToolSearchQuerySchema, TranscribeConfigSchema, TranscribeRequestSchema, TranscribeStreamConfigSchema, validateParams, validateQuery, validateRequest, } from "./utils/validation.js";
|
|
46
48
|
export { createAgentWebSocketHandler, WebSocketConnectionManager, WebSocketMessageRouter, } from "./websocket/index.js";
|
|
47
49
|
export { attachTranscribeWebSocket } from "./websocket/transcribeWebSocket.js";
|
package/dist/server/index.js
CHANGED
|
@@ -117,6 +117,10 @@ pipeAsyncIterableToDataStream, WebStreamWriter, } from "./streaming/index.js";
|
|
|
117
117
|
// Export error constants
|
|
118
118
|
export { ErrorCategory, ErrorSeverity, ServerAdapterErrorCode, } from "../types/index.js";
|
|
119
119
|
export { createStreamRedactor, redactStreamChunk } from "./utils/redaction.js";
|
|
120
|
+
// The address a request is counted against (for a custom rate-limit
|
|
121
|
+
// keyGenerator behind a reverse proxy) and the loopback test the adapters use.
|
|
122
|
+
export { isLoopbackAddress, resolveClientAddress, } from "./utils/clientAddress.js";
|
|
123
|
+
export { clientFacingError } from "./utils/clientError.js";
|
|
120
124
|
// ============================================
|
|
121
125
|
// Validation Utilities
|
|
122
126
|
// ============================================
|
|
@@ -65,6 +65,7 @@ export function createAuthMiddleware(config) {
|
|
|
65
65
|
name: "authentication",
|
|
66
66
|
order: 10, // Run early but after request ID
|
|
67
67
|
excludePaths: skipPaths,
|
|
68
|
+
authenticates: true,
|
|
68
69
|
handler: async (ctx, next) => {
|
|
69
70
|
// The playground header is a development convenience, so it is honoured
|
|
70
71
|
// only when NODE_ENV is exactly "development". An unset NODE_ENV (the
|
|
@@ -286,12 +287,15 @@ export function createBearerAuthMiddleware(validate, options = {}) {
|
|
|
286
287
|
name: "bearer-auth",
|
|
287
288
|
order: 10,
|
|
288
289
|
excludePaths: skipPaths,
|
|
290
|
+
authenticates: true,
|
|
289
291
|
handler: async (ctx, next) => {
|
|
290
292
|
const authHeader = ctx.headers[headerName.toLowerCase()];
|
|
291
|
-
// Extract bearer token
|
|
293
|
+
// Extract bearer token. The token must open with a non-whitespace
|
|
294
|
+
// character, as in extractToken(): with `\s+(.+)` a header that failed
|
|
295
|
+
// to match was retried at every split, quadratic in its length.
|
|
292
296
|
let token = null;
|
|
293
297
|
if (authHeader) {
|
|
294
|
-
const match = authHeader.match(/^Bearer\s+(
|
|
298
|
+
const match = authHeader.match(/^Bearer\s+(\S.*)$/i);
|
|
295
299
|
token = match ? match[1] : null;
|
|
296
300
|
}
|
|
297
301
|
// Handle missing token
|
|
@@ -136,10 +136,15 @@ export function createCacheMiddleware(config) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
* Default cache key generator
|
|
139
|
+
* Default cache key generator. An authenticated user's id is part of the key,
|
|
140
|
+
* so one caller's cached response is never served to another; without a user
|
|
141
|
+
* the key is the request alone, as before.
|
|
140
142
|
*/
|
|
141
143
|
function defaultKeyGenerator(ctx) {
|
|
142
144
|
const parts = [ctx.method, ctx.path];
|
|
145
|
+
if (ctx.user?.id) {
|
|
146
|
+
parts.push(`user=${ctx.user.id}`);
|
|
147
|
+
}
|
|
143
148
|
// Include sorted query params
|
|
144
149
|
const queryKeys = Object.keys(ctx.query).sort();
|
|
145
150
|
if (queryKeys.length > 0) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Rate Limiting Middleware
|
|
3
3
|
* Provides configurable rate limiting for server adapters
|
|
4
4
|
*/
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
5
6
|
import { RateLimitError as ServerRateLimitError } from "../errors.js";
|
|
6
7
|
/**
|
|
7
8
|
* In-memory rate limit store
|
|
@@ -86,6 +87,9 @@ export function createRateLimitMiddleware(config) {
|
|
|
86
87
|
excludePaths: skipPaths,
|
|
87
88
|
handler: async (ctx, next) => {
|
|
88
89
|
const key = keyGenerator(ctx);
|
|
90
|
+
if (!key) {
|
|
91
|
+
return next(); // Nothing to attribute the request to: see defaultKeyGenerator.
|
|
92
|
+
}
|
|
89
93
|
const { count, resetAt } = await store.increment(key, windowMs);
|
|
90
94
|
// Set rate limit headers in responseHeaders (adapters read from here)
|
|
91
95
|
ctx.responseHeaders = ctx.responseHeaders || {};
|
|
@@ -107,12 +111,27 @@ export function createRateLimitMiddleware(config) {
|
|
|
107
111
|
};
|
|
108
112
|
}
|
|
109
113
|
/**
|
|
110
|
-
* Default key
|
|
114
|
+
* Default key: the TCP peer the adapter recorded on the context. Never a
|
|
115
|
+
* header, which the client writes: keyed on `X-Forwarded-For`, one shared
|
|
116
|
+
* bucket served every direct client and a rotating header escaped the limit.
|
|
117
|
+
* Behind a reverse proxy, pass a `keyGenerator` that reads the proxy's header
|
|
118
|
+
* (see `resolveClientAddress`). An empty key means "do not count".
|
|
111
119
|
*/
|
|
112
120
|
function defaultKeyGenerator(ctx) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
const address = ctx.remoteAddress?.trim();
|
|
122
|
+
if (!address) {
|
|
123
|
+
warnNoAddressOnce();
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
return address;
|
|
127
|
+
}
|
|
128
|
+
let warnedNoAddress = false;
|
|
129
|
+
function warnNoAddressOnce() {
|
|
130
|
+
if (warnedNoAddress) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
warnedNoAddress = true;
|
|
134
|
+
logger.alwaysStderr("[rateLimit] A request carried no peer address (ctx.remoteAddress), so the rate limiter let it through uncounted; pass a keyGenerator to limit by another key.");
|
|
116
135
|
}
|
|
117
136
|
/**
|
|
118
137
|
* Create a sliding window rate limiter
|
|
@@ -142,6 +161,9 @@ export function createSlidingWindowRateLimitMiddleware(config) {
|
|
|
142
161
|
excludePaths: skipPaths,
|
|
143
162
|
handler: async (ctx, next) => {
|
|
144
163
|
const key = keyGenerator(ctx);
|
|
164
|
+
if (!key) {
|
|
165
|
+
return next();
|
|
166
|
+
}
|
|
145
167
|
const now = Date.now();
|
|
146
168
|
// Get existing timestamps and filter old ones
|
|
147
169
|
let timestamps = store.get(key) || [];
|
|
@@ -201,6 +223,9 @@ export function createFixedWindowRateLimitMiddleware(config, store) {
|
|
|
201
223
|
excludePaths: skipPaths,
|
|
202
224
|
handler: async (ctx, next) => {
|
|
203
225
|
const key = keyGenerator(ctx);
|
|
226
|
+
if (!key) {
|
|
227
|
+
return next();
|
|
228
|
+
}
|
|
204
229
|
const { count, resetAt } = await rateLimitStore.increment(key, windowMs);
|
|
205
230
|
// Set rate limit headers in responseHeaders (adapters read from here)
|
|
206
231
|
ctx.responseHeaders = ctx.responseHeaders || {};
|
|
@@ -29,7 +29,13 @@ async function resolveRequestToolFilter(ctx, requestedTools) {
|
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
import { AgentInputError, resolveAgentInput } from "../utils/agentInput.js";
|
|
32
|
+
import { shapeRouteError } from "../utils/clientError.js";
|
|
32
33
|
import { createStreamRedactor } from "../utils/redaction.js";
|
|
34
|
+
/** The route's error response for an exception: a fixed message, the detail logged. */
|
|
35
|
+
function agentFailure(ctx, error, code = "EXECUTION_FAILED") {
|
|
36
|
+
const shaped = shapeRouteError("agentRoutes", ctx.requestId, error);
|
|
37
|
+
return createError(shaped.status === 500 ? code : shaped.code, shaped.message, undefined, ctx.requestId, shaped.status);
|
|
38
|
+
}
|
|
33
39
|
import { AgentExecuteRequestSchema, createErrorResponse as createError, EmbedManyRequestSchema, EmbedRequestSchema, SkillCreateRequestSchema, SkillUpdateRequestSchema, validateRequest, } from "../utils/validation.js";
|
|
34
40
|
/**
|
|
35
41
|
* Resolve the skills manager from the server's NeuroLink instance, or a
|
|
@@ -244,6 +250,9 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
244
250
|
return input;
|
|
245
251
|
}
|
|
246
252
|
const requestToolFilter = await resolveRequestToolFilter(ctx, request.tools);
|
|
253
|
+
// The adapter's signal: the client went away, or the request
|
|
254
|
+
// ran past the server's timeout. Either stops the generation
|
|
255
|
+
// instead of leaving it running for nobody.
|
|
247
256
|
const result = await ctx.neurolink.generate({
|
|
248
257
|
input,
|
|
249
258
|
provider: request.provider,
|
|
@@ -252,6 +261,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
252
261
|
temperature: request.temperature,
|
|
253
262
|
maxTokens: request.maxTokens,
|
|
254
263
|
...(requestToolFilter ? { toolFilter: requestToolFilter } : {}),
|
|
264
|
+
...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
|
|
255
265
|
context: callerContext(ctx, request),
|
|
256
266
|
});
|
|
257
267
|
// Map tool calls from SDK format to API format
|
|
@@ -295,6 +305,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
295
305
|
temperature: request.temperature,
|
|
296
306
|
maxTokens: request.maxTokens,
|
|
297
307
|
...(streamToolFilter ? { toolFilter: streamToolFilter } : {}),
|
|
308
|
+
...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
|
|
298
309
|
context: callerContext(ctx, request),
|
|
299
310
|
});
|
|
300
311
|
// Create redactor (no-op if redaction is not enabled)
|
|
@@ -377,9 +388,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
377
388
|
});
|
|
378
389
|
}
|
|
379
390
|
catch (error) {
|
|
380
|
-
return
|
|
381
|
-
? error.message
|
|
382
|
-
: "Embedding generation failed", undefined, ctx.requestId);
|
|
391
|
+
return agentFailure(ctx, error);
|
|
383
392
|
}
|
|
384
393
|
},
|
|
385
394
|
description: "Generate embedding for a single text",
|
|
@@ -418,9 +427,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
418
427
|
});
|
|
419
428
|
}
|
|
420
429
|
catch (error) {
|
|
421
|
-
return
|
|
422
|
-
? error.message
|
|
423
|
-
: "Batch embedding generation failed", undefined, ctx.requestId);
|
|
430
|
+
return agentFailure(ctx, error);
|
|
424
431
|
}
|
|
425
432
|
},
|
|
426
433
|
description: "Generate embeddings for multiple texts in a batch",
|
|
@@ -439,7 +446,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
439
446
|
return { skills, count: skills.length };
|
|
440
447
|
}
|
|
441
448
|
catch (error) {
|
|
442
|
-
return
|
|
449
|
+
return agentFailure(ctx, error);
|
|
443
450
|
}
|
|
444
451
|
},
|
|
445
452
|
description: "List active skills (index only — no instructions). Optional ?scopeId= filter.",
|
|
@@ -461,7 +468,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
461
468
|
return skill;
|
|
462
469
|
}
|
|
463
470
|
catch (error) {
|
|
464
|
-
return
|
|
471
|
+
return agentFailure(ctx, error);
|
|
465
472
|
}
|
|
466
473
|
},
|
|
467
474
|
description: "Fetch one skill (by id or exact name) with instructions",
|
|
@@ -490,7 +497,7 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
490
497
|
return result;
|
|
491
498
|
}
|
|
492
499
|
catch (error) {
|
|
493
|
-
return
|
|
500
|
+
return agentFailure(ctx, error);
|
|
494
501
|
}
|
|
495
502
|
},
|
|
496
503
|
description: "Create a skill (routed through the host's onMutationRequest gate when configured)",
|
|
@@ -520,8 +527,12 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
520
527
|
return result;
|
|
521
528
|
}
|
|
522
529
|
catch (error) {
|
|
530
|
+
// "not found" is this server's own wording for an unknown id, so
|
|
531
|
+
// it may be said; anything else is reduced to its category.
|
|
523
532
|
const message = error instanceof Error ? error.message : "Skill update failed";
|
|
524
|
-
return
|
|
533
|
+
return message.includes("not found")
|
|
534
|
+
? createError("NOT_FOUND", `Skill "${ctx.params.id}" not found`, undefined, ctx.requestId)
|
|
535
|
+
: agentFailure(ctx, error);
|
|
525
536
|
}
|
|
526
537
|
},
|
|
527
538
|
description: "Update a skill (patch semantics; version is bumped)",
|
|
@@ -546,7 +557,9 @@ export function createAgentRoutes(basePath = "/api", options) {
|
|
|
546
557
|
}
|
|
547
558
|
catch (error) {
|
|
548
559
|
const message = error instanceof Error ? error.message : "Skill delete failed";
|
|
549
|
-
return
|
|
560
|
+
return message.includes("not found")
|
|
561
|
+
? createError("NOT_FOUND", `Skill "${ctx.params.id}" not found`, undefined, ctx.requestId)
|
|
562
|
+
: agentFailure(ctx, error);
|
|
550
563
|
}
|
|
551
564
|
},
|
|
552
565
|
description: "Soft-delete (deprecate) a skill",
|
|
@@ -385,6 +385,7 @@ declare function handleAnthropicNonOkResponse(args: {
|
|
|
385
385
|
} | null;
|
|
386
386
|
entitlementFailure: AnthropicEntitlementFailure | null;
|
|
387
387
|
allowConfiguredModelFallback?: boolean;
|
|
388
|
+
clientSuppliedBetaForwarded?: boolean;
|
|
388
389
|
}): Promise<AnthropicNonOkResult>;
|
|
389
390
|
/**
|
|
390
391
|
* Detect Anthropic's anti-abuse / request-construction 429.
|