@microsoft/agents-hosting-express 1.6.0-beta.24.g285b02c21b → 1.6.0-beta.30.g545c41a189

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/README.md CHANGED
@@ -2,17 +2,118 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- Provides integration to host the agent in Express using `startServer`
5
+ Provides integration to host agents with Express. The package offers three levels of abstraction:
6
+
7
+ - **`startServer`** — Quickest way to get running. Creates an Express app, wires up auth, and starts listening.
8
+ - **`createAgentRequestHandler`** — A handler signature without Express imports, for frameworks that can provide Express-compatible request/response objects.
9
+ - **`createCloudAdapter`** — Low-level factory to get a `CloudAdapter` for full control over request processing.
6
10
 
7
11
  ## Usage
8
12
 
9
- ```ts
10
- import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
11
- import { startServer } from '@microsoft/agents-hosting-express';
12
-
13
- const app = new AgentApplication<TurnState>();
14
- app.onMessage('hello', async (context, state) => {
15
- await context.sendActivity('Hello, world!');
16
- });
17
- startServer(app);
18
- ```
13
+ ### Basic — `startServer`
14
+
15
+ ```ts
16
+ import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
17
+ import { startServer } from '@microsoft/agents-hosting-express';
18
+
19
+ const app = new AgentApplication<TurnState>();
20
+ app.onActivity('message', async (context) => {
21
+ await context.sendActivity('Hello, world!');
22
+ });
23
+ startServer(app);
24
+ ```
25
+
26
+ ### Custom routes alongside the agent endpoint
27
+
28
+ Use the `beforeListen` hook to add routes before the server starts listening:
29
+
30
+ _**Note:** The agent messages route is registered *after* this callback.
31
+ Avoid adding catch-all routes (e.g., `app.all('*', ...)` or `app.use('*', ...)`)
32
+ here, as they will shadow the messages endpoint._
33
+
34
+ ```ts
35
+ import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
36
+ import { startServer } from '@microsoft/agents-hosting-express';
37
+
38
+ const agent = new AgentApplication<TurnState>();
39
+
40
+ startServer(agent, {
41
+ port: 8080,
42
+ routePath: '/bot/messages',
43
+ beforeListen: (app) => {
44
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
45
+ }
46
+ });
47
+ ```
48
+
49
+ ### Custom Express setup — `createAgentRequestHandler`
50
+
51
+ If you manage your own Express app (or another framework with an adapter that exposes Express-compatible request/response objects), use `createAgentRequestHandler` to get a handler that includes JWT authorization and activity processing:
52
+
53
+ ```ts
54
+ import express from 'express';
55
+ import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
56
+ import { createAgentRequestHandler } from '@microsoft/agents-hosting-express';
57
+
58
+ const agent = new AgentApplication<TurnState>();
59
+ const handler = createAgentRequestHandler(agent);
60
+
61
+ const app = express();
62
+ app.use(express.json());
63
+ app.post('/api/messages', handler);
64
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
65
+ app.listen(3978);
66
+ ```
67
+
68
+ ### Advanced — `createCloudAdapter`
69
+
70
+ For full control, use `createCloudAdapter` to obtain the `CloudAdapter` directly. This is useful when you need to customize request processing and can provide the request/response shape expected by `CloudAdapter.process`.
71
+
72
+ `CloudAdapter.process` expects:
73
+
74
+ - a parsed activity body on `req.body` (for example, via `express.json()` in Express)
75
+ - a response object that supports `status()`, `setHeader()`, `send()`, and `end()`
76
+
77
+ If your framework does not expose those members directly, add an adapter layer before calling `adapter.process`.
78
+
79
+ ```ts
80
+ import http from 'node:http';
81
+ import { AgentApplication, TurnState, getAuthConfigWithDefaults } from '@microsoft/agents-hosting';
82
+ import { createCloudAdapter } from '@microsoft/agents-hosting-express';
83
+
84
+ const agent = new AgentApplication<TurnState>();
85
+ const authConfig = getAuthConfigWithDefaults();
86
+ const { adapter, headerPropagation } = createCloudAdapter(agent, authConfig);
87
+
88
+ const server = http.createServer(async (req, res) => {
89
+ if (req.method === 'POST' && req.url === '/api/messages') {
90
+ // Adapt request/response as needed so they match CloudAdapter.process expectations.
91
+ await adapter.process(req as any, res as any, (context) => agent.run(context), headerPropagation);
92
+ }
93
+ });
94
+ server.listen(3978);
95
+ ```
96
+
97
+ ### Setting up auth middleware manually
98
+
99
+ If you need to apply JWT authorization on specific routes in your own Express app:
100
+
101
+ ```ts
102
+ import express from 'express';
103
+ import { authorizeJWT, getAuthConfigWithDefaults, AgentApplication, TurnState } from '@microsoft/agents-hosting';
104
+ import { createCloudAdapter } from '@microsoft/agents-hosting-express';
105
+
106
+ const agent = new AgentApplication<TurnState>();
107
+ const authConfig = getAuthConfigWithDefaults();
108
+ const { adapter, headerPropagation } = createCloudAdapter(agent, authConfig);
109
+
110
+ const app = express();
111
+ app.use(express.json());
112
+
113
+ // JWT is applied only on the agent route — custom routes remain unauthenticated
114
+ app.post('/api/messages', authorizeJWT(authConfig), (req, res) =>
115
+ adapter.process(req, res, (context) => agent.run(context), headerPropagation)
116
+ );
117
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
118
+ app.listen(3978);
119
+ ```
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@microsoft/agents-hosting-express",
4
- "version": "1.6.0-beta.24.g285b02c21b",
4
+ "version": "1.6.0-beta.30.g545c41a189",
5
5
  "homepage": "https://github.com/microsoft/Agents-for-js",
