@axiom-lattice/gateway 2.1.81 → 2.1.83
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 +32 -0
- package/CHANGELOG.md +18 -0
- package/dist/index.js +64 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +2 -2
- package/src/router/MessageRouter.ts +32 -0
- package/src/router/middlewares/deduplication.ts +19 -6
- package/src/router/middlewares/rateLimit.ts +8 -0
- package/src/routes/index.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.83",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,11 +40,11 @@
|
|
|
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.
|
|
46
|
-
"@axiom-lattice/protocols": "2.1.
|
|
47
|
-
"@axiom-lattice/queue-redis": "1.0.
|
|
43
|
+
"@axiom-lattice/agent-eval": "2.1.66",
|
|
44
|
+
"@axiom-lattice/core": "2.1.72",
|
|
45
|
+
"@axiom-lattice/pg-stores": "1.0.62",
|
|
46
|
+
"@axiom-lattice/protocols": "2.1.37",
|
|
47
|
+
"@axiom-lattice/queue-redis": "1.0.36"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/jest": "^29.5.14",
|
package/src/index.ts
CHANGED
|
@@ -287,8 +287,8 @@ 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,
|
|
@@ -59,6 +59,13 @@ export class MessageRouter {
|
|
|
59
59
|
|| (await this.installationStore.getInstallationById(message.channelInstallationId))?.tenantId;
|
|
60
60
|
|
|
61
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");
|
|
62
69
|
throw new Error(
|
|
63
70
|
"tenantId is required: provide it in the message or ensure the channelInstallation has a tenantId"
|
|
64
71
|
);
|
|
@@ -79,12 +86,25 @@ export class MessageRouter {
|
|
|
79
86
|
);
|
|
80
87
|
|
|
81
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");
|
|
82
96
|
throw new BindingNotFoundError(
|
|
83
97
|
`No binding for sender "${message.sender.id}" on channel "${message.channel}"`,
|
|
84
98
|
);
|
|
85
99
|
}
|
|
86
100
|
|
|
87
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");
|
|
88
108
|
binding = {
|
|
89
109
|
id: "fallback",
|
|
90
110
|
channel: message.channel,
|
|
@@ -99,6 +119,12 @@ export class MessageRouter {
|
|
|
99
119
|
updatedAt: new Date(),
|
|
100
120
|
};
|
|
101
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");
|
|
102
128
|
throw new BindingNotFoundError(
|
|
103
129
|
`No binding for sender "${message.sender.id}" and no fallback configured`,
|
|
104
130
|
);
|
|
@@ -126,6 +152,12 @@ export class MessageRouter {
|
|
|
126
152
|
}
|
|
127
153
|
|
|
128
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");
|
|
129
161
|
throw new BindingNotFoundError(
|
|
130
162
|
`Binding for sender "${message.sender.id}" is disabled`,
|
|
131
163
|
);
|
|
@@ -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 });
|