@agentick/gateway 0.0.1

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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +477 -0
  3. package/dist/agent-registry.d.ts +51 -0
  4. package/dist/agent-registry.d.ts.map +1 -0
  5. package/dist/agent-registry.js +78 -0
  6. package/dist/agent-registry.js.map +1 -0
  7. package/dist/app-registry.d.ts +51 -0
  8. package/dist/app-registry.d.ts.map +1 -0
  9. package/dist/app-registry.js +78 -0
  10. package/dist/app-registry.js.map +1 -0
  11. package/dist/bin.d.ts +8 -0
  12. package/dist/bin.d.ts.map +1 -0
  13. package/dist/bin.js +37 -0
  14. package/dist/bin.js.map +1 -0
  15. package/dist/gateway.d.ts +165 -0
  16. package/dist/gateway.d.ts.map +1 -0
  17. package/dist/gateway.js +1339 -0
  18. package/dist/gateway.js.map +1 -0
  19. package/dist/http-transport.d.ts +65 -0
  20. package/dist/http-transport.d.ts.map +1 -0
  21. package/dist/http-transport.js +517 -0
  22. package/dist/http-transport.js.map +1 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/protocol.d.ts +162 -0
  28. package/dist/protocol.d.ts.map +1 -0
  29. package/dist/protocol.js +16 -0
  30. package/dist/protocol.js.map +1 -0
  31. package/dist/session-manager.d.ts +101 -0
  32. package/dist/session-manager.d.ts.map +1 -0
  33. package/dist/session-manager.js +208 -0
  34. package/dist/session-manager.js.map +1 -0
  35. package/dist/testing.d.ts +92 -0
  36. package/dist/testing.d.ts.map +1 -0
  37. package/dist/testing.js +129 -0
  38. package/dist/testing.js.map +1 -0
  39. package/dist/transport-protocol.d.ts +162 -0
  40. package/dist/transport-protocol.d.ts.map +1 -0
  41. package/dist/transport-protocol.js +16 -0
  42. package/dist/transport-protocol.js.map +1 -0
  43. package/dist/transport.d.ts +115 -0
  44. package/dist/transport.d.ts.map +1 -0
  45. package/dist/transport.js +56 -0
  46. package/dist/transport.js.map +1 -0
  47. package/dist/types.d.ts +314 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +37 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/websocket-server.d.ts +87 -0
  52. package/dist/websocket-server.d.ts.map +1 -0
  53. package/dist/websocket-server.js +245 -0
  54. package/dist/websocket-server.js.map +1 -0
  55. package/dist/ws-transport.d.ts +17 -0
  56. package/dist/ws-transport.d.ts.map +1 -0
  57. package/dist/ws-transport.js +174 -0
  58. package/dist/ws-transport.js.map +1 -0
  59. package/package.json +51 -0
  60. package/src/__tests__/custom-methods.spec.ts +220 -0
  61. package/src/__tests__/gateway-methods.spec.ts +262 -0
  62. package/src/__tests__/gateway.spec.ts +404 -0
  63. package/src/__tests__/guards.spec.ts +235 -0
  64. package/src/__tests__/protocol.spec.ts +58 -0
  65. package/src/__tests__/session-manager.spec.ts +220 -0
  66. package/src/__tests__/ws-transport.spec.ts +246 -0
  67. package/src/app-registry.ts +103 -0
  68. package/src/bin.ts +38 -0
  69. package/src/gateway.ts +1712 -0
  70. package/src/http-transport.ts +623 -0
  71. package/src/index.ts +94 -0
  72. package/src/session-manager.ts +272 -0
  73. package/src/testing.ts +236 -0
  74. package/src/transport-protocol.ts +249 -0
  75. package/src/transport.ts +191 -0
  76. package/src/types.ts +392 -0
  77. package/src/websocket-server.ts +303 -0
  78. package/src/ws-transport.ts +205 -0