6
6
  "repository": {
7
7
  "type": "git",
@@ -19,8 +19,10 @@
19
19
  "main": "dist/src/index.js",
20
20
  "types": "dist/src/index.d.ts",
21
21
  "dependencies": {
22
- "@microsoft/agents-hosting": "1.6.0-beta.24.g285b02c21b",
23
- "express": "5.2.1"
22
+ "@microsoft/agents-hosting": "1.6.0-beta.30.g545c41a189",
23
+ "@microsoft/agents-telemetry": "1.6.0-beta.30.g545c41a189",
24
+ "express": "5.2.1",
25
+ "express-rate-limit": "8.5.2"
24
26
  },
25
27
  "license": "MIT",
26
28
  "files": [
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { ActivityHandler, AgentApplication, AuthConfiguration, Request, TurnState } from '@microsoft/agents-hosting';
6
+ /**
7
+ * Minimal response interface describing the methods used by the Agent request handler.
8
+ * Any framework whose response object satisfies this shape is compatible.
9
+ */
10
+ export interface WebResponse {
11
+ status(code: number): this;
12
+ setHeader(name: string, value: string): this;
13
+ send(body?: unknown): this;
14
+ end(): this;
15
+ headersSent: boolean;
16
+ writableEnded: boolean;
17
+ }
18
+ /**
19
+ * A request handler function signature that does not import Express types in its public API.
20
+ *
21
+ * @remarks
22
+ * Runtime processing still depends on the request/response behavior expected by `authorizeJWT` and `CloudAdapter.process`.
23
+ * Use this with Express directly, or with adapter layers that provide compatible request/response objects.
24
+ */
25
+ export type AgentRequestHandler = (req: Request, res: WebResponse) => Promise<void>;
26
+ /**
27
+ * Creates a request handler for processing Agent activities.
28
+ *
29
+ * This exposes a handler signature without requiring Express types in consumer code.
30
+ * It can be used with Express directly, or with frameworks that provide adapted objects compatible with
31
+ * the requirements of `authorizeJWT` and `CloudAdapter.process`.
32
+ *
33
+ * JWT authorization is applied within the handler before processing the activity.
34
+ * Requests must provide a parsed activity payload at `req.body`.
35
+ *
36
+ * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
37
+ * @param authConfiguration - Optional custom authentication configuration. If not provided,
38
+ * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
39
+ * @returns A request handler function `(req, res) => Promise<void>`.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import express from 'express';
44
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
45
+ * import { createAgentRequestHandler } from '@microsoft/agents-hosting-express';
46
+ *
47
+ * const agent = new AgentApplication<TurnState>();
48
+ * const handler = createAgentRequestHandler(agent);
49
+ *
50
+ * const app = express();
51
+ * app.use(express.json());
52
+ * app.post('/api/messages', handler);
53
+ * app.get('/health', (req, res) => res.json({ status: 'ok' }));
54
+ * app.listen(3978);
55
+ * ```
56
+ */
57
+ export declare const createAgentRequestHandler: (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfiguration?: AuthConfiguration) => AgentRequestHandler;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createAgentRequestHandler = void 0;
8
+ const agents_hosting_1 = require("@microsoft/agents-hosting");
9
+ const createCloudAdapter_1 = require("./createCloudAdapter");
10
+ /**
11
+ * Creates a request handler for processing Agent activities.
12
+ *
13
+ * This exposes a handler signature without requiring Express types in consumer code.
14
+ * It can be used with Express directly, or with frameworks that provide adapted objects compatible with
15
+ * the requirements of `authorizeJWT` and `CloudAdapter.process`.
16
+ *
17
+ * JWT authorization is applied within the handler before processing the activity.
18
+ * Requests must provide a parsed activity payload at `req.body`.
19
+ *
20
+ * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
21
+ * @param authConfiguration - Optional custom authentication configuration. If not provided,
22
+ * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
23
+ * @returns A request handler function `(req, res) => Promise<void>`.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import express from 'express';
28
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
29
+ * import { createAgentRequestHandler } from '@microsoft/agents-hosting-express';
30
+ *
31
+ * const agent = new AgentApplication<TurnState>();
32
+ * const handler = createAgentRequestHandler(agent);
33
+ *
34
+ * const app = express();
35
+ * app.use(express.json());
36
+ * app.post('/api/messages', handler);
37
+ * app.get('/health', (req, res) => res.json({ status: 'ok' }));
38
+ * app.listen(3978);
39
+ * ```
40
+ */
41
+ const createAgentRequestHandler = (agent, authConfiguration) => {
42
+ const authConfig = (0, agents_hosting_1.getAuthConfigWithDefaults)(authConfiguration);
43
+ const { adapter, headerPropagation } = (0, createCloudAdapter_1.createCloudAdapter)(agent, authConfig);
44
+ const jwtMiddleware = (0, agents_hosting_1.authorizeJWT)(authConfig);
45
+ return async (req, res) => {
46
+ let middlewareError;
47
+ let nextCalled = false;
48
+ await jwtMiddleware(req, res, (err) => {
49
+ nextCalled = true;
50
+ middlewareError = err;
51
+ });
52
+ if (middlewareError) {
53
+ throw middlewareError;
54
+ }
55
+ // If the middleware handled the response without calling next (e.g., 401), don't process the activity.
56
+ if (!nextCalled || res.headersSent) {
57
+ return;
58
+ }
59
+ await adapter.process(req, res, (context) => agent.run(context), headerPropagation);
60
+ };
61
+ };
62
+ exports.createAgentRequestHandler = createAgentRequestHandler;
63
+ //# sourceMappingURL=createAgentRequestHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAgentRequestHandler.js","sourceRoot":"","sources":["../../src/createAgentRequestHandler.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,8DAA6J;AAC7J,6DAAyD;AAwBzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACI,MAAM,yBAAyB,GAAG,CACvC,KAA8D,EAC9D,iBAAqC,EAChB,EAAE;IACvB,MAAM,UAAU,GAAG,IAAA,0CAAyB,EAAC,iBAAiB,CAAC,CAAA;IAC/D,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAA,uCAAkB,EAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IAC5E,MAAM,aAAa,GAAG,IAAA,6BAAY,EAAC,UAAU,CAAC,CAAA;IAE9C,OAAO,KAAK,EAAE,GAAY,EAAE,GAAgB,EAAiB,EAAE;QAC7D,IAAI,eAAoB,CAAA;QACxB,IAAI,UAAU,GAAG,KAAK,CAAA;QAEtB,MAAM,aAAa,CAAC,GAAG,EAAE,GAAe,EAAE,CAAC,GAAS,EAAE,EAAE;YACtD,UAAU,GAAG,IAAI,CAAA;YACjB,eAAe,GAAG,GAAG,CAAA;QACvB,CAAC,CAAC,CAAA;QAEF,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,eAAe,CAAA;QACvB,CAAC;QAED,uGAAuG;QACvG,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACnC,OAAM;QACR,CAAC;QAED,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAA;IACjG,CAAC,CAAA;AACH,CAAC,CAAA;AA5BY,QAAA,yBAAyB,6BA4BrC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { ActivityHandler, AgentApplication, AuthConfiguration, CloudAdapter, HeaderPropagationDefinition, TurnState } from '@microsoft/agents-hosting';
6
+ /**
7
+ * Result of creating a CloudAdapter from an agent.
8
+ */
9
+ export interface CloudAdapterResult {
10
+ adapter: CloudAdapter;
11
+ headerPropagation: HeaderPropagationDefinition | undefined;
12
+ }
13
+ /**
14
+ * Creates a CloudAdapter for the given agent.
15
+ *
16
+ * If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
17
+ * Otherwise, a new CloudAdapter is created.
18
+ *
19
+ * @param agent - The AgentApplication or ActivityHandler instance.
20
+ * @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
21
+ * If the agent already has an adapter, that adapter is reused and this value is ignored.
22
+ * @returns An object containing the CloudAdapter and optional header propagation configuration.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
27
+ * import { createCloudAdapter } from '@microsoft/agents-hosting-express';
28
+ *
29
+ * const app = new AgentApplication<TurnState>();
30
+ * const { adapter, headerPropagation } = createCloudAdapter(app, { clientId: process.env.CLIENT_ID });
31
+ *
32
+ * // Use the adapter directly with request/response objects compatible with CloudAdapter.process
33
+ * adapter.process(req, res, (context) => app.run(context), headerPropagation);
34
+ * ```
35
+ */
36
+ export declare const createCloudAdapter: (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfig?: AuthConfiguration) => CloudAdapterResult;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createCloudAdapter = void 0;
8
+ const agents_hosting_1 = require("@microsoft/agents-hosting");
9
+ /**
10
+ * Creates a CloudAdapter for the given agent.
11
+ *
12
+ * If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
13
+ * Otherwise, a new CloudAdapter is created.
14
+ *
15
+ * @param agent - The AgentApplication or ActivityHandler instance.
16
+ * @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
17
+ * If the agent already has an adapter, that adapter is reused and this value is ignored.
18
+ * @returns An object containing the CloudAdapter and optional header propagation configuration.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
23
+ * import { createCloudAdapter } from '@microsoft/agents-hosting-express';
24
+ *
25
+ * const app = new AgentApplication<TurnState>();
26
+ * const { adapter, headerPropagation } = createCloudAdapter(app, { clientId: process.env.CLIENT_ID });
27
+ *
28
+ * // Use the adapter directly with request/response objects compatible with CloudAdapter.process
29
+ * adapter.process(req, res, (context) => app.run(context), headerPropagation);
30
+ * ```
31
+ */
32
+ const createCloudAdapter = (agent, authConfig) => {
33
+ let adapter;
34
+ let headerPropagation;
35
+ if (agent instanceof agents_hosting_1.ActivityHandler || !agent.adapter) {
36
+ adapter = new agents_hosting_1.CloudAdapter(authConfig);
37
+ }
38
+ else {
39
+ adapter = agent.adapter;
40
+ headerPropagation = agent === null || agent === void 0 ? void 0 : agent.options.headerPropagation;
41
+ }
42
+ return { adapter, headerPropagation };
43
+ };
44
+ exports.createCloudAdapter = createCloudAdapter;
45
+ //# sourceMappingURL=createCloudAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCloudAdapter.js","sourceRoot":"","sources":["../../src/createCloudAdapter.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,8DAAsJ;AAUtJ;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACI,MAAM,kBAAkB,GAAG,CAChC,KAA8D,EAC9D,UAA8B,EACV,EAAE;IACtB,IAAI,OAAqB,CAAA;IACzB,IAAI,iBAA0D,CAAA;IAC9D,IAAI,KAAK,YAAY,gCAAe,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACvD,OAAO,GAAG,IAAI,6BAAY,CAAC,UAAU,CAAC,CAAA;IACxC,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,KAAK,CAAC,OAAuB,CAAA;QACvC,iBAAiB,GAAI,KAA+C,aAA/C,KAAK,uBAAL,KAAK,CAA4C,OAAO,CAAC,iBAAiB,CAAA;IACjG,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAA;AACvC,CAAC,CAAA;AAbY,QAAA,kBAAkB,sBAa9B"}
@@ -1 +1,3 @@
1
1
  export * from './startServer';
2
+ export * from './createCloudAdapter';
3
+ export * from './createAgentRequestHandler';
package/dist/src/index.js CHANGED
@@ -15,4 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./startServer"), exports);
18
+ __exportStar(require("./createCloudAdapter"), exports);
19
+ __exportStar(require("./createAgentRequestHandler"), exports);
18
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA6B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA6B;AAC7B,uDAAoC;AACpC,8DAA2C"}
@@ -4,32 +4,98 @@
4
4
  */
5
5
  import express from 'express';
6
6
  import { ActivityHandler, AgentApplication, AuthConfiguration, TurnState } from '@microsoft/agents-hosting';
7
+ /**
8
+ * Options for configuring the Express server started by `startServer`.
9
+ */
10
+ export interface StartServerOptions {
11
+ /**
12
+ * Optional custom authentication configuration.
13
+ * If not provided, configuration will be loaded from environment variables using loadAuthConfigFromEnv().
14
+ */
15
+ authConfig?: AuthConfiguration;
16
+ /**
17
+ * The port to listen on. Defaults to `process.env.PORT` or `3978`.
18
+ */
19
+ port?: number | string;
20
+ /**
21
+ * The route path for the agent messages endpoint. Defaults to `'/api/messages'`.
22
+ */
23
+ routePath?: string;
24
+ /**
25
+ * Optional rate limiting configuration for the messages endpoint.
26
+ * If not provided, no rate limiting is applied.
27
+ * Specify windowMs (in milliseconds) and max (number of requests) to enable rate limiting.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * startServer(agent, {
32
+ * rateLimitOptions: {
33
+ * windowMs: 15 * 60 * 1000, // 15 minutes
34
+ * max: 1000 // limit each IP to 1000 requests per windowMs
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ rateLimitOptions?: {
40
+ windowMs: number;
41
+ max: number;
42
+ };
43
+ /**
44
+ * A callback invoked with the Express app before `listen()` is called.
45
+ * Use this to add custom routes, middleware, or static file serving.
46
+ *
47
+ * **Note:** The agent messages route is registered *after* this callback.
48
+ * Avoid adding catch-all routes (e.g., `app.all('*', ...)` or `app.use('*', ...)`)
49
+ * here, as they will shadow the messages endpoint.
50
+ * @example
51
+ * ```typescript
52
+ * startServer(agent, {
53
+ * beforeListen: (app) => {
54
+ * app.get('/health', (req, res) => res.json({ status: 'ok' }));
55
+ * }
56
+ * });
57
+ * ```
58
+ */
59
+ beforeListen?: (app: express.Express) => void;
60
+ }
7
61
  /**
8
62
  * Starts an Express server for handling Agent requests.
9
63
  *
10
64
  * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
11
- * @param authConfiguration - Optional custom authentication configuration. If not provided,
12
- * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
13
- * @returns {express.Express} - The Express server instance.
65
+ * @param options - Optional configuration. Accepts either a `StartServerOptions` object or
66
+ * an `AuthConfiguration` for backward compatibility.
67
+ * @returns The Express server instance.
14
68
  *
15
69
  * @remarks
16
70
  * This function sets up an Express server with the necessary middleware and routes for handling
17
- * agent requests. It configures JWT authorization middleware and sets up the message endpoint.
18
- * The server will listen on the port specified in the PORT environment variable (or 3978 by default)
19
- * and logs startup information including the SDK version and configured app ID.
71
+ * agent requests. It configures JWT authorization middleware on the messages route and sets up the endpoint.
72
+ * The server will listen on the port specified in options, the PORT environment variable, or 3978 by default.
20
73
  *
21
74
  * @example
22
75
  * ```typescript
76
+ * // Basic usage
23
77
  * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
24
78
  * import { startServer } from '@microsoft/agents-hosting-express';
25
79
  *
26
80
  * const app = new AgentApplication<TurnState>();
27
- * app.onMessage('hello', async (context, state) => {
28
- * await context.sendActivity('Hello, world!');
29
- * });
30
- *
31
81
  * startServer(app);
32
82
  * ```
33
83
  *
84
+ * @example
85
+ * ```typescript
86
+ * // With options
87
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
88
+ * import { startServer } from '@microsoft/agents-hosting-express';
89
+ *
90
+ * const app = new AgentApplication<TurnState>();
91
+ * startServer(app, {
92
+ * port: 8080,
93
+ * routePath: '/bot/messages',
94
+ * beforeListen: (server) => {
95
+ * server.get('/health', (req, res) => res.json({ status: 'ok' }));
96
+ * }
97
+ * });
98
+ * ```
34
99
  */
35
- export declare const startServer: (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfiguration?: AuthConfiguration) => express.Express;
100
+ export declare function startServer(agent: AgentApplication<TurnState<any, any>> | ActivityHandler, options?: StartServerOptions): express.Express;
101
+ export declare function startServer(agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfiguration?: AuthConfiguration): express.Express;
@@ -7,58 +7,56 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.startServer = void 0;
10
+ exports.startServer = startServer;
11
11
  const express_1 = __importDefault(require("express"));
12
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
12
13
  const agents_hosting_1 = require("@microsoft/agents-hosting");
13
14
  const package_json_1 = require("@microsoft/agents-hosting/package.json");
14
- /**
15
- * Starts an Express server for handling Agent requests.
16
- *
17
- * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
18
- * @param authConfiguration - Optional custom authentication configuration. If not provided,
19
- * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
20
- * @returns {express.Express} - The Express server instance.
21
- *
22
- * @remarks
23
- * This function sets up an Express server with the necessary middleware and routes for handling
24
- * agent requests. It configures JWT authorization middleware and sets up the message endpoint.
25
- * The server will listen on the port specified in the PORT environment variable (or 3978 by default)
26
- * and logs startup information including the SDK version and configured app ID.
27
- *
28
- * @example
29
- * ```typescript
30
- * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
31
- * import { startServer } from '@microsoft/agents-hosting-express';
32
- *
33
- * const app = new AgentApplication<TurnState>();
34
- * app.onMessage('hello', async (context, state) => {
35
- * await context.sendActivity('Hello, world!');
36
- * });
37
- *
38
- * startServer(app);
39
- * ```
40
- *
41
- */
42
- const startServer = (agent, authConfiguration) => {
43
- const authConfig = (0, agents_hosting_1.getAuthConfigWithDefaults)(authConfiguration);
44
- let adapter;
45
- let headerPropagation;
46
- if (agent instanceof agents_hosting_1.ActivityHandler || !agent.adapter) {
47
- adapter = new agents_hosting_1.CloudAdapter();
48
- }
49
- else {
50
- adapter = agent.adapter;
51
- headerPropagation = agent === null || agent === void 0 ? void 0 : agent.options.headerPropagation;
52
- }
15
+ const agents_telemetry_1 = require("@microsoft/agents-telemetry");
16
+ const createCloudAdapter_1 = require("./createCloudAdapter");
17
+ const logger = (0, agents_telemetry_1.debug)('agents:hosting-express');
18
+ function startServer(agent, optionsOrAuth) {
19
+ var _a, _b, _c;
20
+ const isOptions = typeof optionsOrAuth === 'object' && optionsOrAuth !== null &&
21
+ ('authConfig' in optionsOrAuth || 'port' in optionsOrAuth || 'routePath' in optionsOrAuth || 'rateLimitOptions' in optionsOrAuth || 'beforeListen' in optionsOrAuth);
22
+ const opts = isOptions ? optionsOrAuth : { authConfig: optionsOrAuth };
23
+ const authConfig = (0, agents_hosting_1.getAuthConfigWithDefaults)(opts.authConfig);
24
+ const routePath = (_a = opts.routePath) !== null && _a !== void 0 ? _a : '/api/messages';
25
+ const { adapter, headerPropagation } = (0, createCloudAdapter_1.createCloudAdapter)(agent, authConfig);
53
26
  const server = (0, express_1.default)();
54
27
  server.use(express_1.default.json());
55
- server.use((0, agents_hosting_1.authorizeJWT)(authConfig));
56
- server.post('/api/messages', (req, res) => adapter.process(req, res, (context) => agent.run(context), headerPropagation));
57
- const port = process.env.PORT || 3978;
28
+ if (opts.beforeListen && typeof opts.beforeListen === 'function') {
29
+ opts.beforeListen(server);
30
+ }
31
+ const middlewares = [(0, agents_hosting_1.authorizeJWT)(authConfig)];
32
+ if (opts.rateLimitOptions) {
33
+ const messagesRateLimiter = (0, express_rate_limit_1.default)({
34
+ windowMs: opts.rateLimitOptions.windowMs,
35
+ max: opts.rateLimitOptions.max,
36
+ standardHeaders: true,
37
+ legacyHeaders: false
38
+ });
39
+ middlewares.unshift(messagesRateLimiter);
40
+ }
41
+ server.post(routePath, ...middlewares, (req, res) => adapter.process(req, res, (context) => agent.run(context), headerPropagation));
42
+ const port = (_c = (_b = opts.port) !== null && _b !== void 0 ? _b : process.env.PORT) !== null && _c !== void 0 ? _c : 3978;
43
+ const className = (obj) => { var _a, _b; return (_b = (_a = obj === null || obj === void 0 ? void 0 : obj.constructor) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : (obj ? 'custom' : undefined); };
44
+ logger.info('Express server settings loaded', {
45
+ messageEndpoint: `POST ${routePath}`,
46
+ port: {
47
+ value: port,
48
+ source: opts.port ? 'options' : process.env.PORT ? 'env' : 'default',
49
+ },
50
+ middlewares: ['express.json', 'authorizeJWT'],
51
+ adapter: {
52
+ className: className(adapter),
53
+ source: agent instanceof agents_hosting_1.ActivityHandler || !agent.adapter ? 'created' : 'agent.adapter',
54
+ },
55
+ headerPropagation: headerPropagation !== undefined ? 'enabled' : 'disabled',
56
+ });
58
57
  server.listen(port, async () => {
59
58
  console.log(`\nServer listening to port ${port} on sdk ${package_json_1.version} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`);
60
59
  }).on('error', console.error);
61
60
  return server;
62
- };
63
- exports.startServer = startServer;
61
+ }
64
62
  //# sourceMappingURL=startServer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"startServer.js","sourceRoot":"","sources":["../../src/startServer.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,sDAA2C;AAC3C,8DAAwM;AACxM,yEAAgE;AAChE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACI,MAAM,WAAW,GAAG,CAAC,KAA8D,EAAE,iBAAqC,EAAoB,EAAE;IACrJ,MAAM,UAAU,GAAsB,IAAA,0CAAyB,EAAC,iBAAiB,CAAC,CAAA;IAClF,IAAI,OAAqB,CAAA;IACzB,IAAI,iBAA0D,CAAA;IAC9D,IAAI,KAAK,YAAY,gCAAe,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACvD,OAAO,GAAG,IAAI,6BAAY,EAAE,CAAA;IAC9B,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,KAAK,CAAC,OAAuB,CAAA;QACvC,iBAAiB,GAAI,KAA+C,aAA/C,KAAK,uBAAL,KAAK,CAA4C,OAAO,CAAC,iBAAiB,CAAA;IACjG,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,iBAAO,GAAE,CAAA;IACxB,MAAM,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC1B,MAAM,CAAC,GAAG,CAAC,IAAA,6BAAY,EAAC,UAAU,CAAC,CAAC,CAAA;IAEpC,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE,CAC3D,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CACpC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAClB,iBAAiB,CAAC,CACrB,CAAA;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAA;IACrC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QAC7B,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,WAAW,sBAAO,cAAc,UAAU,CAAC,QAAQ,UAAU,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAA;IACjI,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAC7B,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AA1BY,QAAA,WAAW,eA0BvB"}
1
+ {"version":3,"file":"startServer.js","sourceRoot":"","sources":["../../src/startServer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@microsoft/agents-hosting-express",
4
- "version": "1.6.0-beta.24.g285b02c21b",
4
+ "version": "1.6.0-beta.30.g545c41a189",
5
5
  "homepage": "https://github.com/microsoft/Agents-for-js",
6
6
  "repository": {
7
7
  "type": "git",
@@ -19,8 +19,10 @@
19
19
  "main": "dist/src/index.js",
20
20
  "types": "dist/src/index.d.ts",
21
21
  "dependencies": {
22
- "@microsoft/agents-hosting": "1.6.0-beta.24.g285b02c21b",
23
- "express": "5.2.1"
22
+ "@microsoft/agents-hosting": "1.6.0-beta.30.g545c41a189",
23
+ "@microsoft/agents-telemetry": "1.6.0-beta.30.g545c41a189",
24
+ "express": "5.2.1",
25
+ "express-rate-limit": "8.5.2"
24
26
  },
25
27
  "license": "MIT",
26
28
  "files": [
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { type Response } from 'express'
7
+ import { ActivityHandler, AgentApplication, AuthConfiguration, authorizeJWT, getAuthConfigWithDefaults, Request, TurnState } from '@microsoft/agents-hosting'
8
+ import { createCloudAdapter } from './createCloudAdapter'
9
+
10
+ /**
11
+ * Minimal response interface describing the methods used by the Agent request handler.
12
+ * Any framework whose response object satisfies this shape is compatible.
13
+ */
14
+ export interface WebResponse {
15
+ status (code: number): this
16
+ setHeader (name: string, value: string): this
17
+ send (body?: unknown): this
18
+ end (): this
19
+ headersSent: boolean
20
+ writableEnded: boolean
21
+ }
22
+
23
+ /**
24
+ * A request handler function signature that does not import Express types in its public API.
25
+ *
26
+ * @remarks
27
+ * Runtime processing still depends on the request/response behavior expected by `authorizeJWT` and `CloudAdapter.process`.
28
+ * Use this with Express directly, or with adapter layers that provide compatible request/response objects.
29
+ */
30
+ export type AgentRequestHandler = (req: Request, res: WebResponse) => Promise<void>
31
+
32
+ /**
33
+ * Creates a request handler for processing Agent activities.
34
+ *
35
+ * This exposes a handler signature without requiring Express types in consumer code.
36
+ * It can be used with Express directly, or with frameworks that provide adapted objects compatible with
37
+ * the requirements of `authorizeJWT` and `CloudAdapter.process`.
38
+ *
39
+ * JWT authorization is applied within the handler before processing the activity.
40
+ * Requests must provide a parsed activity payload at `req.body`.
41
+ *
42
+ * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
43
+ * @param authConfiguration - Optional custom authentication configuration. If not provided,
44
+ * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
45
+ * @returns A request handler function `(req, res) => Promise<void>`.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * import express from 'express';
50
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
51
+ * import { createAgentRequestHandler } from '@microsoft/agents-hosting-express';
52
+ *
53
+ * const agent = new AgentApplication<TurnState>();
54
+ * const handler = createAgentRequestHandler(agent);
55
+ *
56
+ * const app = express();
57
+ * app.use(express.json());
58
+ * app.post('/api/messages', handler);
59
+ * app.get('/health', (req, res) => res.json({ status: 'ok' }));
60
+ * app.listen(3978);
61
+ * ```
62
+ */
63
+ export const createAgentRequestHandler = (
64
+ agent: AgentApplication<TurnState<any, any>> | ActivityHandler,
65
+ authConfiguration?: AuthConfiguration
66
+ ): AgentRequestHandler => {
67
+ const authConfig = getAuthConfigWithDefaults(authConfiguration)
68
+ const { adapter, headerPropagation } = createCloudAdapter(agent, authConfig)
69
+ const jwtMiddleware = authorizeJWT(authConfig)
70
+
71
+ return async (req: Request, res: WebResponse): Promise<void> => {
72
+ let middlewareError: any
73
+ let nextCalled = false
74
+
75
+ await jwtMiddleware(req, res as Response, (err?: any) => {
76
+ nextCalled = true
77
+ middlewareError = err
78
+ })
79
+
80
+ if (middlewareError) {
81
+ throw middlewareError
82
+ }
83
+
84
+ // If the middleware handled the response without calling next (e.g., 401), don't process the activity.
85
+ if (!nextCalled || res.headersSent) {
86
+ return
87
+ }
88
+
89
+ await adapter.process(req, res as Response, (context) => agent.run(context), headerPropagation)
90
+ }
91
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ActivityHandler, AgentApplication, AuthConfiguration, CloudAdapter, HeaderPropagationDefinition, TurnState } from '@microsoft/agents-hosting'
7
+
8
+ /**
9
+ * Result of creating a CloudAdapter from an agent.
10
+ */
11
+ export interface CloudAdapterResult {
12
+ adapter: CloudAdapter
13
+ headerPropagation: HeaderPropagationDefinition | undefined
14
+ }
15
+
16
+ /**
17
+ * Creates a CloudAdapter for the given agent.
18
+ *
19
+ * If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
20
+ * Otherwise, a new CloudAdapter is created.
21
+ *
22
+ * @param agent - The AgentApplication or ActivityHandler instance.
23
+ * @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
24
+ * If the agent already has an adapter, that adapter is reused and this value is ignored.
25
+ * @returns An object containing the CloudAdapter and optional header propagation configuration.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
30
+ * import { createCloudAdapter } from '@microsoft/agents-hosting-express';
31
+ *
32
+ * const app = new AgentApplication<TurnState>();
33
+ * const { adapter, headerPropagation } = createCloudAdapter(app, { clientId: process.env.CLIENT_ID });
34
+ *
35
+ * // Use the adapter directly with request/response objects compatible with CloudAdapter.process
36
+ * adapter.process(req, res, (context) => app.run(context), headerPropagation);
37
+ * ```
38
+ */
39
+ export const createCloudAdapter = (
40
+ agent: AgentApplication<TurnState<any, any>> | ActivityHandler,
41
+ authConfig?: AuthConfiguration
42
+ ): CloudAdapterResult => {
43
+ let adapter: CloudAdapter
44
+ let headerPropagation: HeaderPropagationDefinition | undefined
45
+ if (agent instanceof ActivityHandler || !agent.adapter) {
46
+ adapter = new CloudAdapter(authConfig)
47
+ } else {
48
+ adapter = agent.adapter as CloudAdapter
49
+ headerPropagation = (agent as AgentApplication<TurnState<any, any>>)?.options.headerPropagation
50
+ }
51
+ return { adapter, headerPropagation }
52
+ }
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './startServer'
2
+ export * from './createCloudAdapter'
3
+ export * from './createAgentRequestHandler'
@@ -4,58 +4,159 @@
4
4
  */
5
5
 
6
6
  import express, { Response } from 'express'
7
- import { ActivityHandler, AgentApplication, AuthConfiguration, authorizeJWT, CloudAdapter, getAuthConfigWithDefaults, HeaderPropagationDefinition, Request, TurnState } from '@microsoft/agents-hosting'
7
+ import rateLimit from 'express-rate-limit'
8
+ import { ActivityHandler, AgentApplication, AuthConfiguration, authorizeJWT, getAuthConfigWithDefaults, Request, TurnState } from '@microsoft/agents-hosting'
8
9
  import { version } from '@microsoft/agents-hosting/package.json'
10
+ import { debug } from '@microsoft/agents-telemetry'
11
+ import { createCloudAdapter } from './createCloudAdapter'
12
+
13
+ const logger = debug('agents:hosting-express')
14
+
15
+ /**
16
+ * Options for configuring the Express server started by `startServer`.
17
+ */
18
+ export interface StartServerOptions {
19
+ /**
20
+ * Optional custom authentication configuration.
21
+ * If not provided, configuration will be loaded from environment variables using loadAuthConfigFromEnv().
22
+ */
23
+ authConfig?: AuthConfiguration
24
+
25
+ /**
26
+ * The port to listen on. Defaults to `process.env.PORT` or `3978`.
27
+ */
28
+ port?: number | string
29
+
30
+ /**
31
+ * The route path for the agent messages endpoint. Defaults to `'/api/messages'`.
32
+ */
33
+ routePath?: string
34
+
35
+ /**
36
+ * Optional rate limiting configuration for the messages endpoint.
37
+ * If not provided, no rate limiting is applied.
38
+ * Specify windowMs (in milliseconds) and max (number of requests) to enable rate limiting.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * startServer(agent, {
43
+ * rateLimitOptions: {
44
+ * windowMs: 15 * 60 * 1000, // 15 minutes
45
+ * max: 1000 // limit each IP to 1000 requests per windowMs
46
+ * }
47
+ * });
48
+ * ```
49
+ */
50
+ rateLimitOptions?: { windowMs: number; max: number }
51
+
52
+ /**
53
+ * A callback invoked with the Express app before `listen()` is called.
54
+ * Use this to add custom routes, middleware, or static file serving.
55
+ *
56
+ * **Note:** The agent messages route is registered *after* this callback.
57
+ * Avoid adding catch-all routes (e.g., `app.all('*', ...)` or `app.use('*', ...)`)
58
+ * here, as they will shadow the messages endpoint.
59
+ * @example
60
+ * ```typescript
61
+ * startServer(agent, {
62
+ * beforeListen: (app) => {
63
+ * app.get('/health', (req, res) => res.json({ status: 'ok' }));
64
+ * }
65
+ * });
66
+ * ```
67
+ */
68
+ beforeListen?: (app: express.Express) => void
69
+ }
70
+
9
71
  /**
10
72
  * Starts an Express server for handling Agent requests.
11
73
  *
12
74
  * @param agent - The AgentApplication or ActivityHandler instance to process incoming activities.
13
- * @param authConfiguration - Optional custom authentication configuration. If not provided,
14
- * configuration will be loaded from environment variables using loadAuthConfigFromEnv().
15
- * @returns {express.Express} - The Express server instance.
75
+ * @param options - Optional configuration. Accepts either a `StartServerOptions` object or
76
+ * an `AuthConfiguration` for backward compatibility.
77
+ * @returns The Express server instance.
16
78
  *
17
79
  * @remarks
18
80
  * This function sets up an Express server with the necessary middleware and routes for handling
19
- * agent requests. It configures JWT authorization middleware and sets up the message endpoint.
20
- * The server will listen on the port specified in the PORT environment variable (or 3978 by default)
21
- * and logs startup information including the SDK version and configured app ID.
81
+ * agent requests. It configures JWT authorization middleware on the messages route and sets up the endpoint.
82
+ * The server will listen on the port specified in options, the PORT environment variable, or 3978 by default.
22
83
  *
23
84
  * @example
24
85
  * ```typescript
86
+ * // Basic usage
25
87
  * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
26
88
  * import { startServer } from '@microsoft/agents-hosting-express';
27
89
  *
28
90
  * const app = new AgentApplication<TurnState>();
29
- * app.onMessage('hello', async (context, state) => {
30
- * await context.sendActivity('Hello, world!');
31
- * });
32
- *
33
91
  * startServer(app);
34
92
  * ```
35
93
  *
94
+ * @example
95
+ * ```typescript
96
+ * // With options
97
+ * import { AgentApplication, TurnState } from '@microsoft/agents-hosting';
98
+ * import { startServer } from '@microsoft/agents-hosting-express';
99
+ *
100
+ * const app = new AgentApplication<TurnState>();
101
+ * startServer(app, {
102
+ * port: 8080,
103
+ * routePath: '/bot/messages',
104
+ * beforeListen: (server) => {
105
+ * server.get('/health', (req, res) => res.json({ status: 'ok' }));
106
+ * }
107
+ * });
108
+ * ```
36
109
  */
37
- export const startServer = (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfiguration?: AuthConfiguration) : express.Express => {
38
- const authConfig: AuthConfiguration = getAuthConfigWithDefaults(authConfiguration)
39
- let adapter: CloudAdapter
40
- let headerPropagation: HeaderPropagationDefinition | undefined
41
- if (agent instanceof ActivityHandler || !agent.adapter) {
42
- adapter = new CloudAdapter()
43
- } else {
44
- adapter = agent.adapter as CloudAdapter
45
- headerPropagation = (agent as AgentApplication<TurnState<any, any>>)?.options.headerPropagation
46
- }
110
+ export function startServer (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, options?: StartServerOptions): express.Express
111
+ export function startServer (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, authConfiguration?: AuthConfiguration): express.Express
112
+ export function startServer (agent: AgentApplication<TurnState<any, any>> | ActivityHandler, optionsOrAuth?: StartServerOptions | AuthConfiguration): express.Express {
113
+ const isOptions = typeof optionsOrAuth === 'object' && optionsOrAuth !== null &&
114
+ ('authConfig' in optionsOrAuth || 'port' in optionsOrAuth || 'routePath' in optionsOrAuth || 'rateLimitOptions' in optionsOrAuth || 'beforeListen' in optionsOrAuth)
115
+
116
+ const opts: StartServerOptions = isOptions ? optionsOrAuth as StartServerOptions : { authConfig: optionsOrAuth as AuthConfiguration | undefined }
117
+ const authConfig: AuthConfiguration = getAuthConfigWithDefaults(opts.authConfig)
118
+ const routePath = opts.routePath ?? '/api/messages'
119
+ const { adapter, headerPropagation } = createCloudAdapter(agent, authConfig)
47
120
 
48
121
  const server = express()
49
122
  server.use(express.json())
50
- server.use(authorizeJWT(authConfig))
51
123
 
52
- server.post('/api/messages', (req: Request, res: Response) =>
124
+ if (opts.beforeListen && typeof opts.beforeListen === 'function') {
125
+ opts.beforeListen(server)
126
+ }
127
+
128
+ const middlewares: express.RequestHandler[] = [authorizeJWT(authConfig)]
129
+ if (opts.rateLimitOptions) {
130
+ const messagesRateLimiter = rateLimit({
131
+ windowMs: opts.rateLimitOptions.windowMs,
132
+ max: opts.rateLimitOptions.max,
133
+ standardHeaders: true,
134
+ legacyHeaders: false
135
+ })
136
+ middlewares.unshift(messagesRateLimiter)
137
+ }
138
+
139
+ server.post(routePath, ...middlewares, (req: Request, res: Response) =>
53
140
  adapter.process(req, res, (context) =>
54
141
  agent.run(context)
55
142
  , headerPropagation)
56
143
  )
57
144
 
58
- const port = process.env.PORT || 3978
145
+ const port = opts.port ?? process.env.PORT ?? 3978
146
+ const className = (obj: any) => obj?.constructor?.name ?? (obj ? 'custom' : undefined)
147
+ logger.info('Express server settings loaded', {
148
+ messageEndpoint: `POST ${routePath}`,
149
+ port: {
150
+ value: port,
151
+ source: opts.port ? 'options' : process.env.PORT ? 'env' : 'default',
152
+ },
153
+ middlewares: ['express.json', 'authorizeJWT'],
154
+ adapter: {
155
+ className: className(adapter),
156
+ source: agent instanceof ActivityHandler || !agent.adapter ? 'created' : 'agent.adapter',
157
+ },
158
+ headerPropagation: headerPropagation !== undefined ? 'enabled' : 'disabled',
159
+ })
59
160
  server.listen(port, async () => {
60
161
  console.log(`\nServer listening to port ${port} on sdk ${version} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`)
61
162
  }).on('error', console.error)