@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.
@@ -7,6 +7,7 @@ import { ProviderFactory } from "../../factories/providerFactory.js";
7
7
  import { withSpan } from "../../telemetry/withSpan.js";
8
8
  import { tracers } from "../../telemetry/tracers.js";
9
9
  import { logger } from "../../utils/logger.js";
10
+ import { isContextCarried } from "../utils/carriedContextMark.js";
10
11
  /**
11
12
  * Resolve `request.tools` (an array of tool NAMES shared by the execute and
12
13
  * stream endpoints) into a `toolFilter` whitelist. The field was previously
@@ -78,14 +79,144 @@ async function resolveRequestInput(ctx, requestInput, options) {
78
79
  /**
79
80
  * Create agent routes
80
81
  *
82
+ * `options.perUserAuth` decides where a request's identity comes from; see
83
+ * `ServerAgentRouteOptions`. Authentication middleware that sets `ctx.user`
84
+ * does not change that on its own: one shared API key sets the same user for
85
+ * every caller, so trusting it as a person would give every client the same
86
+ * userId and no conversation memory. The converse is the operator's to act on:
87
+ * a server whose authentication identifies individual people must set
88
+ * `perUserAuth`, or any authenticated caller can name another userId and
89
+ * sessionId in the body.
90
+ *
91
+ * Unset, identity resolves as it did before the adapters carried `ctx.user` to
92
+ * the handler: the body's `userId` and `sessionId`, with one exception. On
93
+ * Hono, a request whose client sent its own `X-Request-ID` header reached the
94
+ * handler with the middleware's context, so an authenticated `ctx.user` (and
95
+ * `ctx.session`) won over the body there, and still does. A context the
96
+ * built-in adapters did not hand the carry to also keeps that rule: a custom
97
+ * adapter that sets `ctx.user` itself, or a handler called directly with a
98
+ * context the caller built. Only `perUserAuth` (with the server carrying
99
+ * middleware state) makes these routes take the user from `ctx.user`, and
100
+ * ignore the body's `userId`, on every built-in adapter and for every request.
101
+ *
102
+ * `perUserAuth` needs the user in the handler, which an adapter hands over on
103
+ * every request only when the server carries middleware state.
104
+ * `registerAllRoutes()` turns that on for the server when it is given
105
+ * `perUserAuth`. A caller that registers these routes itself with `perUserAuth`
106
+ * must create the server with `carryMiddlewareState: true`. Without it, a
107
+ * request has a user only on Hono when the client sent its own `X-Request-ID`
108
+ * (Hono then hands the handler the middleware's context regardless); every
109
+ * other request (Hono without that header, Express, Fastify, Koa) runs as
110
+ * anonymous, with no `userId` and no session.
111
+ *
112
+ * Without `perUserAuth`, and when the server carries middleware state, the
113
+ * routes log one warning per instance the first time a request whose body names
114
+ * a different `userId` than its `ctx.user` is served from the body, unless that
115
+ * user is marked `shared: true` (an identity for every holder of one
116
+ * credential, which the CLI's API-key middleware sets). Such a request is
117
+ * served from the body; the warning only tells the operator that per-person
118
+ * authentication is running without the flag that makes it hold. With the
119
+ * carry off the handler has no `ctx.user` on those requests, so there is
120
+ * nothing to compare and no warning. A request where `ctx.user` already won has
121
+ * nothing to warn about.
122
+ *
81
123
  * @param options - Limits for `input.images` / `input.files`:
82
124
  * `allowedInputRoots` (default: none, every local-path attachment is
83
125
  * refused), `maxInputBytes` per attachment (default 50 MiB),
84
126
  * `maxInputAttachments` per request (default 20, images and files together)
85
127
  * and `maxInputTotalBytes` per request (default 100 MiB, one budget shared by
86
- * images and files).
128
+ * images and files); and `perUserAuth` (described above).
87
129
  */
