@axiom-lattice/gateway 2.1.87 → 2.1.89
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/.turbo/turbo-build.log +12 -12
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +22 -0
- package/README.md +37 -94
- package/dist/index.d.mts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +139 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/controllers/channel-bindings.ts +1 -1
- package/src/controllers/data-query.ts +1 -1
- package/src/controllers/metrics-configs.ts +3 -3
- package/src/index.ts +1 -15
- package/src/router/MessageRouter.ts +169 -17
- package/src/bindings/index.ts +0 -14
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @axiom-lattice/gateway@2.1.
|
|
2
|
+
> @axiom-lattice/gateway@2.1.89 build /home/runner/work/agentic/agentic/packages/gateway
|
|
3
3
|
> tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -11,22 +11,22 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[warn] [33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1m"import.meta" is not available with the "cjs" output format and will be empty[0m [empty-import-meta]
|
|
13
13
|
|
|
14
|
-
src/index.ts:
|
|
15
|
-
[37m
|
|
14
|
+
src/index.ts:175:33:
|
|
15
|
+
[37m 175 │ const __filename = fileURLToPath([32mimport.meta[37m.url);
|
|
16
16
|
╵ [32m~~~~~~~~~~~[0m
|
|
17
17
|
|
|
18
18
|
You need to set the output format to "esm" for "import.meta" to work correctly.
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
22
|
-
[32mCJS[39m [1mdist/index.js.map [22m[
|
|
23
|
-
[32mCJS[39m ⚡️ Build success in
|
|
24
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
21
|
+
[32mCJS[39m [1mdist/index.js [22m[32m246.20 KB[39m
|
|
22
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m515.19 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 415ms
|
|
24
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m241.30 KB[39m
|
|
25
25
|
[32mESM[39m [1mdist/sender-PX32VSHB.mjs [22m[32m873.00 B[39m
|
|
26
26
|
[32mESM[39m [1mdist/sender-PX32VSHB.mjs.map [22m[32m2.07 KB[39m
|
|
27
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[
|
|
28
|
-
[32mESM[39m ⚡️ Build success in
|
|
27
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m513.63 KB[39m
|
|
28
|
+
[32mESM[39m ⚡️ Build success in 415ms
|
|
29
29
|
[34mDTS[39m Build start
|
|
30
|
-
[32mDTS[39m ⚡️ Build success in
|
|
31
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
32
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
30
|
+
[32mDTS[39m ⚡️ Build success in 14563ms
|
|
31
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.57 KB[39m
|
|
32
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m7.57 KB[39m
|
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,12 @@ src/
|
|
|
24
24
|
queue_service.ts # Task queue (memory/redis)
|
|
25
25
|
agent_task_consumer.ts # Queue consumer
|
|
26
26
|
sandbox_service.ts # Sandbox proxy
|
|
27
|
+
channels/ # External channel adapters (Lark, Slack, etc.)
|
|
28
|
+
lark/ # Lark (Feishu) adapter
|
|
29
|
+
registry.ts # ChannelAdapterRegistry
|
|
30
|
+
router/ # Message routing
|
|
31
|
+
MessageRouter.ts # Inbound message dispatch + channel reply
|
|
32
|
+
MessageContext.ts # Request-scoped context
|
|
27
33
|
schemas/index.ts # Zod/Fastify schemas
|
|
28
34
|
logger/Logger.ts # Logger lattice integration
|
|
29
35
|
```
|
|
@@ -40,6 +46,62 @@ src/
|
|
|
40
46
|
- **DB configs**: `routes/index.ts`, `controllers/database-configs.ts`
|
|
41
47
|
- **Queue setup**: `services/queue_service.ts`, `services/agent_task_consumer.ts`
|
|
42
48
|
- **Logger lattice**: `logger/Logger.ts`, integration in `index.ts`
|
|
49
|
+
- **Channel message routing**: `router/MessageRouter.ts`, `channels/lark/`
|
|
50
|
+
- **Channel reply mechanism**: `router/MessageRouter.ts#dispatch` (see below)
|
|
51
|
+
|
|
52
|
+
## CHANNEL MESSAGE REPLY FLOW
|
|
53
|
+
|
|
54
|
+
When an external channel (e.g. Lark) sends a message to a bound agent, the gateway
|
|
55
|
+
routes the message and sends the AI response back to the channel. The flow:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
1. Channel webhook → ChannelAdapter.receive(rawPayload)
|
|
59
|
+
→ InboundMessage with replyTarget set
|
|
60
|
+
|
|
61
|
+
2. MessageRouter.dispatch(inboundMessage)
|
|
62
|
+
→ resolve binding → create/reuse thread
|
|
63
|
+
→ subscribe to Agent 'reply:ready' event (before addMessage)
|
|
64
|
+
→ agent.addMessage({ custom_run_config: { _replyTarget: replyTarget } })
|
|
65
|
+
|
|
66
|
+
3. Agent queue processes the message
|
|
67
|
+
→ agentStreamExecutor() runs the agent
|
|
68
|
+
→ emits 'reply:ready' { state, customRunConfig }
|
|
69
|
+
|
|
70
|
+
4. reply:ready callback fires
|
|
71
|
+
→ extracts last AI message from state.values.messages
|
|
72
|
+
→ calls ChannelAdapter.sendReply(replyTarget, { text }, installation)
|
|
73
|
+
→ reply appears in the external channel
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Reply deduplication per thread
|
|
77
|
+
|
|
78
|
+
`MessageRouter` uses a counter-based subscription (`_replySubs: Map<string, { count, timer }>`) to handle
|
|
79
|
+
multiple concurrent dispatches on the same thread:
|
|
80
|
+
|
|
81
|
+
- Each dispatch increments the counter, only the first registers the `subscribeOnce` listener.
|
|
82
|
+
- When `reply:ready` fires, the callback decrements the counter. The subscription is removed
|
|
83
|
+
only when the counter reaches 0.
|
|
84
|
+
- A 1-hour timeout cleans up stale subscriptions (e.g. agent aborted, sequence error).
|
|
85
|
+
|
|
86
|
+
### Reply timing across QueueModes
|
|
87
|
+
|
|
88
|
+
| Mode | `reply:ready` emitted | Reply behavior |
|
|
89
|
+
|------|----------------------|----------------|
|
|
90
|
+
| STEER | After each execution | 1 reply per message |
|
|
91
|
+
| FOLLOWUP | After each execution | 1 reply per message |
|
|
92
|
+
| COLLECT | After the batch (all per-message events first) | 1 reply for the entire batch |
|
|
93
|
+
|
|
94
|
+
### Key logs for debugging
|
|
95
|
+
|
|
96
|
+
| Event | Meaning |
|
|
97
|
+
|-------|---------|
|
|
98
|
+
| `dispatch:reply:subscribed` | First reply subscription for this thread |
|
|
99
|
+
| `dispatch:reply:incremented` | Additional dispatch joined existing subscription |
|
|
100
|
+
| `dispatch:reply:sending` | AI text extracted, about to call sendReply |
|
|
101
|
+
| `dispatch:reply:sent` | Channel API responded OK |
|
|
102
|
+
| `dispatch:reply:failed` | Channel API returned an error |
|
|
103
|
+
| `dispatch:reply:empty` | Agent produced no output, skipping reply |
|
|
104
|
+
| `dispatch:reply:timeout` | No reply:ready fired within 1h, subscription cleaned up |
|
|
43
105
|
|
|
44
106
|
## CONVENTIONS
|
|
45
107
|
- Controllers export named handlers, consume from `services/`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @axiom-lattice/gateway
|
|
2
2
|
|
|
3
|
+
## 2.1.89
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 176bfe8: custom middleware, enhance messagerouter
|
|
8
|
+
- Updated dependencies [176bfe8]
|
|
9
|
+
- @axiom-lattice/core@2.1.77
|
|
10
|
+
- @axiom-lattice/agent-eval@2.1.71
|
|
11
|
+
- @axiom-lattice/pg-stores@1.0.68
|
|
12
|
+
|
|
13
|
+
## 2.1.88
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 6ff5bd5: fix issue
|
|
18
|
+
- Updated dependencies [6ff5bd5]
|
|
19
|
+
- @axiom-lattice/agent-eval@2.1.70
|
|
20
|
+
- @axiom-lattice/core@2.1.76
|
|
21
|
+
- @axiom-lattice/pg-stores@1.0.67
|
|
22
|
+
- @axiom-lattice/protocols@2.1.39
|
|
23
|
+
- @axiom-lattice/queue-redis@1.0.38
|
|
24
|
+
|
|
3
25
|
## 2.1.87
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,125 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @axiom-lattice/gateway
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fastify-based HTTP API gateway exposing REST endpoints for agent execution, thread management, and configuration.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Store Consumption
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- 流式响应支持
|
|
10
|
-
- 事件总线
|
|
11
|
-
- 队列服务(基于 QueueLattice)
|
|
12
|
-
- 日志服务(基于 LoggerLattice,支持自定义配置)
|
|
7
|
+
The gateway reads all stores from `StoreLatticeManager` at startup — no manual wiring needed.
|
|
8
|
+
Register stores via `configureStores` **before** calling `startAsHttpEndpoint()`:
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## 启动服务
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# 开发环境
|
|
24
|
-
pnpm dev
|
|
25
|
-
|
|
26
|
-
# 生产环境
|
|
27
|
-
pnpm build
|
|
28
|
-
pnpm start
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## API 接口
|
|
32
|
-
|
|
33
|
-
### Configuration API
|
|
34
|
-
|
|
35
|
-
The gateway supports dynamic configuration updates via JSON. You can update environment variables at runtime without restarting the server.
|
|
10
|
+
```typescript
|
|
11
|
+
import { configureStores } from "@axiom-lattice/core";
|
|
12
|
+
import { createPgStoreConfig } from "@axiom-lattice/pg-stores";
|
|
13
|
+
import { LatticeGateway } from "@axiom-lattice/gateway";
|
|
36
14
|
|
|
37
|
-
|
|
15
|
+
const stores = createPgStoreConfig(process.env.DATABASE_URL);
|
|
16
|
+
await configureStores({ ...stores });
|
|
38
17
|
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
// Gateway picks up stores from StoreLatticeManager automatically
|
|
19
|
+
LatticeGateway.startAsHttpEndpoint({ port: 4001 });
|
|
41
20
|
```
|
|
42
21
|
|
|
43
|
-
|
|
22
|
+
### How it works
|
|
44
23
|
|
|
45
|
-
|
|
24
|
+
The gateway internally calls `getStoreLattice("default", type)` for each store it needs:
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
- **`channelBinding`** — used by `MessageRouter` for sender-to-agent binding resolution
|
|
27
|
+
- **`channelInstallation`** — used for channel instance lookup
|
|
28
|
+
- **`database`** — `SqlDatabaseManager` reads configs lazily on first query
|
|
29
|
+
- **`metrics`** — `MetricsServerManager` loads configs on first access
|
|
30
|
+
- **All other stores** — consumed by controllers via `getStoreLattice("default", type)`
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
"config": {
|
|
53
|
-
"port": 4001,
|
|
54
|
-
"queueServiceType": "redis",
|
|
55
|
-
"redisUrl": "redis://localhost:6379",
|
|
56
|
-
"redisPassword": "your-password",
|
|
57
|
-
"queueName": "tasks"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**Note**: When updating `queueServiceType`, the queue service will be automatically reconfigured.
|
|
32
|
+
No `setConfigStore()` or `loadConfigsFromStore()` calls needed — each manager reads directly from `StoreLatticeManager`.
|
|
63
33
|
|
|
64
|
-
|
|
34
|
+
## Quick Start
|
|
65
35
|
|
|
66
36
|
```typescript
|
|
67
|
-
|
|
68
|
-
const response = await fetch("http://localhost:4001/api/config", {
|
|
69
|
-
method: "PUT",
|
|
70
|
-
headers: {
|
|
71
|
-
"Content-Type": "application/json",
|
|
72
|
-
},
|
|
73
|
-
body: JSON.stringify({
|
|
74
|
-
config: {
|
|
75
|
-
queueServiceType: "redis",
|
|
76
|
-
redisUrl: "redis://localhost:6379",
|
|
77
|
-
redisPassword: "your-password",
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
80
|
-
});
|
|
37
|
+
import { LatticeGateway } from "@axiom-lattice/gateway";
|
|
81
38
|
|
|
82
|
-
|
|
83
|
-
|
|
39
|
+
LatticeGateway.startAsHttpEndpoint({
|
|
40
|
+
port: 4001,
|
|
41
|
+
queueServiceConfig: { type: "memory", defaultStartPollingQueue: true },
|
|
42
|
+
});
|
|
84
43
|
```
|
|
85
44
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- `POST /api/v1/run`: 运行代理
|
|
89
|
-
- `POST /api/v1/run/stream`: 流式运行代理
|
|
90
|
-
- `GET /api/v1/run/:thread_id`: 获取运行状态
|
|
91
|
-
- `GET /api/v1/run/:thread_id/messages`: 获取消息历史
|
|
92
|
-
|
|
93
|
-
## Logger 配置
|
|
94
|
-
|
|
95
|
-
Gateway 使用 Logger Lattice 进行日志管理,支持自定义日志配置。详细说明请参考 [LOGGER_CONFIG.md](./LOGGER_CONFIG.md)。
|
|
96
|
-
|
|
97
|
-
### 快速开始
|
|
45
|
+
## Logger Configuration
|
|
98
46
|
|
|
99
47
|
```typescript
|
|
100
48
|
import { LatticeGateway } from "@axiom-lattice/gateway";
|
|
101
49
|
import { LoggerType } from "@axiom-lattice/protocols";
|
|
102
50
|
|
|
103
|
-
// 使用默认配置
|
|
104
|
-
LatticeGateway.startAsHttpEndpoint({
|
|
105
|
-
port: 4001,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// 自定义日志配置
|
|
109
51
|
LatticeGateway.startAsHttpEndpoint({
|
|
110
52
|
port: 4001,
|
|
111
53
|
loggerConfig: {
|
|
112
|
-
|
|
54
|
+
name: "default",
|
|
55
|
+
type: LoggerType.PINO,
|
|
113
56
|
serviceName: "my-service",
|
|
114
57
|
},
|
|
115
58
|
});
|
|
116
59
|
```
|
|
117
60
|
|
|
118
|
-
##
|
|
61
|
+
## API Endpoints
|
|
119
62
|
|
|
120
|
-
- `
|
|
121
|
-
- `
|
|
122
|
-
- `
|
|
123
|
-
- `
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
63
|
+
- `POST /api/v1/run` — Execute agent
|
|
64
|
+
- `POST /api/v1/run/stream` — Stream agent execution
|
|
65
|
+
- `GET /api/v1/run/:thread_id` — Get run status
|
|
66
|
+
- `GET /api/v1/run/:thread_id/messages` — Get message history
|
|
67
|
+
- `GET /api/config` — Get gateway configuration
|
|
68
|
+
- `PUT /api/config` — Update gateway configuration
|
package/dist/index.d.mts
CHANGED
|
@@ -62,19 +62,81 @@ interface MessageContext {
|
|
|
62
62
|
}
|
|
63
63
|
type MessageMiddleware = (ctx: MessageContext, next: () => Promise<void>) => Promise<void>;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for {@link MessageRouter}.
|
|
67
|
+
*
|
|
68
|
+
* @param middlewares - Ordered middleware chain executed before dispatch
|
|
69
|
+
* @param bindingRegistry - Resolves sender-to-agent bindings
|
|
70
|
+
* @param adapterRegistry - Registry of {@link ChannelAdapter} implementations
|
|
71
|
+
* @param installationStore - Persisted channel installation configs
|
|
72
|
+
*/
|
|
65
73
|
interface MessageRouterConfig {
|
|
66
74
|
middlewares: MessageMiddleware[];
|
|
67
75
|
bindingRegistry: BindingRegistry;
|
|
68
76
|
adapterRegistry: ChannelAdapterRegistry;
|
|
69
77
|
installationStore: ChannelInstallationStore;
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Core message router for external channel integration.
|
|
81
|
+
*
|
|
82
|
+
* Receives normalized {@link InboundMessage} objects from channel adapters,
|
|
83
|
+
* resolves the target agent via {@link BindingRegistry}, manages thread
|
|
84
|
+
* lifecycle, and sends AI responses back to the channel via
|
|
85
|
+
* `ChannelAdapter.sendReply`.
|
|
86
|
+
*
|
|
87
|
+
* ## Reply flow
|
|
88
|
+
*
|
|
89
|
+
* The router uses a counter-based deduplication scheme on the internal
|
|
90
|
+
* {@link Agent.reply:ready} event:
|
|
91
|
+
*
|
|
92
|
+
* 1. Before `agent.addMessage()`, it subscribes to `reply:ready` on the agent.
|
|
93
|
+
* 2. Multiple concurrent dispatches on the same thread increment a counter;
|
|
94
|
+
* only the first registers the `EventEmitter.once` listener.
|
|
95
|
+
* 3. When `reply:ready` fires, the callback extracts the last AI message from
|
|
96
|
+
* the LangGraph state and sends it via `ChannelAdapter.sendReply`.
|
|
97
|
+
* 4. The counter is decremented; the subscription is only removed when the
|
|
98
|
+
* counter reaches 0.
|
|
99
|
+
* 5. Stale subscriptions are cleaned up after a 1-hour timeout.
|
|
100
|
+
*
|
|
101
|
+
* The `replyTarget` is carried through by injecting `_replyTarget` into
|
|
102
|
+
* `custom_run_config` on {@link Agent.addMessage}, and retrieved from the
|
|
103
|
+
* `reply:ready` event payload.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link ChannelAdapter.sendReply}
|
|
106
|
+
* @see {@link InboundMessage.replyTarget}
|
|
107
|
+
*/
|
|
71
108
|
declare class MessageRouter {
|
|
72
109
|
private middlewares;
|
|
73
110
|
private bindingRegistry;
|
|
74
111
|
private adapterRegistry;
|
|
75
112
|
private installationStore;
|
|
113
|
+
/**
|
|
114
|
+
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
115
|
+
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
116
|
+
*
|
|
117
|
+
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
118
|
+
*/
|
|
119
|
+
private _replySubs;
|
|
76
120
|
constructor(config: MessageRouterConfig);
|
|
121
|
+
/**
|
|
122
|
+
* Register an additional middleware at the end of the chain.
|
|
123
|
+
*
|
|
124
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
125
|
+
*/
|
|
77
126
|
use(middleware: MessageMiddleware): void;
|
|
127
|
+
/**
|
|
128
|
+
* Dispatch an inbound channel message to the bound agent.
|
|
129
|
+
*
|
|
130
|
+
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
131
|
+
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
132
|
+
*
|
|
133
|
+
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
134
|
+
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
135
|
+
* the AI response back to the channel when it arrives.
|
|
136
|
+
*
|
|
137
|
+
* @param message - Normalized inbound message from a channel adapter
|
|
138
|
+
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
139
|
+
*/
|
|
78
140
|
dispatch(message: InboundMessage): Promise<DispatchResult>;
|
|
79
141
|
private runMiddlewares;
|
|
80
142
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -62,19 +62,81 @@ interface MessageContext {
|
|
|
62
62
|
}
|
|
63
63
|
type MessageMiddleware = (ctx: MessageContext, next: () => Promise<void>) => Promise<void>;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for {@link MessageRouter}.
|
|
67
|
+
*
|
|
68
|
+
* @param middlewares - Ordered middleware chain executed before dispatch
|
|
69
|
+
* @param bindingRegistry - Resolves sender-to-agent bindings
|
|
70
|
+
* @param adapterRegistry - Registry of {@link ChannelAdapter} implementations
|
|
71
|
+
* @param installationStore - Persisted channel installation configs
|
|
72
|
+
*/
|
|
65
73
|
interface MessageRouterConfig {
|
|
66
74
|
middlewares: MessageMiddleware[];
|
|
67
75
|
bindingRegistry: BindingRegistry;
|
|
68
76
|
adapterRegistry: ChannelAdapterRegistry;
|
|
69
77
|
installationStore: ChannelInstallationStore;
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Core message router for external channel integration.
|
|
81
|
+
*
|
|
82
|
+
* Receives normalized {@link InboundMessage} objects from channel adapters,
|
|
83
|
+
* resolves the target agent via {@link BindingRegistry}, manages thread
|
|
84
|
+
* lifecycle, and sends AI responses back to the channel via
|
|
85
|
+
* `ChannelAdapter.sendReply`.
|
|
86
|
+
*
|
|
87
|
+
* ## Reply flow
|
|
88
|
+
*
|
|
89
|
+
* The router uses a counter-based deduplication scheme on the internal
|
|
90
|
+
* {@link Agent.reply:ready} event:
|
|
91
|
+
*
|
|
92
|
+
* 1. Before `agent.addMessage()`, it subscribes to `reply:ready` on the agent.
|
|
93
|
+
* 2. Multiple concurrent dispatches on the same thread increment a counter;
|
|
94
|
+
* only the first registers the `EventEmitter.once` listener.
|
|
95
|
+
* 3. When `reply:ready` fires, the callback extracts the last AI message from
|
|
96
|
+
* the LangGraph state and sends it via `ChannelAdapter.sendReply`.
|
|
97
|
+
* 4. The counter is decremented; the subscription is only removed when the
|
|
98
|
+
* counter reaches 0.
|
|
99
|
+
* 5. Stale subscriptions are cleaned up after a 1-hour timeout.
|
|
100
|
+
*
|
|
101
|
+
* The `replyTarget` is carried through by injecting `_replyTarget` into
|
|
102
|
+
* `custom_run_config` on {@link Agent.addMessage}, and retrieved from the
|
|
103
|
+
* `reply:ready` event payload.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link ChannelAdapter.sendReply}
|
|
106
|
+
* @see {@link InboundMessage.replyTarget}
|
|
107
|
+
*/
|
|
71
108
|
declare class MessageRouter {
|
|
72
109
|
private middlewares;
|
|
73
110
|
private bindingRegistry;
|
|
74
111
|
private adapterRegistry;
|
|
75
112
|
private installationStore;
|
|
113
|
+
/**
|
|
114
|
+
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
115
|
+
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
116
|
+
*
|
|
117
|
+
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
118
|
+
*/
|
|
119
|
+
private _replySubs;
|
|
76
120
|
constructor(config: MessageRouterConfig);
|
|
121
|
+
/**
|
|
122
|
+
* Register an additional middleware at the end of the chain.
|
|
123
|
+
*
|
|
124
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
125
|
+
*/
|
|
77
126
|
use(middleware: MessageMiddleware): void;
|
|
127
|
+
/**
|
|
128
|
+
* Dispatch an inbound channel message to the bound agent.
|
|
129
|
+
*
|
|
130
|
+
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
131
|
+
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
132
|
+
*
|
|
133
|
+
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
134
|
+
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
135
|
+
* the AI response back to the channel when it arrives.
|
|
136
|
+
*
|
|
137
|
+
* @param message - Normalized inbound message from a channel adapter
|
|
138
|
+
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
139
|
+
*/
|
|
78
140
|
dispatch(message: InboundMessage): Promise<DispatchResult>;
|
|
79
141
|
private runMiddlewares;
|
|
80
142
|
}
|