package/src/types.ts ADDED
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Gateway Types
3
+ */
4
+
5
+ import type { App } from "@agentick/core";
6
+ import type { KernelContext, UserContext } from "@agentick/kernel";
7
+ import type { AuthConfig } from "@agentick/server";
8
+
9
+ // Re-export auth types from server
10
+ export type { AuthConfig, AuthResult } from "@agentick/server";
11
+
12
+ /**
13
+ * Schema type that works with both Zod 3 and Zod 4.
14
+ * We only need parse() and type inference (_output).
15
+ */
16
+ export interface ZodLikeSchema<T = unknown> {
17
+ parse(data: unknown): T;
18
+ _output: T;
19
+ }
20
+
21
+ export type { UserContext } from "@agentick/kernel";
22
+
23
+ // ============================================================================
24
+ // Gateway Configuration
25
+ // ============================================================================
26
+
27
+ export interface GatewayConfig {
28
+ /**
29
+ * Port to listen on (ignored in embedded mode)
30
+ * @default 18789
31
+ */
32
+ port?: number;
33
+
34
+ /**
35
+ * Host to bind to (ignored in embedded mode)
36
+ * @default "127.0.0.1"
37
+ */
38
+ host?: string;
39
+
40
+ /**
41
+ * Gateway ID (auto-generated if not provided)
42
+ */
43
+ id?: string;
44
+
45
+ /**
46
+ * App definitions
47
+ */
48
+ apps: Record<string, App>;
49
+
50
+ /**
51
+ * Default app to use when session key doesn't specify one
52
+ */
53
+ defaultApp: string;
54
+
55
+ /**
56
+ * Authentication configuration
57
+ */
58
+ auth?: AuthConfig;
59
+
60
+ /**
61
+ * Run in embedded mode (no standalone server).
62
+ * Use handleRequest() to process requests from your framework.
63
+ * @default false
64
+ */
65
+ embedded?: boolean;
66
+
67
+ /**
68
+ * Persistence configuration
69
+ */
70
+ storage?: StorageConfig;
71
+
72
+ /**
73
+ * Channel adapters (WhatsApp, Slack, etc.)
74
+ */
75
+ channels?: ChannelAdapter[];
76
+
77
+ /**
78
+ * Message routing configuration
79
+ */
80
+ routing?: RoutingConfig;
81
+
82
+ /**
83
+ * Transport mode (ignored in embedded mode)
84
+ * - "websocket": WebSocket only (default, good for CLI/native clients)
85
+ * - "http": HTTP/SSE only (good for web browsers)
86
+ * - "both": Both transports on different ports
87
+ * @default "websocket"
88
+ */
89
+ transport?: "websocket" | "http" | "both";
90
+
91
+ /**
92
+ * HTTP path prefix (e.g., "/api")
93
+ * @default ""
94
+ */
95
+ httpPathPrefix?: string;
96
+
97
+ /**
98
+ * CORS origin for HTTP transport
99
+ * @default "*"
100
+ */
101
+ httpCorsOrigin?: string;
102
+
103
+ /**
104
+ * HTTP port when using "both" mode
105
+ * @default port + 1
106
+ */
107
+ httpPort?: number;
108
+
109
+ /**
110
+ * Custom methods - runs within Agentick ALS context.
111
+ *
112
+ * Supports:
113
+ * - Simple handlers: `async (params) => result`
114
+ * - Streaming: `async function* (params) { yield value }`
115
+ * - With config: `method({ schema, handler, roles, guard })`
116
+ * - Namespaces: `{ tasks: { list, create, admin: { ... } } }` (recursive)
117
+ *
118
+ * Use method() wrapper for schema validation, roles, guards, etc.
119
+ * ctx param is optional - use Context.get() for idiomatic access.
120
+ */
121
+ methods?: MethodsConfig;
122
+ }
123
+
124
+ // ============================================================================
125
+ // Storage
126
+ // ============================================================================
127
+
128
+ export interface StorageConfig {
129
+ /**
130
+ * Base directory for storage
131
+ * @default "~/.agentick"
132
+ */
133
+ directory?: string;
134
+
135
+ /**
136
+ * Enable session persistence
137
+ * @default true
138
+ */
139
+ sessions?: boolean;
140
+
141
+ /**
142
+ * Enable memory persistence
143
+ * @default true
144
+ */
145
+ memory?: boolean;
146
+ }
147
+
148
+ // ============================================================================
149
+ // Channels
150
+ // ============================================================================
151
+
152
+ export interface ChannelAdapter {
153
+ /**
154
+ * Channel identifier
155
+ */
156
+ id: string;
157
+
158
+ /**
159
+ * Human-readable name
160
+ */
161
+ name: string;
162
+
163
+ /**
164
+ * Initialize the channel
165
+ */
166
+ initialize(gateway: GatewayContext): Promise<void>;
167
+
168
+ /**
169
+ * Clean up resources
170
+ */
171
+ destroy(): Promise<void>;
172
+ }
173
+
174
+ export interface GatewayContext {
175
+ /**
176
+ * Send a message to a session
177
+ */
178
+ sendToSession(sessionId: string, message: string): Promise<void>;
179
+
180
+ /**
181
+ * Get available apps
182
+ */
183
+ getApps(): string[];
184
+
185
+ /**
186
+ * Get or create a session
187
+ */
188
+ getSession(sessionId: string): SessionContext;
189
+ }
190
+
191
+ export interface SessionContext {
192
+ id: string;
193
+ appId: string;
194
+ send(message: string): AsyncGenerator<SessionEvent>;
195
+ }
196
+
197
+ export interface SessionEvent {
198
+ type: string;
199
+ data: unknown;
200
+ }
201
+
202
+ // ============================================================================
203
+ // Routing
204
+ // ============================================================================
205
+
206
+ export interface RoutingConfig {
207
+ /**
208
+ * Map channels to agents
209
+ */
210
+ channels?: Record<string, string>;
211
+
212
+ /**
213
+ * Custom routing function
214
+ */
215
+ custom?: (message: IncomingMessage, context: RoutingContext) => string | null;
216
+ }
217
+
218
+ export interface IncomingMessage {
219
+ text: string;
220
+ channel?: string;
221
+ from?: string;
222
+ metadata?: Record<string, unknown>;
223
+ }
224
+
225
+ export interface RoutingContext {
226
+ availableApps: string[];
227
+ defaultApp: string;
228
+ sessionHistory?: Array<{ role: string; content: string }>;
229
+ }
230
+
231
+ // ============================================================================
232
+ // Client State
233
+ // ============================================================================
234
+
235
+ export interface ClientState {
236
+ id: string;
237
+ connectedAt: Date;
238
+ authenticated: boolean;
239
+ /** Full user context from auth */
240
+ user?: UserContext;
241
+ subscriptions: Set<string>;
242
+ metadata?: Record<string, unknown>;
243
+ }
244
+
245
+ // ============================================================================
246
+ // Session State
247
+ // ============================================================================
248
+
249
+ export interface SessionState {
250
+ id: string;
251
+ appId: string;
252
+ createdAt: Date;
253
+ lastActivityAt: Date;
254
+ messageCount: number;
255
+ isActive: boolean;
256
+ subscribers: Set<string>;
257
+ }
258
+
259
+ // ============================================================================
260
+ // Events
261
+ // ============================================================================
262
+
263
+ export interface GatewayEvents {
264
+ started: { port: number; host: string };
265
+ stopped: Record<string, never>;
266
+ "client:connected": { clientId: string; ip?: string };
267
+ "client:disconnected": { clientId: string; reason?: string };
268
+ "client:authenticated": { clientId: string; user?: UserContext };
269
+ "session:created": { sessionId: string; appId: string };
270
+ "session:closed": { sessionId: string };
271
+ "session:message": {
272
+ sessionId: string;
273
+ role: "user" | "assistant";
274
+ content: string;
275
+ };
276
+ "app:message": {
277
+ appId: string;
278
+ sessionId: string;
279
+ message: string;
280
+ };
281
+ "channel:message": {
282
+ channel: string;
283
+ from: string;
284
+ message: string;
285
+ };
286
+ "channel:error": {
287
+ channel: string;
288
+ error: Error;
289
+ };
290
+ error: Error;
291
+ }
292
+
293
+ // ============================================================================
294
+ // Custom Methods
295
+ // ============================================================================
296
+
297
+ /** Symbol for detecting method definitions vs namespaces */
298
+ export const METHOD_DEFINITION = Symbol.for("agentick:method-definition");
299
+
300
+ /**
301
+ * Simple method handler - ctx param is optional since Context.get() works
302
+ */
303
+ export type SimpleMethodHandler<TParams = Record<string, unknown>, TResult = unknown> = (
304
+ params: TParams,
305
+ ctx?: KernelContext,
306
+ ) => Promise<TResult> | TResult;
307
+
308
+ /**
309
+ * Streaming method handler - yields values to client
310
+ */
311
+ export type StreamingMethodHandler<TParams = Record<string, unknown>, TYield = unknown> = (
312
+ params: TParams,
313
+ ctx?: KernelContext,
314
+ ) => AsyncGenerator<TYield>;
315
+
316
+ /**
317
+ * Method definition input (what you pass to method())
318
+ */
319
+ export interface MethodDefinitionInput<TSchema extends ZodLikeSchema = ZodLikeSchema> {
320
+ /** Zod schema for params validation + TypeScript inference */
321
+ schema?: TSchema;
322
+ /** Handler function - receives validated & typed params */
323
+ handler: SimpleMethodHandler<TSchema["_output"]> | StreamingMethodHandler<TSchema["_output"]>;
324
+ /** Required roles - checked before handler */
325
+ roles?: string[];
326
+ /** Custom guard function */
327
+ guard?: (ctx: KernelContext) => boolean | Promise<boolean>;
328
+ /** Method description for discovery */
329
+ description?: string;
330
+ }
331
+
332
+ /**
333
+ * Method definition with symbol marker (returned by method())
334
+ */
335
+ export interface MethodDefinition<
336
+ TSchema extends ZodLikeSchema = ZodLikeSchema,
337
+ > extends MethodDefinitionInput<TSchema> {
338
+ [METHOD_DEFINITION]: true;
339
+ }
340
+
341
+ /**
342
+ * Factory function to create a method definition.
343
+ * Stores config (schema, roles, guards) - the gateway creates the
344
+ * actual procedure during initialization with the full inferred path name.
345
+ *
346
+ * @example
347
+ * methods: {
348
+ * tasks: {
349
+ * list: async (params) => { ... }, // Simple - auto-wrapped
350
+ * create: method({ // With config
351
+ * schema: z.object({ title: z.string() }),
352
+ * handler: async (params) => { ... }
353
+ * }),
354
+ * }
355
+ * }
356
+ */
357
+ export function method<TSchema extends ZodLikeSchema>(
358
+ definition: MethodDefinitionInput<TSchema>,
359
+ ): MethodDefinition<TSchema> {
360
+ return {
361
+ [METHOD_DEFINITION]: true,
362
+ ...definition,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Check if a value is a method definition (vs a namespace)
368
+ */
369
+ export function isMethodDefinition(value: unknown): value is MethodDefinition {
370
+ return typeof value === "object" && value !== null && METHOD_DEFINITION in value;
371
+ }
372
+
373
+ /**
374
+ * Method can be:
375
+ * - Simple function: async (params) => result
376
+ * - Streaming function: async function* (params) { yield }
377
+ * - Method definition: method({ schema, handler, roles, ... })
378
+ */
379
+
380
+ export type Method = SimpleMethodHandler | StreamingMethodHandler | MethodDefinition<any>;
381
+
382
+ /**
383
+ * Method namespace - recursively nested, arbitrary depth
384
+ */
385
+ export type MethodNamespace = {
386
+ [key: string]: Method | MethodNamespace;
387
+ };
388
+
389
+ /**
390
+ * Methods config - supports flat or nested namespaces
391
+ */
392
+ export type MethodsConfig = MethodNamespace;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * WebSocket Server
3
+ *
4
+ * Handles WebSocket connections and message routing.
5
+ */
6
+
7
+ import { WebSocketServer, WebSocket } from "ws";
8
+ import type { IncomingMessage } from "http";
9
+ import type {
10
+ ClientMessage,
11
+ GatewayMessage,
12
+ ConnectMessage,
13
+ RequestMessage,
14
+ } from "./transport-protocol.js";
15
+ import type { ClientState, AuthConfig, AuthResult } from "./types.js";
16
+
17
+ export interface WSServerConfig {
18
+ port: number;
19
+ host: string;
20
+ auth?: AuthConfig;
21
+ }
22
+
23
+ export interface WSServerEvents {
24
+ connection: (client: WSClient) => void;
25
+ disconnect: (clientId: string, reason?: string) => void;
26
+ message: (clientId: string, message: ClientMessage) => void;
27
+ error: (error: Error) => void;
28
+ }
29
+
30
+ export class WSClient {
31
+ readonly id: string;
32
+ readonly socket: WebSocket;
33
+ readonly state: ClientState;
34
+ private server: WSServer;
35
+
36
+ constructor(id: string, socket: WebSocket, server: WSServer) {
37
+ this.id = id;
38
+ this.socket = socket;
39
+ this.server = server;
40
+ this.state = {
41
+ id,
42
+ connectedAt: new Date(),
43
+ authenticated: false,
44
+ subscriptions: new Set(),
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Send a message to this client
50
+ */
51
+ send(message: GatewayMessage): void {
52
+ if (this.socket.readyState === WebSocket.OPEN) {
53
+ this.socket.send(JSON.stringify(message));
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Close the connection
59
+ */
60
+ close(code?: number, reason?: string): void {
61
+ this.socket.close(code, reason);
62
+ }
63
+
64
+ /**
65
+ * Check if connected
66
+ */
67
+ get isConnected(): boolean {
68
+ return this.socket.readyState === WebSocket.OPEN;
69
+ }
70
+ }
71
+
72
+ export class WSServer {
73
+ private wss: WebSocketServer | null = null;
74
+ private clients = new Map<string, WSClient>();
75
+ private config: WSServerConfig;
76
+ private handlers: Partial<WSServerEvents> = {};
77
+ private clientIdCounter = 0;
78
+
79
+ constructor(config: WSServerConfig) {
80
+ this.config = config;
81
+ }
82
+
83
+ /**
84
+ * Start the WebSocket server
85
+ */
86
+ start(): Promise<void> {
87
+ return new Promise((resolve, reject) => {
88
+ try {
89
+ this.wss = new WebSocketServer({
90
+ port: this.config.port,
91
+ host: this.config.host,
92
+ });
93
+
94
+ this.wss.on("connection", this.handleConnection.bind(this));
95
+ this.wss.on("error", (error) => {
96
+ this.handlers.error?.(error);
97
+ reject(error);
98
+ });
99
+
100
+ this.wss.on("listening", () => {
101
+ resolve();
102
+ });
103
+ } catch (error) {
104
+ reject(error);
105
+ }
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Stop the server
111
+ */
112
+ stop(): Promise<void> {
113
+ return new Promise((resolve) => {
114
+ if (!this.wss) {
115
+ resolve();
116
+ return;
117
+ }
118
+
119
+ // Close all client connections
120
+ for (const client of this.clients.values()) {
121
+ client.close(1001, "Server shutting down");
122
+ }
123
+ this.clients.clear();
124
+
125
+ // Close the server
126
+ this.wss.close(() => {
127
+ this.wss = null;
128
+ resolve();
129
+ });
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Register event handlers
135
+ */
136
+ on<K extends keyof WSServerEvents>(event: K, handler: WSServerEvents[K]): void {
137
+ this.handlers[event] = handler;
138
+ }
139
+
140
+ /**
141
+ * Get a client by ID
142
+ */
143
+ getClient(id: string): WSClient | undefined {
144
+ return this.clients.get(id);
145
+ }
146
+
147
+ /**
148
+ * Get all clients
149
+ */
150
+ getClients(): WSClient[] {
151
+ return Array.from(this.clients.values());
152
+ }
153
+
154
+ /**
155
+ * Get authenticated clients
156
+ */
157
+ getAuthenticatedClients(): WSClient[] {
158
+ return this.getClients().filter((c) => c.state.authenticated);
159
+ }
160
+
161
+ /**
162
+ * Broadcast a message to all authenticated clients
163
+ */
164
+ broadcast(message: GatewayMessage): void {
165
+ for (const client of this.getAuthenticatedClients()) {
166
+ client.send(message);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Send a message to clients subscribed to a session
172
+ */
173
+ sendToSubscribers(sessionId: string, message: GatewayMessage): void {
174
+ for (const client of this.getAuthenticatedClients()) {
175
+ if (client.state.subscriptions.has(sessionId)) {
176
+ client.send(message);
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Get connected client count
183
+ */
184
+ get clientCount(): number {
185
+ return this.clients.size;
186
+ }
187
+
188
+ private handleConnection(socket: WebSocket, _request: IncomingMessage): void {
189
+ const clientId = `client-${++this.clientIdCounter}`;
190
+ const client = new WSClient(clientId, socket, this);
191
+ this.clients.set(clientId, client);
192
+
193
+ socket.on("message", (data) => {
194
+ try {
195
+ const message = JSON.parse(data.toString()) as ClientMessage;
196
+ this.handleMessage(client, message);
197
+ } catch (error) {
198
+ client.send({
199
+ type: "error",
200
+ code: "INVALID_MESSAGE",
201
+ message: "Failed to parse message",
202
+ });
203
+ }
204
+ });
205
+
206
+ socket.on("close", () => {
207
+ this.clients.delete(clientId);
208
+ this.handlers.disconnect?.(clientId);
209
+ });
210
+
211
+ socket.on("error", (error) => {
212
+ this.handlers.error?.(error);
213
+ });
214
+
215
+ // Notify handler of new connection (before auth)
216
+ this.handlers.connection?.(client);
217
+ }
218
+
219
+ private async handleMessage(client: WSClient, message: ClientMessage): Promise<void> {
220
+ // Handle connect message (authentication)
221
+ if (message.type === "connect") {
222
+ await this.handleConnect(client, message);
223
+ return;
224
+ }
225
+
226
+ // Handle ping
227
+ if (message.type === "ping") {
228
+ client.send({ type: "pong", timestamp: message.timestamp });
229
+ return;
230
+ }
231
+
232
+ // All other messages require authentication
233
+ if (!client.state.authenticated) {
234
+ client.send({
235
+ type: "error",
236
+ code: "UNAUTHORIZED",
237
+ message: "Authentication required. Send connect message first.",
238
+ });
239
+ return;
240
+ }
241
+
242
+ // Forward to message handler
243
+ this.handlers.message?.(client.id, message);
244
+ }
245
+
246
+ private async handleConnect(client: WSClient, message: ConnectMessage): Promise<void> {
247
+ // Validate authentication
248
+ const authResult = await this.validateAuth(message.token);
249
+
250
+ if (!authResult.valid) {
251
+ client.send({
252
+ type: "error",
253
+ code: "AUTH_FAILED",
254
+ message: "Authentication failed",
255
+ });
256
+ client.close(4001, "Authentication failed");
257
+ return;
258
+ }
259
+
260
+ // Update client state
261
+ client.state.authenticated = true;
262
+ client.state.user = authResult.user;
263
+ client.state.metadata = {
264
+ ...client.state.metadata,
265
+ ...authResult.metadata,
266
+ ...message.metadata,
267
+ };
268
+
269
+ // Client ID from message takes precedence
270
+ if (message.clientId) {
271
+ // Update internal tracking if client provides their own ID
272
+ this.clients.delete(client.id);
273
+ (client as { id: string }).id = message.clientId;
274
+ this.clients.set(message.clientId, client);
275
+ }
276
+ }
277
+
278
+ private async validateAuth(token?: string): Promise<AuthResult> {
279
+ const auth = this.config.auth;
280
+
281
+ // No auth configured
282
+ if (!auth || auth.type === "none") {
283
+ return { valid: true };
284
+ }
285
+
286
+ // Token auth
287
+ if (auth.type === "token") {
288
+ return { valid: token === auth.token };
289
+ }
290
+
291
+ // JWT auth
292
+ if (auth.type === "jwt") {
293
+ return { valid: false };
294
+ }
295
+
296
+ // Custom auth
297
+ if (auth.type === "custom") {
298
+ return await auth.validate(token ?? "");
299
+ }
300
+
301
+ return { valid: false };
302
+ }
303
+ }