88
130
  export function createAgentRoutes(basePath = "/api", options) {
131
+ // Truthiness, not `=== true`: a JavaScript caller passing "true" or 1 must get
132
+ // the strict mode, not silently the permissive one.
133
+ const perUser = Boolean(options?.perUserAuth);
134
+ // Without perUserAuth the body's userId wins (except where ctx.user already
135
+ // did), so an authenticated person who names someone else is acting as them.
136
+ // That is expected under one shared key (marked `shared`), where the body is
137
+ // how callers keep separate memory, and a misconfiguration under per-person
138
+ // authentication: say so, once, with no request values in the message.
139
+ // alwaysStderr, not warn: logger.warn is dropped unless debug logging is on,
140
+ // which would hide a security notice from the operator it is for.
141
+ let warnedNamingAnotherUser = false;
142
+ const warnIfNamingAnotherUser = (ctx, request) => {
143
+ if (warnedNamingAnotherUser ||
144
+ !ctx.user ||
145
+ ctx.user.shared === true ||
146
+ !isContextCarried(ctx) ||
147
+ typeof request.userId !== "string" ||
148
+ request.userId === ctx.user.id) {
149
+ return;
150
+ }
151
+ warnedNamingAnotherUser = true;
152
+ logger.alwaysStderr("[agentRoutes] An authenticated user named a different userId in the request body, and the body's userId was used. " +
153
+ "Per-person authentication without perUserAuth lets a caller name another user. " +
154
+ "Set perUserAuth: true, or mark an identity that stands for a shared credential with shared: true. " +
155
+ "This warning is logged once per server.");
156
+ };
157
+ // Who a skill mutation is attributed to. With perUserAuth the authenticated
158
+ // user wins and a caller-supplied name is never trusted. Otherwise the body's
159
+ // `requestedBy` is used, since a shared key's `ctx.user` names no person, for
160
+ // a context that received the adapters' carry; a context that did not (Hono
161
+ // with the caller's X-Request-ID, a custom adapter, a direct call) lets the
162
+ // authenticated user win, as it did before.
163
+ const skillRequester = (ctx, bodyRequestedBy) => {
164
+ if (perUser) {
165
+ return ctx.user?.id;
166
+ }
167
+ return isContextCarried(ctx)
168
+ ? bodyRequestedBy
169
+ : (ctx.user?.id ?? bodyRequestedBy);
170
+ };
171
+ // The identity generate() and stream() run under.
172
+ //
173
+ // perUserAuth: the middleware identifies individual people, so userId, email,
174
+ // roles and the session come only from ctx.user / ctx.session and the body's
175
+ // are ignored (no caller-supplied impersonation). The session is the
176
+ // authenticated user's: a request with no ctx.user gets none, even if a
177
+ // session middleware named one, because with optional authentication every
178
+ // anonymous caller that sends the same session id would otherwise share one
179
+ // conversation.
180
+ //
181
+ // Otherwise the body keeps choosing userId and sessionId, which is what every
182
+ // adapter did before it carried ctx.user to the handler: a context that
183
+ // received the adapters' carry is treated as the fresh one it used to be. The
184
+ // exceptions are the contexts that did not get the carry, where an
185
+ // authenticated ctx.user wins over the body, as it did, and so does the
186
+ // session: Hono with the caller's own X-Request-ID, a custom adapter that sets
187
+ // ctx.user itself, a handler called directly. Per-person authentication
188
+ // without perUserAuth therefore does not stop a caller naming someone else on
189
+ // the other requests.
190
+ const callerContext = (ctx, request) => {
191
+ if (perUser) {
192
+ return {
193
+ sessionId: ctx.user ? ctx.session?.id : undefined,
194
+ userId: ctx.user?.id,
195
+ userEmail: ctx.user?.email,
196
+ userRoles: ctx.user?.roles,
197
+ requestId: ctx.requestId,
198
+ };
199
+ }
200
+ if (!isContextCarried(ctx)) {
201
+ return {
202
+ sessionId: ctx.user
203
+ ? ctx.session?.id
204
+ : (ctx.session?.id ?? request.sessionId),
205
+ userId: ctx.user ? ctx.user.id : request.userId,
206
+ userEmail: ctx.user?.email,
207
+ userRoles: ctx.user?.roles,
208
+ requestId: ctx.requestId,
209
+ };
210
+ }
211
+ warnIfNamingAnotherUser(ctx, request);
212
+ return {
213
+ sessionId: request.sessionId,
214
+ userId: request.userId,
215
+ userEmail: undefined,
216
+ userRoles: undefined,
217
+ requestId: ctx.requestId,
218
+ };
219
+ };
89
220
  return {
90
221
  prefix: `${basePath}/agent`,
91
222
  routes: [
@@ -121,17 +252,7 @@ export function createAgentRoutes(basePath = "/api", options) {
121
252
  temperature: request.temperature,
122
253
  maxTokens: request.maxTokens,
123
254
  ...(requestToolFilter ? { toolFilter: requestToolFilter } : {}),
124
- context: {
125
- // When an authenticated user context exists (set by auth middleware),
126
- // always use its IDs to prevent caller-supplied impersonation.
127
- sessionId: ctx.user
128
- ? ctx.session?.id
129
- : (ctx.session?.id ?? request.sessionId),
130
- userId: ctx.user ? ctx.user.id : request.userId,
131
- userEmail: ctx.user?.email,
132
- userRoles: ctx.user?.roles,
133
- requestId: ctx.requestId,
134
- },
255
+ context: callerContext(ctx, request),
135
256
  });
136
257
  // Map tool calls from SDK format to API format
137
258
  const toolCalls = result.toolCalls?.map((tc) => ({
@@ -174,17 +295,7 @@ export function createAgentRoutes(basePath = "/api", options) {
174
295
  temperature: request.temperature,
175
296
  maxTokens: request.maxTokens,
176
297
  ...(streamToolFilter ? { toolFilter: streamToolFilter } : {}),
177
- context: {
178
- // When an authenticated user context exists (set by auth middleware),
179
- // always use its IDs to prevent caller-supplied impersonation.
180
- sessionId: ctx.user
181
- ? ctx.session?.id
182
- : (ctx.session?.id ?? request.sessionId),
183
- userId: ctx.user ? ctx.user.id : request.userId,
184
- userEmail: ctx.user?.email,
185
- userRoles: ctx.user?.roles,
186
- requestId: ctx.requestId,
187
- },
298
+ context: callerContext(ctx, request),
188
299
  });
189
300
  // Create redactor (no-op if redaction is not enabled)
190
301
  const redactor = createStreamRedactor(ctx.redaction);
@@ -369,14 +480,12 @@ export function createAgentRoutes(basePath = "/api", options) {
369
480
  return validation.error;
370
481
  }
371
482
  const { requestedBy, ...skill } = validation.data;
483
+ const requester = skillRequester(ctx, requestedBy);
372
484
  try {
373
485
  const result = await manager.requestMutation({
374
486
  type: "create",
375
487
  skill,
376
- // Authenticated identity wins over caller-supplied attribution.
377
- ...(ctx.user?.id || requestedBy
378
- ? { requestedBy: ctx.user?.id ?? requestedBy }
379
- : {}),
488
+ ...(requester ? { requestedBy: requester } : {}),
380
489
  });
381
490
  return result;
382
491
  }
@@ -400,14 +509,13 @@ export function createAgentRoutes(basePath = "/api", options) {
400
509
  return validation.error;
401
510
  }
402
511
  const { requestedBy, ...patch } = validation.data;
512
+ const requester = skillRequester(ctx, requestedBy);
403
513
  try {
404
514
  const result = await manager.requestMutation({
405
515
  type: "update",
406
516
  skillId: ctx.params.id,
407
517
  patch,
408
- ...(ctx.user?.id || requestedBy
409
- ? { requestedBy: ctx.user?.id ?? requestedBy }
410
- : {}),
518
+ ...(requester ? { requestedBy: requester } : {}),
411
519
  });
412
520
  return result;
413
521
  }
@@ -427,11 +535,12 @@ export function createAgentRoutes(basePath = "/api", options) {
427
535
  if ("error" in manager) {
428
536
  return manager;
429
537
  }
538
+ const requester = skillRequester(ctx, undefined);
430
539
  try {
431
540
  const result = await manager.requestMutation({
432
541
  type: "delete",
433
542
  skillId: ctx.params.id,
434
- ...(ctx.user?.id ? { requestedBy: ctx.user.id } : {}),
543
+ ...(requester ? { requestedBy: requester } : {}),
435
544
  });
436
545
  return result;
437
546
  }
@@ -29,8 +29,17 @@ export { createTranscribeRoutes } from "./transcribeRoutes.js";
29
29
  export declare function createAllRoutes(basePath?: string, options?: CreateRoutesOptions): RouteGroup[];
30
30
  /**
31
31
  * Register all routes with a server adapter
32
+ *
33
+ * With `options.perUserAuth` this also turns on the adapter's
34
+ * `carryMiddlewareState` (`enableMiddlewareStateCarry()`): the agent routes can
35
+ * only take identity from `ctx.user` if the adapter hands the handler what
36
+ * authentication middleware set. An adapter without that method must carry
37
+ * state itself; otherwise the routes see a user only where the adapter happens
38
+ * to share its context (Hono, when the client sent its own `X-Request-ID`), and
39
+ * every other request runs as anonymous.
32
40
  */
33
41
  export declare function registerAllRoutes(adapter: {
34
42
  registerRouteGroup: (group: RouteGroup) => void;
35
43
  listRoutes?: () => RouteDefinition[];
44
+ enableMiddlewareStateCarry?: () => void;
36
45
  }, basePath?: string, options?: CreateRoutesOptions): void;
@@ -41,7 +41,10 @@ export { createTranscribeRoutes } from "./transcribeRoutes.js";
41
41
  */
42
42
  export function createAllRoutes(basePath = "/api", options) {
43
43
  const routes = [
44
- createAgentRoutes(basePath, options?.agent),
44
+ createAgentRoutes(basePath, {
45
+ ...options?.agent,
46
+ perUserAuth: options?.perUserAuth,
47
+ }),
45
48
  createToolRoutes(basePath),
46
49
  createMCPRoutes(basePath),
47
50
  createMemoryRoutes(basePath),
@@ -76,8 +79,19 @@ export function createAllRoutes(basePath = "/api", options) {
76
79
  }
77
80
  /**
78
81
  * Register all routes with a server adapter
82
+ *
83
+ * With `options.perUserAuth` this also turns on the adapter's
84
+ * `carryMiddlewareState` (`enableMiddlewareStateCarry()`): the agent routes can
85
+ * only take identity from `ctx.user` if the adapter hands the handler what
86
+ * authentication middleware set. An adapter without that method must carry
87
+ * state itself; otherwise the routes see a user only where the adapter happens
88
+ * to share its context (Hono, when the client sent its own `X-Request-ID`), and
89
+ * every other request runs as anonymous.
79
90
  */
80
91
  export function registerAllRoutes(adapter, basePath = "/api", options) {
92
+ if (options?.perUserAuth) {
93
+ adapter.enableMiddlewareStateCarry?.();
94
+ }
81
95
  // If adapter has listRoutes and getRoutes not provided, use adapter's listRoutes
82
96
  const routeOptions = {
83
97
  ...options,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Which route-handler contexts received the state the adapters now carry from
3
+ * middleware to the handler.
4
+ *
5
+ * Before the adapters carried that state, a handler normally got a fresh
6
+ * context with no `ctx.user` and no `ctx.session`. The one exception was Hono
7
+ * when the client sent its own `X-Request-ID`: Hono kept the middleware's
8
+ * context in a store keyed by that header, so the handler found it again, user
9
+ * included. The agent routes' default (`perUserAuth` unset) must not start
10
+ * trusting `ctx.user` on the requests that never had it, so the built-in
11
+ * adapters mark the contexts that received the carry, and the agent routes
12
+ * treat a marked context as the fresh one it used to be: identity from the body.
13
+ *
14
+ * Everything else keeps the earlier rule, an authenticated `ctx.user` wins: a
15
+ * Hono request with a caller-supplied `X-Request-ID` (never marked), a custom
16
+ * adapter that carries `ctx.user` itself, and a route handler called directly
17
+ * with a context the caller built.
18
+ *
19
+ * It is an internal fact about how the context was built, not part of
20
+ * `ServerContext`, and it is not exported from the package.
21
+ *
22
+ * @module server/utils/carriedContextMark
23
+ */
24
+ import type { ServerContext } from "../../types/index.js";
25
+ /** Record that this context received state carried from middleware. */
26
+ export declare function markContextCarried(ctx: ServerContext): void;
27
+ /** `true` when `markContextCarried` was called for this context. */
28
+ export declare function isContextCarried(ctx: ServerContext): boolean;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Which route-handler contexts received the state the adapters now carry from
3
+ * middleware to the handler.
4
+ *
5
+ * Before the adapters carried that state, a handler normally got a fresh
6
+ * context with no `ctx.user` and no `ctx.session`. The one exception was Hono
7
+ * when the client sent its own `X-Request-ID`: Hono kept the middleware's
8
+ * context in a store keyed by that header, so the handler found it again, user
9
+ * included. The agent routes' default (`perUserAuth` unset) must not start
10
+ * trusting `ctx.user` on the requests that never had it, so the built-in
11
+ * adapters mark the contexts that received the carry, and the agent routes
12
+ * treat a marked context as the fresh one it used to be: identity from the body.
13
+ *
14
+ * Everything else keeps the earlier rule, an authenticated `ctx.user` wins: a
15
+ * Hono request with a caller-supplied `X-Request-ID` (never marked), a custom
16
+ * adapter that carries `ctx.user` itself, and a route handler called directly
17
+ * with a context the caller built.
18
+ *
19
+ * It is an internal fact about how the context was built, not part of
20
+ * `ServerContext`, and it is not exported from the package.
21
+ *
22
+ * @module server/utils/carriedContextMark
23
+ */
24
+ const receivedCarry = new WeakSet();
25
+ /** Record that this context received state carried from middleware. */
26
+ export function markContextCarried(ctx) {
27
+ receivedCarry.add(ctx);
28
+ }
29
+ /** `true` when `markContextCarried` was called for this context. */
30
+ export function isContextCarried(ctx) {
31
+ return receivedCarry.has(ctx);
32
+ }
@@ -40,6 +40,41 @@ export type ServerAdapterConfig = {
40
40
  redaction?: RedactionConfig;
41
41
  /** Shutdown configuration for graceful shutdown behavior */
42
42
  shutdown?: ShutdownConfig;
43
+ /**
44
+ * Carry what middleware sets on its context (`ctx.user`, `ctx.session`,
45
+ * `ctx.metadata`, `ctx.abortSignal` and `ctx.abortController`) to later
46
+ * middleware and to the route handler, on every built-in adapter (default:
47
+ * `false`). It does not apply to a context you build yourself: a custom
48
+ * adapter, or a route handler called directly, can supply a context that
49
+ * already has `ctx.user` set.
50
+ *
51
+ * Off, each adapter propagates context as it always did. Express, Fastify
52
+ * and Koa build a fresh context for every middleware step and for the
53
+ * handler and carry only `responseHeaders`; Hono shares one context between
54
+ * the steps and the handler only when the caller sent its own
55
+ * `X-Request-ID`. So a role guard, a permission guard or a rate-limit
56
+ * `keyGenerator` registered after authentication sees `ctx.user` only on
57
+ * that Hono request shape: on Express, Fastify and Koa, and on Hono without
58
+ * a caller `X-Request-ID`, the user never reaches it and the guard refuses
59
+ * everyone.
60
+ *
61
+ * On, the user and the rest reach later middleware and the handler on every
62
+ * built-in adapter. Set it when guards or key generators depend on `ctx.user`.
63
+ * `registerAllRoutes()` turns it on for the server by itself when called with
64
+ * `perUserAuth`, which needs the user in the handler. A caller that builds
65
+ * the agent routes with `createAgentRoutes(basePath, { perUserAuth: true })`
66
+ * and registers them itself must set this on the server: with it off, such a
67
+ * request has a user only on Hono when the client sent its own
68
+ * `X-Request-ID`, and every other request runs as anonymous (no `userId`, no
69
+ * session).
70
+ *
71
+ * The agent routes' warning about a body naming another user is raised only
72
+ * with this on. With it off, a handler that does receive `ctx.user` (Hono
73
+ * when the caller sent its own `X-Request-ID`) takes the user from it and
74
+ * ignores the body's `userId`, so nothing is named over it; every other
75
+ * request reaches the handler without a `ctx.user` to compare against.
76
+ */
77
+ carryMiddlewareState?: boolean;
43
78
  };
44
79
  /**
45
80
  * Required server adapter configuration (with defaults applied)
@@ -58,6 +93,7 @@ export type RequiredServerAdapterConfig = {
58
93
  disableBuiltInHealth: boolean;
59
94
  redaction?: RequiredRedactionConfig;
60
95
  shutdown: RequiredShutdownConfig;
96
+ carryMiddlewareState: boolean;
61
97
  };
62
98
  /**
63
99
  * CORS configuration
@@ -256,6 +292,12 @@ export type ServerContext = {
256
292
  /** Redaction configuration (for stream redaction support) */
257
293
  redaction?: RedactionConfig;
258
294
  };
295
+ /**
296
+ * What middleware sets on its context that later middleware and the route
297
+ * handler must see. Adapters build a fresh context for every step, so this is
298
+ * what they carry between steps in the framework's per-request storage.
299
+ */
300
+ export type ServerCarriedContext = Pick<ServerContext, "user" | "session" | "metadata" | "abortSignal" | "abortController">;
259
301
  /**
260
302
  * Server response object
261
303
  */
@@ -471,6 +513,50 @@ export type ServerAgentRouteOptions = {
471
513
  * file or download that would overflow it stops mid-transfer.
472
514
  */
473
515
  maxInputTotalBytes?: number;
516
+ /**
517
+ * For a caller of `createAgentRoutes()` itself; with `registerAllRoutes()` /
518
+ * `createAllRoutes()` use `CreateRoutesOptions.perUserAuth` instead. The
519
+ * routes can only take identity from `ctx.user` if the server hands it to
520
+ * the handler, so a caller that registers them itself with this option must
521
+ * create the server with `carryMiddlewareState: true`; `registerAllRoutes()`
522
+ * does that for the server by itself. With `carryMiddlewareState` off, such a request has
523
+ * a user only on Hono when the client sent its own `X-Request-ID` (Hono then
524
+ * hands the handler the middleware's context regardless); every other request
525
+ * runs as anonymous, with no `userId` and no session.
526
+ *
527
+ * Set to `true` when the authentication middleware identifies individual
528
+ * people (one credential per person). The agent routes then take `userId`
529
+ * and the session only from the authenticated user (`ctx.user`,
530
+ * `ctx.session`) and ignore the request body's, on every adapter and for
531
+ * every request, so a caller cannot act as someone else. A request with no
532
+ * authenticated user gets no `userId` and no session, so with optional
533
+ * authentication anonymous callers never share a conversation. Any truthy
534
+ * value turns it on.
535
+ *
536
+ * **A server with per-person authentication must set this.** Left at the
537
+ * default (`false`) identity resolves as it always has: requests choose
538
+ * `userId` and `sessionId` in the body, so any authenticated caller can name
539
+ * another person there and act as them. The one exception is Hono, where a
540
+ * request whose client sent its own `X-Request-ID` header reaches the
541
+ * handler with the middleware's context, and an authenticated `ctx.user`
542
+ * (and `ctx.session`) wins over the body, as before, and so does it on a
543
+ * context the built-in adapters did not build (your own adapter, a handler
544
+ * called directly). Only this flag, with `carryMiddlewareState` on, makes the agent routes take
545
+ * the user from `ctx.user`, and ignore the body's `userId`, on every
546
+ * built-in adapter and every request. When the server carries middleware state
547
+ * the routes log one warning per instance the first time a request served
548
+ * from the body names a different `userId` than a `ctx.user` that is not
549
+ * marked `shared: true`; the body's `userId` is still used. With `carryMiddlewareState`
550
+ * off the handler has no `ctx.user` on such a request, so there is nothing to
551
+ * compare and no warning.
552
+ *
553
+ * Leave it unset for no authentication or one shared API key, such as
554
+ * `neurolink serve` with `NEUROLINK_SERVER_API_KEY`: a shared key names no
555
+ * person, so requests choose `userId` and `sessionId` in the body. Middleware
556
+ * for a shared key should set `shared: true` on the `AuthenticatedUser` it
557
+ * returns, which keeps the warning quiet (the CLI's key middleware does).
558
+ */
559
+ perUserAuth?: boolean;
474
560
  };
475
561
  /**
476
562
  * The route options plus the one thing that belongs to a single call:
@@ -762,6 +848,15 @@ export type WebSocketRequestId = string | number;
762
848
  */
763
849
  export type AuthenticatedUser = {
764
850
  id: string;
851
+ /**
852
+ * `true` when this identity stands for every holder of one credential, such
853
+ * as a shared API key, rather than for one person. The agent routes then do
854
+ * not warn when a request names a different `userId` in its body: with a
855
+ * shared key every caller is one trust domain and the body is how callers
856
+ * keep separate conversation memory. Middleware that sets one fixed user for
857
+ * everyone holding a key should set this; per-person middleware should not.
858
+ */
859
+ shared?: boolean;
765
860
  email?: string;
766
861
  name?: string;
767
862
  roles?: string[];
@@ -1073,6 +1168,12 @@ export type AuthResult = {
1073
1168
  email?: string;
1074
1169
  /** User roles (optional) */
1075
1170
  roles?: string[];
1171
+ /**
1172
+ * `true` when this identity stands for every holder of one credential, such
1173
+ * as a shared API key, rather than one person. Carried to `ctx.user.shared`;
1174
+ * see `AuthenticatedUser.shared`.
1175
+ */
1176
+ shared?: boolean;
1076
1177
  /** Additional user data */
1077
1178
  metadata?: Record<string, unknown>;
1078
1179
  };
@@ -1174,9 +1275,46 @@ export type CreateRoutesOptions = {
1174
1275
  /**
1175
1276
  * Options for the agent routes (`POST <basePath>/agent/execute` and
1176
1277
  * `/agent/stream`): where local-path attachments may be read from, and how
1177
- * large an attachment may be.
1278
+ * large an attachment may be. Whose identity the routes trust is decided by
1279
+ * the top-level `perUserAuth`, which is why it is not accepted here.
1280
+ */
1281
+ agent?: Omit<ServerAgentRouteOptions, "perUserAuth">;
1282
+ /**
1283
+ * Set to `true` when the authentication middleware identifies individual
1284
+ * people (one credential per person). The agent routes then take `userId`
1285
+ * and the session only from the authenticated user and ignore the request
1286
+ * body's, on every adapter and for every request; a request with no
1287
+ * authenticated user gets neither. Any truthy value turns it on.
1288
+ *
1289
+ * **A server with per-person authentication must set this.** Left at the
1290
+ * default (`false`) requests choose `userId` and `sessionId` in the body, so
1291
+ * any authenticated caller can name another person there and act as them.
1292
+ * The one exception is Hono, where a request whose client sent its own
1293
+ * `X-Request-ID` header reaches the handler with the middleware's context and
1294
+ * an authenticated `ctx.user` wins over the body, as before, and so on a
1295
+ * context the built-in adapters did not build (your own adapter, a handler
1296
+ * called directly); only this flag, with `carryMiddlewareState` on, makes the agent routes take
1297
+ * the user from `ctx.user` everywhere. When the server carries middleware state, the first time a
1298
+ * request served from the body names a different `userId` than its
1299
+ * `ctx.user`, the routes log a warning (once per instance); with `carryMiddlewareState`
1300
+ * off the handler has no `ctx.user` to compare, and there is no warning.
1301
+ * Leave it unset for no authentication or one shared
1302
+ * API key, such as `neurolink serve` with `NEUROLINK_SERVER_API_KEY`:
1303
+ * requests choose `userId` and `sessionId` in the body. Middleware for a
1304
+ * shared key should set `shared: true` on the `AuthenticatedUser` it
1305
+ * returns, which keeps the warning quiet.
1306
+ *
1307
+ * This is the one switch for a server built with `registerAllRoutes` /
1308
+ * `createAllRoutes`: every route group that scopes by identity reads it from
1309
+ * here, so it cannot be set for the agent routes alone. `registerAllRoutes`
1310
+ * also turns on the server's `carryMiddlewareState` when this is set, since
1311
+ * the routes need the user in the handler; `createAllRoutes` returns route
1312
+ * groups only, so a caller that registers them itself must create the server
1313
+ * with `carryMiddlewareState: true`; with `carryMiddlewareState` off, a `perUserAuth`
1314
+ * request has a user only on Hono when the client sent its own
1315
+ * `X-Request-ID`, and every other request runs as anonymous.
1178
1316
  */
1179
- agent?: ServerAgentRouteOptions;
1317
+ perUserAuth?: boolean;
1180
1318
  };
1181
1319
  /** Options for `createTranscribeRoutes()`. */
1182
1320
  /**