@juspay/neurolink 13.8.14 → 13.8.15

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.
@@ -16,6 +16,10 @@ export declare function readServerApiKeys(): string[];
16
16
  * Require one of `apiKeys` as `Authorization: Bearer <key>` or `X-API-Key`.
17
17
  * Health probes stay open so orchestrators can reach them, and the dev
18
18
  * playground header cannot bypass a key the operator configured.
19
+ *
20
+ * Every holder of a key gets the same `{ id: "api-key" }`, marked `shared` so
21
+ * the agent routes know it names no person and do not warn when a request
22
+ * names its own userId.
19
23
  */
20
24
  export declare function createServerApiKeyMiddleware(apiKeys: string[], basePath: string): MiddlewareDefinition;
21
25
  /**
@@ -41,6 +41,10 @@ const matchesKey = (key, token) => key.length === token.length && timingSafeEqua
41
41
  * Require one of `apiKeys` as `Authorization: Bearer <key>` or `X-API-Key`.
42
42
  * Health probes stay open so orchestrators can reach them, and the dev
43
43
  * playground header cannot bypass a key the operator configured.
44
+ *
45
+ * Every holder of a key gets the same `{ id: "api-key" }`, marked `shared` so
46
+ * the agent routes know it names no person and do not warn when a request
47
+ * names its own userId.
44
48
  */
