@axiom-lattice/gateway 2.1.80 → 2.1.82
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/CHANGELOG.md +16 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +110 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +3 -2
- package/src/router/MessageRouter.ts +83 -18
- package/src/router/middlewares/deduplication.ts +19 -6
- package/src/router/middlewares/rateLimit.ts +8 -0
- package/src/routes/index.ts +6 -1
- package/.turbo/turbo-build.log +0 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.82",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"redis": "^5.0.1",
|
|
41
41
|
"uuid": "^9.0.1",
|
|
42
42
|
"zod": "3.25.76",
|
|
43
|
-
"@axiom-lattice/agent-eval": "2.1.
|
|
44
|
-
"@axiom-lattice/core": "2.1.
|
|
45
|
-
"@axiom-lattice/pg-stores": "1.0.
|
|
43
|
+
"@axiom-lattice/agent-eval": "2.1.65",
|
|
44
|
+
"@axiom-lattice/core": "2.1.71",
|
|
45
|
+
"@axiom-lattice/pg-stores": "1.0.61",
|
|
46
46
|
"@axiom-lattice/protocols": "2.1.36",
|
|
47
47
|
"@axiom-lattice/queue-redis": "1.0.35"
|
|
48
48
|
},
|
package/src/index.ts
CHANGED
|
@@ -287,13 +287,14 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
287
287
|
|
|
288
288
|
const router = new MessageRouter({
|
|
289
289
|
middlewares: [
|
|
290
|
-
createDeduplicationMiddleware(),
|
|
291
|
-
createRateLimitMiddleware(),
|
|
290
|
+
createDeduplicationMiddleware(undefined, logger),
|
|
291
|
+
createRateLimitMiddleware(undefined, undefined, undefined, logger),
|
|
292
292
|
createAuditLoggerMiddleware(),
|
|
293
293
|
],
|
|
294
294
|
bindingRegistry: bindingStore,
|
|
295
295
|
adapterRegistry,
|
|
296
296
|
installationStore,
|
|
297
|
+
logger,
|
|
297
298
|
});
|
|
298
299
|
|
|
299
300
|
channelDeps = { router, installationStore };
|
|
@@ -24,6 +24,7 @@ export interface MessageRouterConfig {
|
|
|
24
24
|
bindingRegistry: BindingRegistry;
|
|
25
25
|
adapterRegistry: ChannelAdapterRegistry;
|
|
26
26
|
installationStore: ChannelInstallationStore;
|
|
27
|
+
logger?: any;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export class MessageRouter {
|
|
@@ -31,12 +32,14 @@ export class MessageRouter {
|
|
|
31
32
|
private bindingRegistry: BindingRegistry;
|
|
32
33
|
private adapterRegistry: ChannelAdapterRegistry;
|
|
33
34
|
private installationStore: ChannelInstallationStore;
|
|
35
|
+
private logger?: any;
|
|
34
36
|
|
|
35
37
|
constructor(config: MessageRouterConfig) {
|
|
36
38
|
this.middlewares = [...config.middlewares];
|
|
37
39
|
this.bindingRegistry = config.bindingRegistry;
|
|
38
40
|
this.adapterRegistry = config.adapterRegistry;
|
|
39
41
|
this.installationStore = config.installationStore;
|
|
42
|
+
this.logger = config.logger;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
use(middleware: MessageMiddleware): void {
|
|
@@ -56,11 +59,20 @@ export class MessageRouter {
|
|
|
56
59
|
|| (await this.installationStore.getInstallationById(message.channelInstallationId))?.tenantId;
|
|
57
60
|
|
|
58
61
|
if (!tenantId) {
|
|
62
|
+
this.logger?.error({
|
|
63
|
+
event: "dispatch:error",
|
|
64
|
+
error: "tenantId missing",
|
|
65
|
+
channel: message.channel,
|
|
66
|
+
channelInstallationId: message.channelInstallationId,
|
|
67
|
+
senderId: message.sender.id,
|
|
68
|
+
}, "tenantId is required");
|
|
59
69
|
throw new Error(
|
|
60
70
|
"tenantId is required: provide it in the message or ensure the channelInstallation has a tenantId"
|
|
61
71
|
);
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
this.logger?.info({ event: "dispatch:start", channel: message.channel, senderId: message.sender.id, tenantId }, "Message dispatch started");
|
|
75
|
+
|
|
64
76
|
let binding = await this.bindingRegistry.resolve({
|
|
65
77
|
channel: message.channel,
|
|
66
78
|
senderId: message.sender.id,
|
|
@@ -74,12 +86,25 @@ export class MessageRouter {
|
|
|
74
86
|
);
|
|
75
87
|
|
|
76
88
|
if (installation?.rejectWhenNoBinding) {
|
|
89
|
+
this.logger?.warn({
|
|
90
|
+
event: "dispatch:no_binding",
|
|
91
|
+
channel: message.channel,
|
|
92
|
+
senderId: message.sender.id,
|
|
93
|
+
tenantId,
|
|
94
|
+
channelInstallationId: message.channelInstallationId,
|
|
95
|
+
}, "No binding found and rejectWhenNoBinding is enabled");
|
|
77
96
|
throw new BindingNotFoundError(
|
|
78
97
|
`No binding for sender "${message.sender.id}" on channel "${message.channel}"`,
|
|
79
98
|
);
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
if (installation?.fallbackAgentId) {
|
|
102
|
+
this.logger?.warn({
|
|
103
|
+
event: "dispatch:fallback",
|
|
104
|
+
channel: message.channel,
|
|
105
|
+
senderId: message.sender.id,
|
|
106
|
+
fallbackAgentId: installation.fallbackAgentId,
|
|
107
|
+
}, "No binding found, falling back to fallbackAgentId");
|
|
83
108
|
binding = {
|
|
84
109
|
id: "fallback",
|
|
85
110
|
channel: message.channel,
|
|
@@ -94,6 +119,12 @@ export class MessageRouter {
|
|
|
94
119
|
updatedAt: new Date(),
|
|
95
120
|
};
|
|
96
121
|
} else {
|
|
122
|
+
this.logger?.error({
|
|
123
|
+
event: "dispatch:no_fallback",
|
|
124
|
+
channel: message.channel,
|
|
125
|
+
senderId: message.sender.id,
|
|
126
|
+
tenantId,
|
|
127
|
+
}, "No binding found and no fallbackAgentId configured");
|
|
97
128
|
throw new BindingNotFoundError(
|
|
98
129
|
`No binding for sender "${message.sender.id}" and no fallback configured`,
|
|
99
130
|
);
|
|
@@ -102,7 +133,31 @@ export class MessageRouter {
|
|
|
102
133
|
|
|
103
134
|
ctx.binding = binding;
|
|
104
135
|
|
|
136
|
+
this.logger?.info({
|
|
137
|
+
event: "dispatch:binding",
|
|
138
|
+
bindingId: binding.id,
|
|
139
|
+
agentId: binding.agentId,
|
|
140
|
+
threadId: binding.threadId,
|
|
141
|
+
threadMode: binding.threadMode,
|
|
142
|
+
workspaceId: binding.workspaceId,
|
|
143
|
+
projectId: binding.projectId,
|
|
144
|
+
}, "Binding resolved");
|
|
145
|
+
|
|
146
|
+
if (binding.threadMode === "per_conversation") {
|
|
147
|
+
this.logger?.warn({
|
|
148
|
+
event: "dispatch:per_conversation",
|
|
149
|
+
bindingId: binding.id,
|
|
150
|
+
conversationId: message.conversation?.id,
|
|
151
|
+
}, "per_conversation mode active — thread lookup by conversation not yet implemented, using binding.threadId");
|
|
152
|
+
}
|
|
153
|
+
|
|
105
154
|
if (!binding.enabled) {
|
|
155
|
+
this.logger?.warn({
|
|
156
|
+
event: "dispatch:binding_disabled",
|
|
157
|
+
bindingId: binding.id,
|
|
158
|
+
agentId: binding.agentId,
|
|
159
|
+
senderId: message.sender.id,
|
|
160
|
+
}, "Binding is disabled, rejecting message");
|
|
106
161
|
throw new BindingNotFoundError(
|
|
107
162
|
`Binding for sender "${message.sender.id}" is disabled`,
|
|
108
163
|
);
|
|
@@ -112,8 +167,14 @@ export class MessageRouter {
|
|
|
112
167
|
if (!threadId) {
|
|
113
168
|
const threadStore = getStoreLattice("default", "thread").store;
|
|
114
169
|
const newThreadId = randomUUID();
|
|
170
|
+
this.logger?.info({
|
|
171
|
+
event: "dispatch:thread:create",
|
|
172
|
+
agentId: ctx.binding.agentId,
|
|
173
|
+
newThreadId,
|
|
174
|
+
tenantId: tenantId!,
|
|
175
|
+
}, "Creating new thread for binding");
|
|
115
176
|
const newThread = await threadStore.createThread(
|
|
116
|
-
tenantId
|
|
177
|
+
tenantId!,
|
|
117
178
|
ctx.binding.agentId,
|
|
118
179
|
newThreadId,
|
|
119
180
|
{
|
|
@@ -134,19 +195,35 @@ export class MessageRouter {
|
|
|
134
195
|
}
|
|
135
196
|
}
|
|
136
197
|
|
|
198
|
+
this.logger?.info({
|
|
199
|
+
event: "dispatch:agent",
|
|
200
|
+
agentId: ctx.binding.agentId,
|
|
201
|
+
threadId,
|
|
202
|
+
threadMode: ctx.binding.threadMode,
|
|
203
|
+
senderId: message.sender.id,
|
|
204
|
+
contentLength: message.content.text.length,
|
|
205
|
+
}, "Dispatching to agent");
|
|
206
|
+
|
|
137
207
|
const agent = agentInstanceManager.getAgent({
|
|
138
|
-
tenant_id: tenantId
|
|
208
|
+
tenant_id: tenantId!,
|
|
139
209
|
assistant_id: ctx.binding.agentId,
|
|
140
210
|
thread_id: threadId,
|
|
141
211
|
workspace_id: ctx.binding.workspaceId || "",
|
|
142
212
|
project_id: ctx.binding.projectId || "",
|
|
143
213
|
});
|
|
144
214
|
|
|
145
|
-
const
|
|
215
|
+
const addResult = await agent.addMessage({
|
|
146
216
|
input: { message: message.content.text },
|
|
217
|
+
custom_run_config: message.content.metadata || {},
|
|
147
218
|
});
|
|
148
219
|
|
|
149
|
-
|
|
220
|
+
this.logger?.info({
|
|
221
|
+
event: "dispatch:complete",
|
|
222
|
+
agentId: ctx.binding.agentId,
|
|
223
|
+
threadId,
|
|
224
|
+
messageId: (addResult as Record<string, unknown>)?.messageId,
|
|
225
|
+
result: JSON.stringify(addResult),
|
|
226
|
+
}, "Agent dispatch complete — messageId = " + ((addResult as Record<string, unknown>)?.messageId || "N/A"));
|
|
150
227
|
|
|
151
228
|
if (message.replyTarget) {
|
|
152
229
|
const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
|
|
@@ -157,7 +234,7 @@ export class MessageRouter {
|
|
|
157
234
|
if (installation) {
|
|
158
235
|
await adapter.sendReply(
|
|
159
236
|
message.replyTarget,
|
|
160
|
-
{ text: ctx.result },
|
|
237
|
+
{ text: ctx.result || "" },
|
|
161
238
|
installation,
|
|
162
239
|
);
|
|
163
240
|
}
|
|
@@ -173,6 +250,7 @@ export class MessageRouter {
|
|
|
173
250
|
};
|
|
174
251
|
} catch (error) {
|
|
175
252
|
ctx.error = error instanceof Error ? error : new Error(String(error));
|
|
253
|
+
this.logger?.error({ event: "dispatch:error", error: ctx.error.message, channel: message.channel, senderId: message.sender.id }, "Message dispatch failed");
|
|
176
254
|
return {
|
|
177
255
|
success: false,
|
|
178
256
|
bindingId: ctx.binding?.id,
|
|
@@ -196,16 +274,3 @@ export class MessageRouter {
|
|
|
196
274
|
return dispatch(0);
|
|
197
275
|
}
|
|
198
276
|
}
|
|
199
|
-
|
|
200
|
-
function extractTextFromInvokeResult(result: unknown): string {
|
|
201
|
-
if (result && typeof result === "object" && "messages" in result) {
|
|
202
|
-
const messages = (result as { messages: Array<{ role: string; content: string }> }).messages;
|
|
203
|
-
if (Array.isArray(messages)) {
|
|
204
|
-
const aiMessages = messages.filter((m) => m.role === "ai");
|
|
205
|
-
if (aiMessages.length > 0) {
|
|
206
|
-
return aiMessages.map((m) => m.content).join("\n");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return JSON.stringify(result);
|
|
211
|
-
}
|
|
@@ -2,18 +2,31 @@ import type { MessageMiddleware } from "../MessageContext";
|
|
|
2
2
|
|
|
3
3
|
const processedMessages = new Map<string, number>();
|
|
4
4
|
|
|
5
|
-
export function createDeduplicationMiddleware(
|
|
5
|
+
export function createDeduplicationMiddleware(
|
|
6
|
+
ttlMs: number = 5 * 60 * 1000,
|
|
7
|
+
logger?: any,
|
|
8
|
+
): MessageMiddleware {
|
|
6
9
|
return async (ctx, next) => {
|
|
7
10
|
const msg = ctx.inboundMessage;
|
|
8
11
|
const msgId = msg.content.metadata?.messageId as string | undefined;
|
|
9
12
|
const key = msgId
|
|
10
13
|
? `${msg.channel}:${msg.channelInstallationId}:${msgId}`
|
|
11
|
-
:
|
|
12
|
-
const now = Date.now();
|
|
13
|
-
const lastProcessed = processedMessages.get(key);
|
|
14
|
-
if (lastProcessed && (now - lastProcessed) < ttlMs) return;
|
|
14
|
+
: null;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
if (key) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const lastProcessed = processedMessages.get(key);
|
|
19
|
+
if (lastProcessed && (now - lastProcessed) < ttlMs) {
|
|
20
|
+
logger?.warn({
|
|
21
|
+
event: "dedup:blocked",
|
|
22
|
+
channel: msg.channel,
|
|
23
|
+
senderId: msg.sender.id,
|
|
24
|
+
messageId: msgId,
|
|
25
|
+
}, "Duplicate message blocked by deduplication");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
processedMessages.set(key, now);
|
|
29
|
+
}
|
|
17
30
|
|
|
18
31
|
if (processedMessages.size > 10000) {
|
|
19
32
|
const oldest = Array.from(processedMessages.entries())
|
|
@@ -14,6 +14,7 @@ export function createRateLimitMiddleware(
|
|
|
14
14
|
maxRequests: number = 10,
|
|
15
15
|
windowMs: number = 60 * 1000,
|
|
16
16
|
maxEntries: number = 10000,
|
|
17
|
+
logger?: any,
|
|
17
18
|
): MessageMiddleware {
|
|
18
19
|
return async (ctx, next) => {
|
|
19
20
|
const senderKey = `${ctx.inboundMessage.channel}:${ctx.inboundMessage.sender.id}`;
|
|
@@ -32,6 +33,13 @@ export function createRateLimitMiddleware(
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
if (counter.count > maxRequests) {
|
|
36
|
+
logger?.warn({
|
|
37
|
+
event: "ratelimit:blocked",
|
|
38
|
+
channel: ctx.inboundMessage.channel,
|
|
39
|
+
senderId: ctx.inboundMessage.sender.id,
|
|
40
|
+
count: counter.count,
|
|
41
|
+
maxRequests,
|
|
42
|
+
}, "Rate limit exceeded");
|
|
35
43
|
throw new RateLimitError(`Rate limit exceeded`);
|
|
36
44
|
}
|
|
37
45
|
await next();
|
package/src/routes/index.ts
CHANGED
|
@@ -390,7 +390,12 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
|
|
|
390
390
|
} as Parameters<typeof router.dispatch>[0];
|
|
391
391
|
|
|
392
392
|
await router.dispatch(inboundMessage).catch((error) => {
|
|
393
|
-
console.error(
|
|
393
|
+
console.error(JSON.stringify({
|
|
394
|
+
event: "inbound:dispatch_error",
|
|
395
|
+
error: error instanceof Error ? error.message : String(error),
|
|
396
|
+
channel: inboundMessage.channel,
|
|
397
|
+
senderId: inboundMessage.sender.id,
|
|
398
|
+
}));
|
|
394
399
|
});
|
|
395
400
|
|
|
396
401
|
reply.status(200).send({ accepted: true });
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @axiom-lattice/gateway@2.1.80 build /home/runner/work/agentic/agentic/packages/gateway
|
|
3
|
-
> tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.0
|
|
8
|
-
[34mCLI[39m Target: es2020
|
|
9
|
-
[34mCLI[39m Cleaning output folder
|
|
10
|
-
[34mCJS[39m Build start
|
|
11
|
-
[34mESM[39m Build start
|
|
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
|
-
|
|
14
|
-
src/index.ts:178:33:
|
|
15
|
-
[37m 178 │ const __filename = fileURLToPath([32mimport.meta[37m.url);
|
|
16
|
-
╵ [32m~~~~~~~~~~~[0m
|
|
17
|
-
|
|
18
|
-
You need to set the output format to "esm" for "import.meta" to work correctly.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[32mCJS[39m [1mdist/index.js [22m[32m239.18 KB[39m
|
|
22
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m502.61 KB[39m
|
|
23
|
-
[32mCJS[39m ⚡️ Build success in 314ms
|
|
24
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m234.48 KB[39m
|
|
25
|
-
[32mESM[39m [1mdist/sender-PX32VSHB.mjs [22m[32m873.00 B[39m
|
|
26
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m501.10 KB[39m
|
|
27
|
-
[32mESM[39m [1mdist/sender-PX32VSHB.mjs.map [22m[32m2.07 KB[39m
|
|
28
|
-
[32mESM[39m ⚡️ Build success in 322ms
|
|
29
|
-
[34mDTS[39m Build start
|
|
30
|
-
[32mDTS[39m ⚡️ Build success in 14133ms
|
|
31
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m5.01 KB[39m
|
|
32
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m5.01 KB[39m
|