45
49
  export function createServerApiKeyMiddleware(apiKeys, basePath) {
46
50
  const keys = apiKeys.map((key) => Buffer.from(key, "utf8"));
@@ -53,7 +57,7 @@ export function createServerApiKeyMiddleware(apiKeys, basePath) {
53
57
  validate: async (token) => {
54
58
  const candidate = Buffer.from(token, "utf8");
55
59
  return keys.some((key) => matchesKey(key, candidate))
56
- ? { id: "api-key" }
60
+ ? { id: "api-key", shared: true }
57
61
  : null;
58
62
  },
59
63
  skipPaths: [`${basePath.replace(/\/+$/, "")}/health`],
@@ -7,7 +7,7 @@ import { EventEmitter } from "events";
7
7
  import type { ExternalServerManager } from "../../mcp/externalServerManager.js";
8
8
  import type { MCPToolRegistry } from "../../mcp/toolRegistry.js";
9
9
  import type { NeuroLink } from "../../neurolink.js";
10
- import type { MiddlewareDefinition, RedactionConfig, RequiredServerAdapterConfig, RequiredShutdownConfig, RouteDefinition, ServerAdapterConfig, ServerAdapterEvents, ServerContext, ServerLifecycleState, ServerStatus, TrackedConnection } from "../../types/index.js";
10
+ import type { MiddlewareDefinition, RedactionConfig, RequiredServerAdapterConfig, RequiredShutdownConfig, RouteDefinition, ServerAdapterConfig, ServerAdapterEvents, ServerCarriedContext, ServerContext, ServerLifecycleState, ServerStatus, TrackedConnection } from "../../types/index.js";
11
11
  /**
12
12
  * Abstract base class for server adapters
13
13
  * Provides common functionality and defines the interface for framework-specific implementations
@@ -28,6 +28,7 @@ export declare abstract class BaseServerAdapter extends EventEmitter {
28
28
  protected lifecycleState: ServerLifecycleState;
29
29
  protected activeConnections: Map<string, TrackedConnection>;
30
30
  protected readonly shutdownConfig: RequiredShutdownConfig;
31
+ private carryRequestedByRoutes;
31
32
  constructor(neurolink: NeuroLink, config?: ServerAdapterConfig);
32
33
  /**
33
34
  * Initialize the underlying server framework
@@ -155,6 +156,34 @@ export declare abstract class BaseServerAdapter extends EventEmitter {
155
156
  * Only registers health routes if disableBuiltInHealth is false (default)
156
157
  */
157
158
  protected registerBuiltInRoutes(): Promise<void>;
159
+ /**
160
+ * Ask the adapter to carry middleware state (`ctx.user` and the rest) to later
161
+ * middleware and the route handler, as the `carryMiddlewareState` option does.
162
+ * `registerAllRoutes()` calls it when given `perUserAuth`, which cannot work
163
+ * without the user in the handler. It takes effect for requests that start
164
+ * after the call, so call it before the server starts, as registering routes
165
+ * is.
166
+ */
167
+ enableMiddlewareStateCarry(): void;
168
+ /** Whether this adapter carries middleware state to the handler. */
169
+ protected carriesMiddlewareState(): boolean;
170
+ /**
171
+ * What middleware has set on its context that later middleware and the route
172
+ * handler must see. Each step gets a freshly built context, so unless this is
173
+ * parked in the framework's per-request storage an authenticated `ctx.user`
174
+ * set by middleware is gone before the handler runs. `undefined` when the
175
+ * adapter does not carry middleware state, so nothing is parked.
176
+ */
177
+ protected captureCarriedContext(ctx: ServerContext): ServerCarriedContext | undefined;
178
+ /**
179
+ * Put what `captureCarriedContext` saved onto a freshly built context, and
180
+ * mark the context as one that received the carry. The agent routes read the
181
+ * mark: a request that never used to reach its handler with `ctx.user` must
182
+ * not start being identified by it unless the server opts in. Does nothing
183
+ * when the adapter does not carry middleware state: the context stays the
184
+ * fresh one it always was, and unmarked.
185
+ */
186
+ protected restoreCarriedContext(ctx: ServerContext, carried: ServerCarriedContext | undefined): void;
158
187
  /**
159
188
  * Generate unique request ID
160
189
  */
@@ -7,6 +7,7 @@ import { EventEmitter } from "events";
7
7
  import { getMetricsAggregator, SpanSerializer, SpanStatus, SpanType, } from "../../observability/index.js";
8
8
  import { withTimeout } from "../../utils/errorHandling.js";
9
9
  import { logger } from "../../utils/logger.js";
10
+ import { markContextCarried } from "../utils/carriedContextMark.js";
10
11
  import { tryImport } from "../../utils/tryImport.js";
11
12
  import { DrainTimeoutError, InvalidLifecycleStateError, MissingDependencyError, ServerAdapterError, ShutdownTimeoutError, } from "../errors.js";
12
13
  /**
@@ -33,6 +34,9 @@ export class BaseServerAdapter extends EventEmitter {
33
34
  lifecycleState = "uninitialized";
34
35
  activeConnections = new Map();
35
36
  shutdownConfig;
37
+ // Set by enableMiddlewareStateCarry(); the carryMiddlewareState option is the
38
+ // other way in.
39
+ carryRequestedByRoutes = false;
36
40
  constructor(neurolink, config = {}) {
37
41
  super();
38
42
  this.neurolink = neurolink;
@@ -101,6 +105,7 @@ export class BaseServerAdapter extends EventEmitter {
101
105
  enableSwagger: config.enableSwagger ?? false,
102
106
  disableBuiltInHealth: config.disableBuiltInHealth ?? false,
103
107
  shutdown: this.shutdownConfig,
108
+ carryMiddlewareState: config.carryMiddlewareState ?? false,
104
109
  };
105
110
  }
106
111
  /**
@@ -454,6 +459,72 @@ export class BaseServerAdapter extends EventEmitter {
454
459
  });
455
460
  }
456
461
  }
462
+ /**
463
+ * Ask the adapter to carry middleware state (`ctx.user` and the rest) to later
464
+ * middleware and the route handler, as the `carryMiddlewareState` option does.
465
+ * `registerAllRoutes()` calls it when given `perUserAuth`, which cannot work
466
+ * without the user in the handler. It takes effect for requests that start
467
+ * after the call, so call it before the server starts, as registering routes
468
+ * is.
469
+ */
470
+ enableMiddlewareStateCarry() {
471
+ this.carryRequestedByRoutes = true;
472
+ }
473
+ /** Whether this adapter carries middleware state to the handler. */
474
+ carriesMiddlewareState() {
475
+ return this.config.carryMiddlewareState || this.carryRequestedByRoutes;
476
+ }
477
+ /**
478
+ * What middleware has set on its context that later middleware and the route
479
+ * handler must see. Each step gets a freshly built context, so unless this is
480
+ * parked in the framework's per-request storage an authenticated `ctx.user`
481
+ * set by middleware is gone before the handler runs. `undefined` when the
482
+ * adapter does not carry middleware state, so nothing is parked.
483
+ */
484
+ captureCarriedContext(ctx) {
485
+ if (!this.carriesMiddlewareState()) {
486
+ return undefined;
487
+ }
488
+ return {
489
+ user: ctx.user,
490
+ session: ctx.session,
491
+ metadata: ctx.metadata,
492
+ abortSignal: ctx.abortSignal,
493
+ abortController: ctx.abortController,
494
+ };
495
+ }
496
+ /**
497
+ * Put what `captureCarriedContext` saved onto a freshly built context, and
498
+ * mark the context as one that received the carry. The agent routes read the
499
+ * mark: a request that never used to reach its handler with `ctx.user` must
500
+ * not start being identified by it unless the server opts in. Does nothing
501
+ * when the adapter does not carry middleware state: the context stays the
502
+ * fresh one it always was, and unmarked.
503
+ */
504
+ restoreCarriedContext(ctx, carried) {
505
+ if (!this.carriesMiddlewareState()) {
506
+ return;
507
+ }
508
+ markContextCarried(ctx);
509
+ if (!carried) {
510
+ return;
511
+ }
512
+ if (carried.user) {
513
+ ctx.user = carried.user;
514
+ }
515
+ if (carried.session) {
516
+ ctx.session = carried.session;
517
+ }
518
+ if (carried.metadata) {
519
+ ctx.metadata = { ...ctx.metadata, ...carried.metadata };
520
+ }
521
+ if (carried.abortSignal) {
522
+ ctx.abortSignal = carried.abortSignal;
523
+ }
524
+ if (carried.abortController) {
525
+ ctx.abortController = carried.abortController;
526
+ }
527
+ }
457
528
  /**
458
529
  * Generate unique request ID
459
530
  */
@@ -166,6 +166,7 @@ export class ExpressServerAdapter extends BaseServerAdapter {
166
166
  if (res.locals.responseHeaders) {
167
167
  ctx.responseHeaders = { ...res.locals.responseHeaders };
168
168
  }
169
+ this.restoreCarriedContext(ctx, res.locals.carriedContext);
169
170
  // Emit request event
170
171
  this.emit("request", {
171
172
  requestId,
@@ -366,9 +367,13 @@ export class ExpressServerAdapter extends BaseServerAdapter {
366
367
  });
367
368
  // Copy existing response headers to context
368
369
  ctx.responseHeaders = { ...res.locals.responseHeaders };
370
+ this.restoreCarriedContext(ctx, res.locals.carriedContext);
369
371
  // Execute middleware
370
372
  try {
371
373
  await middleware.handler(ctx, async () => {
374
+ // What this middleware set (ctx.user included) is for the next
375
+ // middleware and the route handler, which build their own contexts.
376
+ res.locals.carriedContext = this.captureCarriedContext(ctx);
372
377
  // After middleware execution, merge response headers back to res.locals
373
378
  if (ctx.responseHeaders) {
374
379
  Object.assign(res.locals.responseHeaders, ctx.responseHeaders);
@@ -13,6 +13,7 @@ import type { MiddlewareDefinition, RouteDefinition, ServerAdapterConfig } from
13
13
  export declare class FastifyServerAdapter extends BaseServerAdapter {
14
14
  private app;
15
15
  private frameworkInitialized;
16
+ private connectionIds;
16
17
  constructor(neurolink: NeuroLink, config?: ServerAdapterConfig);
17
18
  /**
18
19
  * Initialize Fastify framework
@@ -16,6 +16,11 @@ import { isRawBodyContentType, isWebResponse, readWebResponse, } from "../utils/
16
16
  export class FastifyServerAdapter extends BaseServerAdapter {
17
17
  app = null;
18
18
  frameworkInitialized = false;
19
+ // The shutdown drain's record for each in-flight request. Fastify takes
20
+ // `request.id` from the caller's X-Request-ID, so that id can repeat across
21
+ // overlapping requests: a record named after it would be shared, and the
22
+ // first request to finish would untrack the other.
23
+ connectionIds = new WeakMap();
19
24
  constructor(neurolink, config = {}) {
20
25
  super(neurolink, config);
21
26
  }
@@ -239,6 +244,7 @@ export class FastifyServerAdapter extends BaseServerAdapter {
239
244
  if (reqWithHeaders.responseHeaders) {
240
245
  ctx.responseHeaders = { ...reqWithHeaders.responseHeaders };
241
246
  }
247
+ this.restoreCarriedContext(ctx, reqWithHeaders.carriedContext);
242
248
  // Emit request event
243
249
  this.emit("request", {
244
250
  requestId,
@@ -399,8 +405,12 @@ export class FastifyServerAdapter extends BaseServerAdapter {
399
405
  });
400
406
  // Copy existing response headers to context
401
407
  ctx.responseHeaders = { ...reqWithHeaders.responseHeaders };
408
+ this.restoreCarriedContext(ctx, reqWithHeaders.carriedContext);
402
409
  // Execute middleware
403
410
  await middleware.handler(ctx, async () => {
411
+ // What this middleware set (ctx.user included) is for the next
412
+ // middleware and the route handler, which build their own contexts.
413
+ reqWithHeaders.carriedContext = this.captureCarriedContext(ctx);
404
414
  // After middleware execution, merge response headers back to request
405
415
  if (ctx.responseHeaders) {
406
416
  Object.assign(reqWithHeaders.responseHeaders, ctx.responseHeaders);
@@ -431,12 +441,15 @@ export class FastifyServerAdapter extends BaseServerAdapter {
431
441
  const startupTimeout = this.config.timeout || 30000;
432
442
  // Track connections via Fastify hooks (must be registered before listen)
433
443
  app.addHook("onRequest", async (request) => {
434
- const connectionId = `conn-${request.id}`;
444
+ const connectionId = `conn-${this.generateRequestId()}`;
445
+ this.connectionIds.set(request, connectionId);
435
446
  this.trackConnection(connectionId, request.raw.socket, request.id);
436
447
  });
437
448
  app.addHook("onResponse", async (request) => {
438
- const connectionId = `conn-${request.id}`;
439
- this.untrackConnection(connectionId);
449
+ const connectionId = this.connectionIds.get(request);
450
+ if (connectionId) {
451
+ this.untrackConnection(connectionId);
452
+ }
440
453
  });
441
454
  try {
442
455
  await withTimeout(app.listen({ port, host }), startupTimeout, new ServerStartError(`Fastify server startup timed out after ${startupTimeout}ms`, undefined, port, host));
@@ -16,6 +16,7 @@ export declare class HonoServerAdapter extends BaseServerAdapter {
16
16
  private rateLimitStore;
17
17
  private rateLimitCleanupInterval?;
18
18
  private requestContextStore;
19
+ private requestIds;
19
20
  private HTTPExceptionCtor;
20
21
  private streamSSEFn;
21
22
  private parseFormBody;
@@ -52,6 +53,11 @@ export declare class HonoServerAdapter extends BaseServerAdapter {
52
53
  * Register middleware with Hono
53
54
  */
54
55
  protected registerFrameworkMiddleware(middleware: MiddlewareDefinition): void;
56
+ /**
57
+ * The id of this request: the caller's `X-Request-ID` when sent, otherwise
58
+ * one generated on first use, and the same one on every later use.
59
+ */
60
+ private requestIdFor;
55
61
  /**
56
62
  * Start the Hono server
57
63
  */
@@ -6,6 +6,7 @@
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 { markContextCarried } from "../utils/carriedContextMark.js";
9
10
  import { isErrorResponse } from "../utils/validation.js";
10
11
  import { isRawBodyContentType, isWebResponse, parseByteLimit, RawBodyTooLargeError, readBoundedRequestBody, } from "../utils/rawBody.js";
11
12
  /**
@@ -17,8 +18,17 @@ export class HonoServerAdapter extends BaseServerAdapter {
17
18
  server;
18
19
  rateLimitStore = new Map();
19
20
  rateLimitCleanupInterval;
20
- // Store context by request ID for sharing between middleware and route handlers
21
- requestContextStore = new Map();
21
+ // Context shared between middleware and the route handler. Keyed by the request
22
+ // object, never by the request id: the id can be the
23
+ // caller's own X-Request-ID, so two overlapping requests that send the same one
24
+ // would share a context, and the second would run with the first one's
25
+ // identity, headers and body. A WeakMap also frees an entry whose handler
26
+ // never ran.
27
+ requestContextStore = new WeakMap();
28
+ // One id per request, for logs and events: the caller's X-Request-ID when sent,
29
+ // otherwise generated once, so every step reports the same id. With no header
30
+ // every step used to invent its own.
31
+ requestIds = new WeakMap();
22
32
  // Populated by initializeFramework's dynamic imports; used by methods that
23
33
  // run after initialization (route registration, streaming) and so cannot
24
34
  // rely on initializeFramework's own local closure variables.
@@ -50,7 +60,7 @@ export class HonoServerAdapter extends BaseServerAdapter {
50
60
  headers[name] = value;
51
61
  });
52
62
  const minCtx = {
53
- requestId: c.req.header("X-Request-ID") || this.generateRequestId(),
63
+ requestId: this.requestIdFor(c),
54
64
  method: c.req.method,
55
65
  path: c.req.path,
56
66
  headers,
@@ -162,7 +172,7 @@ export class HonoServerAdapter extends BaseServerAdapter {
162
172
  }
163
173
  // Global error handler
164
174
  this.app.onError((error, c) => {
165
- const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
175
+ const requestId = this.requestIdFor(c);
166
176
  this.logRequestError("HonoAdapter", requestId, error);
167
177
  this.emitRequestError({
168
178
  requestId,
@@ -217,8 +227,11 @@ export class HonoServerAdapter extends BaseServerAdapter {
217
227
  registerFrameworkRoute(route) {
218
228
  const method = route.method.toLowerCase();
219
229
  this.app[method](route.path, async (c) => {
220
- const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
221
- const connectionId = `conn-${requestId}`;
230
+ const requestId = this.requestIdFor(c);
231
+ // Not derived from the request id, which a caller may repeat: two
232
+ // overlapping requests would then share one record and the first to
233
+ // finish would untrack the other.
234
+ const connectionId = `conn-${this.generateRequestId()}`;
222
235
  const startTime = Date.now();
223
236
  // Track connection for graceful shutdown
224
237
  this.trackConnection(connectionId, undefined, requestId);
@@ -226,12 +239,30 @@ export class HonoServerAdapter extends BaseServerAdapter {
226
239
  // Extract path parameters
227
240
  const params = c.req.param();
228
241
  // Reuse existing context from middleware if available, otherwise create new one
229
- const created = this.requestContextStore.get(requestId) ||
230
- (await this.createRequestContext(c, requestId, params));
231
- if (isWebResponse(created)) {
232
- return created;
242
+ const existingCtx = this.requestContextStore.get(c.req.raw);
243
+ // Middleware ran before the router matched this route, so the shared
244
+ // context carries no path parameters yet.
245
+ if (existingCtx) {
246
+ existingCtx.params = params;
247
+ // The one request the handler already got the middleware's context
248
+ // for before this adapter carried it on every request: the caller
249
+ // sent its own X-Request-ID, which the old store was keyed by. That
250
+ // context stays unmarked, so the agent routes keep trusting its
251
+ // ctx.user there. A context found for any other request exists only
252
+ // because the server carries middleware state, is new to the
253
+ // handler, and is marked.
254
+ if (!c.req.header("X-Request-ID")) {
255
+ markContextCarried(existingCtx);
256
+ }
257
+ }
258
+ let ctx = existingCtx;
259
+ if (!ctx) {
260
+ const created = await this.createRequestContext(c, requestId, params);
261
+ if (isWebResponse(created)) {
262
+ return created;
263
+ }
264
+ ctx = created;
233
265
  }
234
- const ctx = created;
235
266
  // Emit request event
236
267
  this.emit("request", {
237
268
  requestId,
@@ -325,7 +356,7 @@ export class HonoServerAdapter extends BaseServerAdapter {
325
356
  // Untrack connection when request completes
326
357
  this.untrackConnection(connectionId);
327
358
  // Clean up context store to prevent memory leaks
328
- this.requestContextStore.delete(requestId);
359
+ this.requestContextStore.delete(c.req.raw);
329
360
  }
330
361
  });
331
362
  }
@@ -457,9 +488,9 @@ export class HonoServerAdapter extends BaseServerAdapter {
457
488
  // Extract path parameters
458
489
  const params = c.req.param();
459
490
  // Get or generate request ID for context lookup
460
- const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
491
+ const requestId = this.requestIdFor(c);
461
492
  // Reuse existing context from previous middleware if available
462
- let ctx = this.requestContextStore.get(requestId);
493
+ let ctx = this.requestContextStore.get(c.req.raw);
463
494
  if (!ctx) {
464
495
  // Create new context for the first middleware
465
496
  const created = await this.createRequestContext(c, requestId, params);
@@ -467,14 +498,34 @@ export class HonoServerAdapter extends BaseServerAdapter {
467
498
  return created;
468
499
  }
469
500
  ctx = created;
470
- // Store context for sharing between middleware and route handlers
471
- this.requestContextStore.set(requestId, ctx);
501
+ // Share the context between the middleware steps and the route
502
+ // handler when the server carries middleware state, or when the
503
+ // caller sent its own X-Request-ID: the one case the old store, keyed
504
+ // by that header, ever found a context again. Otherwise every step
505
+ // and the handler build their own, and storing this one would only
506
+ // leave it behind.
507
+ if (this.carriesMiddlewareState() || c.req.header("X-Request-ID")) {
508
+ this.requestContextStore.set(c.req.raw, ctx);
509
+ }
472
510
  }
473
511
  // Execute middleware
474
512
  return middleware.handler(ctx, next);
475
513
  });
476
514
  }
477
515
  }
516
+ /**
517
+ * The id of this request: the caller's `X-Request-ID` when sent, otherwise
518
+ * one generated on first use, and the same one on every later use.
519
+ */
520
+ requestIdFor(c) {
521
+ const known = this.requestIds.get(c.req.raw);
522
+ if (known) {
523
+ return known;
524
+ }
525
+ const id = c.req.header("X-Request-ID") || this.generateRequestId();
526
+ this.requestIds.set(c.req.raw, id);
527
+ return id;
528
+ }
478
529
  /**
479
530
  * Start the Hono server
480
531
  */
@@ -261,6 +261,7 @@ export class KoaServerAdapter extends BaseServerAdapter {
261
261
  if (ctx.state.responseHeaders) {
262
262
  serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
263
263
  }
264
+ this.restoreCarriedContext(serverCtx, ctx.state.carriedContext);
264
265
  // Emit request event
265
266
  this.emit("request", {
266
267
  requestId,
@@ -419,8 +420,12 @@ export class KoaServerAdapter extends BaseServerAdapter {
419
420
  });
420
421
  // Copy existing response headers to context
421
422
  serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
423
+ this.restoreCarriedContext(serverCtx, ctx.state.carriedContext);
422
424
  // Execute middleware
423
425
  await middleware.handler(serverCtx, async () => {
426
+ // What this middleware set (ctx.user included) is for the next
427
+ // middleware and the route handler, which build their own contexts.
428
+ ctx.state.carriedContext = this.captureCarriedContext(serverCtx);
424
429
  // After middleware execution, merge response headers back to ctx.state
425
430
  if (serverCtx.responseHeaders) {
426
431
  Object.assign(ctx.state.responseHeaders, serverCtx.responseHeaders);
@@ -35,6 +35,7 @@ export function isDevPlayground(headers) {
35
35
  export const DEV_PLAYGROUND_USER = {
36
36
  id: "playground",
37
37
  roles: ["developer"],
38
+ shared: true,
38
39
  };
39
40
  /**
40
41
  * Authentication result
@@ -76,6 +77,7 @@ export function createAuthMiddleware(config) {
76
77
  id: DEV_PLAYGROUND_USER.id,
77
78
  email: DEV_PLAYGROUND_USER.email,
78
79
  roles: DEV_PLAYGROUND_USER.roles,
80
+ shared: DEV_PLAYGROUND_USER.shared,
79
81
  };
80
82
  return next();
81
83
  }
@@ -95,6 +97,7 @@ export function createAuthMiddleware(config) {
95
97
  id: result.id,
96
98
  email: result.email,
97
99
  roles: result.roles,
100
+ shared: result.shared,
98
101
  };
99
102
  // Store additional metadata
100
103
  if (result.metadata) {
@@ -6,11 +6,52 @@ import type { RouteGroup, ServerAgentRouteOptions } from "../../types/index.js";
6
6
  /**
7
7
  * Create agent routes
8
8
  *
9
+ * `options.perUserAuth` decides where a request's identity comes from; see
10
+ * `ServerAgentRouteOptions`. Authentication middleware that sets `ctx.user`
11
+ * does not change that on its own: one shared API key sets the same user for
12
+ * every caller, so trusting it as a person would give every client the same
13
+ * userId and no conversation memory. The converse is the operator's to act on:
14
+ * a server whose authentication identifies individual people must set
15
+ * `perUserAuth`, or any authenticated caller can name another userId and
16
+ * sessionId in the body.
17
+ *
18
+ * Unset, identity resolves as it did before the adapters carried `ctx.user` to
19
+ * the handler: the body's `userId` and `sessionId`, with one exception. On
20
+ * Hono, a request whose client sent its own `X-Request-ID` header reached the
21
+ * handler with the middleware's context, so an authenticated `ctx.user` (and
22
+ * `ctx.session`) won over the body there, and still does. A context the
23
+ * built-in adapters did not hand the carry to also keeps that rule: a custom
24
+ * adapter that sets `ctx.user` itself, or a handler called directly with a
25
+ * context the caller built. Only `perUserAuth` (with the server carrying
26
+ * middleware state) makes these routes take the user from `ctx.user`, and
27
+ * ignore the body's `userId`, on every built-in adapter and for every request.
28
+ *
29
+ * `perUserAuth` needs the user in the handler, which an adapter hands over on
30
+ * every request only when the server carries middleware state.
31
+ * `registerAllRoutes()` turns that on for the server when it is given
32
+ * `perUserAuth`. A caller that registers these routes itself with `perUserAuth`
33
+ * must create the server with `carryMiddlewareState: true`. Without it, a
34
+ * request has a user only on Hono when the client sent its own `X-Request-ID`
35
+ * (Hono then hands the handler the middleware's context regardless); every
36
+ * other request (Hono without that header, Express, Fastify, Koa) runs as
37
+ * anonymous, with no `userId` and no session.
38
+ *
39
+ * Without `perUserAuth`, and when the server carries middleware state, the
40
+ * routes log one warning per instance the first time a request whose body names
41
+ * a different `userId` than its `ctx.user` is served from the body, unless that
42
+ * user is marked `shared: true` (an identity for every holder of one
43
+ * credential, which the CLI's API-key middleware sets). Such a request is
44
+ * served from the body; the warning only tells the operator that per-person
45
+ * authentication is running without the flag that makes it hold. With the
46
+ * carry off the handler has no `ctx.user` on those requests, so there is
47
+ * nothing to compare and no warning. A request where `ctx.user` already won has
48
+ * nothing to warn about.
49
+ *
9
50
  * @param options - Limits for `input.images` / `input.files`:
10
51
  * `allowedInputRoots` (default: none, every local-path attachment is
11
52
  * refused), `maxInputBytes` per attachment (default 50 MiB),
12
53
  * `maxInputAttachments` per request (default 20, images and files together)
13
54
  * and `maxInputTotalBytes` per request (default 100 MiB, one budget shared by
14
- * images and files).
55
+ * images and files); and `perUserAuth` (described above).
15
56
  */
16
57
  export declare function createAgentRoutes(basePath?: string, options?: ServerAgentRouteOptions): RouteGroup;