@axiom-lattice/gateway 8.3.0 → 8.4.0
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/dist/{A2AAuthService-7UGQ7GMW.mjs → A2AAuthService-UKN53ZGP.mjs} +2 -2
- package/dist/{a2a-BHBLACJV.mjs → a2a-TDZ3TT6H.mjs} +3 -3
- package/dist/{a2a-standard-VL5I7B4W.mjs → a2a-standard-INVCUSAW.mjs} +39 -2
- package/dist/a2a-standard-INVCUSAW.mjs.map +1 -0
- package/dist/chunk-A7YNAU5L.mjs +377 -0
- package/dist/chunk-A7YNAU5L.mjs.map +1 -0
- package/dist/{chunk-3XIIQ6EK.mjs → chunk-BOPNXTMO.mjs} +66 -5
- package/dist/chunk-BOPNXTMO.mjs.map +1 -0
- package/dist/{chunk-L6MBEEII.mjs → chunk-JGQURU44.mjs} +24 -6
- package/dist/chunk-JGQURU44.mjs.map +1 -0
- package/dist/{chunk-GPHOYR3X.mjs → chunk-LCM4OQTI.mjs} +30 -1
- package/dist/chunk-LCM4OQTI.mjs.map +1 -0
- package/dist/{chunk-HVIKCUOF.mjs → chunk-MFWTGNQR.mjs} +74 -5
- package/dist/chunk-MFWTGNQR.mjs.map +1 -0
- package/dist/{chunk-HYYLYIMH.mjs → chunk-OBODBHIS.mjs} +2 -2
- package/dist/chunk-T2HIWZGP.mjs +580 -0
- package/dist/chunk-T2HIWZGP.mjs.map +1 -0
- package/dist/chunk-Y7OVVPVU.mjs +13 -0
- package/dist/chunk-Y7OVVPVU.mjs.map +1 -0
- package/dist/{chunk-GL7RV5L3.mjs → chunk-YR32MWEJ.mjs} +36 -73
- package/dist/chunk-YR32MWEJ.mjs.map +1 -0
- package/dist/embed/{embed-page-AgentDetailSideApp-O6G4NJ7I.css → embed-page-AgentDetailSideApp-JMJOOC4V.css} +1 -1
- package/dist/embed/{embed-page-AgentDetailSideApp-7YTMN643.js → embed-page-AgentDetailSideApp-TQEJK6ZF.js} +1 -1
- package/dist/embed/{embed-page-AgentGovernanceWorkbench-SFZNVMUH.css → embed-page-AgentGovernanceWorkbench-3T3ZFJI5.css} +1 -1
- package/dist/embed/{embed-page-AgentGovernanceWorkbench-YWJ4ZLEV.js → embed-page-AgentGovernanceWorkbench-II2MRGCG.js} +1 -1
- package/dist/embed/{embed-page-LangfuseDashboard-BEOJDMWF.js → embed-page-LangfuseDashboard-7OANGQXY.js} +1 -1
- package/dist/embed/{embed-page-MessageGraph-N2DXVMEK.js → embed-page-MessageGraph-KJPQWTHE.js} +1 -1
- package/dist/embed/{embed-page-TopologyRuntimeView-CWOXLVFW.css → embed-page-MessageGraph-UDC6GQA5.css} +1 -1
- package/dist/embed/{embed-page-ReactChart-3QDZX2WG.js → embed-page-ReactChart-2CAIM4JD.js} +1 -1
- package/dist/embed/{embed-page-ReactChart-MXGF35OQ.css → embed-page-ReactChart-URBKYQYE.css} +1 -1
- package/dist/embed/{embed-page-SkillFlow-3X24ILKU.js → embed-page-SkillFlow-243MXRXC.js} +1 -1
- package/dist/embed/{embed-page-TasksSideApp-MYIRJWFM.js → embed-page-TasksSideApp-OA7CULHS.js} +1 -1
- package/dist/embed/{embed-page-TasksSideApp-HV6DKQ26.css → embed-page-TasksSideApp-TTCP4ENP.css} +1 -1
- package/dist/embed/{embed-page-ThreadMaintenancePanel-V327PV5R.js → embed-page-ThreadMaintenancePanel-Z4424X3J.js} +1 -1
- package/dist/embed/{embed-page-WorkflowAutomationView-YQFB3CTV.css → embed-page-TopologyRuntimeView-S4AMB4RI.css} +1 -1
- package/dist/embed/{embed-page-TopologyRuntimeView-3O6VJUZR.js → embed-page-TopologyRuntimeView-THTCZUS7.js} +1 -1
- package/dist/embed/{embed-page-MessageGraph-UT2TCPSO.css → embed-page-WorkflowAutomationView-FWVCQ3YM.css} +1 -1
- package/dist/embed/{embed-page-WorkflowAutomationView-YHOS4WQR.js → embed-page-WorkflowAutomationView-YDIDG35E.js} +1 -1
- package/dist/embed/{embed-page-chunk-4XKCMM5C.js → embed-page-chunk-7ULTMRM2.js} +1 -1
- package/dist/embed/{embed-page-chunk-BSRASK7V.js → embed-page-chunk-FZX4A32D.js} +5 -5
- package/dist/embed/{embed-page-chunk-PMOGSY6W.js → embed-page-chunk-JMP7EQEF.js} +1 -1
- package/dist/embed/{embed-page-chunk-IELKCYQX.js → embed-page-chunk-L2EGVVJS.js} +1 -1
- package/dist/embed/{embed-page-chunk-WGEXK773.js → embed-page-chunk-LDQGH44H.js} +1 -1
- package/dist/embed/{embed-page-chunk-JTL73EBV.js → embed-page-chunk-OMN6G52V.js} +1 -1
- package/dist/embed/{embed-page-chunk-PROZXFWS.js → embed-page-chunk-RKQJBKN2.js} +1 -1
- package/dist/embed/{embed-page-chunk-YMVKQQXJ.js → embed-page-chunk-VYS3TAK4.js} +1 -1
- package/dist/embed/{embed-page-chunk-ASEIOK7X.js → embed-page-chunk-W5OP2CZG.js} +2 -2
- package/dist/embed/embed-page-chunk-Z6KWW64I.js +6837 -0
- package/dist/embed/{embed-page-en-E35GE7TA.js → embed-page-en-D4M3DPFT.js} +1 -1
- package/dist/embed/{embed-page-en-XJYWUJEL.js → embed-page-en-DNDGCPMA.js} +1 -1
- package/dist/embed/embed-page-en-H5NOZEKE.js +1 -0
- package/dist/embed/{embed-page-en-HDSVQ4O6.js → embed-page-en-QJRX2NQE.js} +1 -1
- package/dist/embed/{embed-page-zh-6EBUEFKP.js → embed-page-zh-2H4EB3F3.js} +1 -1
- package/dist/embed/embed-page-zh-4C2K3RH2.js +1 -0
- package/dist/embed/{embed-page-zh-IR652A6J.js → embed-page-zh-HTEHFCSL.js} +1 -1
- package/dist/embed/{embed-page-zh-ZLK3GLM3.js → embed-page-zh-NEXLMDHF.js} +1 -1
- package/dist/embed/embed-page.css +1 -1
- package/dist/embed/embed-page.js +1 -1
- package/dist/embed/mcp-app-page.js +61 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +2551 -1476
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -690
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-app-shares-SHPBJQXS.mjs +300 -0
- package/dist/mcp-app-shares-SHPBJQXS.mjs.map +1 -0
- package/dist/mcp-apps-QVUK6UBA.mjs +26 -0
- package/dist/{open-7ED27I5B.mjs → open-27ZPLBIW.mjs} +101 -18
- package/dist/open-27ZPLBIW.mjs.map +1 -0
- package/dist/{open-catalog-SZQ435AY.mjs → open-catalog-NVGP6SQP.mjs} +38 -7
- package/dist/open-catalog-NVGP6SQP.mjs.map +1 -0
- package/dist/{open-exposure-I3DN2X4E.mjs → open-exposure-BPLYUIX6.mjs} +4 -4
- package/dist/{open-files-V3R2BFTV.mjs → open-files-JPB2NBL6.mjs} +4 -4
- package/dist/{open-session-2Y7XJJW2.mjs → open-session-CLZT3HVG.mjs} +5 -5
- package/dist/open-session-CLZT3HVG.mjs.map +1 -0
- package/dist/openLattice-D5YTSZBY.mjs +28 -0
- package/dist/{registerExposedPlugins-6GCOE64C.mjs → registerExposedPlugins-2JGXSMAL.mjs} +4 -3
- package/dist/{resources-DAU5ZIPB.mjs → resources-YD5DOPGD.mjs} +83 -3
- package/dist/resources-YD5DOPGD.mjs.map +1 -0
- package/dist/{sandboxOpenCredentialProvider-AT227D4N.mjs → sandboxOpenCredentialProvider-RTYOYRCT.mjs} +19 -14
- package/dist/sandboxOpenCredentialProvider-RTYOYRCT.mjs.map +1 -0
- package/package.json +10 -8
- package/dist/a2a-standard-VL5I7B4W.mjs.map +0 -1
- package/dist/chunk-3XIIQ6EK.mjs.map +0 -1
- package/dist/chunk-GL7RV5L3.mjs.map +0 -1
- package/dist/chunk-GPHOYR3X.mjs.map +0 -1
- package/dist/chunk-GXZFO3VU.mjs +0 -42
- package/dist/chunk-GXZFO3VU.mjs.map +0 -1
- package/dist/chunk-HVIKCUOF.mjs.map +0 -1
- package/dist/chunk-L6MBEEII.mjs.map +0 -1
- package/dist/embed/embed-page-chunk-IBRMWDHE.js +0 -6835
- package/dist/embed/embed-page-en-GLT2HEZZ.js +0 -1
- package/dist/embed/embed-page-zh-XMEJPDMF.js +0 -1
- package/dist/open-7ED27I5B.mjs.map +0 -1
- package/dist/open-catalog-SZQ435AY.mjs.map +0 -1
- package/dist/open-session-2Y7XJJW2.mjs.map +0 -1
- package/dist/openLattice-Q6M5S6CY.mjs +0 -18
- package/dist/openRateLimiter-XEOL3A5G.mjs +0 -8
- package/dist/resources-DAU5ZIPB.mjs.map +0 -1
- package/dist/sandboxOpenCredentialProvider-AT227D4N.mjs.map +0 -1
- /package/dist/{A2AAuthService-7UGQ7GMW.mjs.map → A2AAuthService-UKN53ZGP.mjs.map} +0 -0
- /package/dist/{a2a-BHBLACJV.mjs.map → a2a-TDZ3TT6H.mjs.map} +0 -0
- /package/dist/{chunk-HYYLYIMH.mjs.map → chunk-OBODBHIS.mjs.map} +0 -0
- /package/dist/{openLattice-Q6M5S6CY.mjs.map → mcp-apps-QVUK6UBA.mjs.map} +0 -0
- /package/dist/{open-exposure-I3DN2X4E.mjs.map → open-exposure-BPLYUIX6.mjs.map} +0 -0
- /package/dist/{open-files-V3R2BFTV.mjs.map → open-files-JPB2NBL6.mjs.map} +0 -0
- /package/dist/{openRateLimiter-XEOL3A5G.mjs.map → openLattice-D5YTSZBY.mjs.map} +0 -0
- /package/dist/{registerExposedPlugins-6GCOE64C.mjs.map → registerExposedPlugins-2JGXSMAL.mjs.map} +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// src/services/rate-limit/RateLimitService.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/services/rate-limit/defaultPolicies.ts
|
|
5
|
+
var DEFAULT_RATE_LIMIT_POLICIES = {
|
|
6
|
+
"mcp-app-share.rpc": {
|
|
7
|
+
limit: 600,
|
|
8
|
+
windowMs: 6e4,
|
|
9
|
+
dimensions: ["token", "ip"],
|
|
10
|
+
dimensionLimits: { ip: 60 }
|
|
11
|
+
},
|
|
12
|
+
"mcp-app-share.unlock": {
|
|
13
|
+
limit: 5,
|
|
14
|
+
windowMs: 6e4,
|
|
15
|
+
dimensions: ["token", "ip"]
|
|
16
|
+
},
|
|
17
|
+
"mcp-app-share.bootstrap": {
|
|
18
|
+
limit: 300,
|
|
19
|
+
windowMs: 6e4,
|
|
20
|
+
dimensions: ["ip"]
|
|
21
|
+
},
|
|
22
|
+
"auth.login": {
|
|
23
|
+
limit: 10,
|
|
24
|
+
windowMs: 6e4,
|
|
25
|
+
dimensions: ["ip"]
|
|
26
|
+
},
|
|
27
|
+
"open.api": {
|
|
28
|
+
limit: 6e4,
|
|
29
|
+
windowMs: 6e4,
|
|
30
|
+
dimensions: ["credential"]
|
|
31
|
+
},
|
|
32
|
+
"open.agent": {
|
|
33
|
+
limit: 2e3,
|
|
34
|
+
windowMs: 6e4,
|
|
35
|
+
dimensions: ["credential"]
|
|
36
|
+
},
|
|
37
|
+
"a2a.jsonrpc": {
|
|
38
|
+
limit: 600,
|
|
39
|
+
windowMs: 6e4,
|
|
40
|
+
dimensions: ["key", "ip"],
|
|
41
|
+
dimensionLimits: { ip: 300 }
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function legacyOpenRateLimitPolicies(env = process.env) {
|
|
45
|
+
const open = Number(env.OPEN_RATE_LIMIT_PER_MINUTE);
|
|
46
|
+
const agent = Number(env.OPEN_AGENT_RATE_LIMIT_PER_MINUTE);
|
|
47
|
+
return {
|
|
48
|
+
...Number.isFinite(open) && open > 0 ? { "open.api": { limit: open } } : {},
|
|
49
|
+
...Number.isFinite(agent) && agent > 0 ? { "open.agent": { limit: agent } } : {}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/router/middlewares/channelSharedState.ts
|
|
54
|
+
import { createClient } from "redis";
|
|
55
|
+
var clientPromises = /* @__PURE__ */ new Map();
|
|
56
|
+
async function getSharedRedisClient(url = process.env.REDIS_URL?.trim()) {
|
|
57
|
+
if (!url) return void 0;
|
|
58
|
+
let clientPromise = clientPromises.get(url);
|
|
59
|
+
if (!clientPromise) {
|
|
60
|
+
clientPromise = (async () => {
|
|
61
|
+
const client = createClient({ url });
|
|
62
|
+
client.on("error", () => void 0);
|
|
63
|
+
await client.connect();
|
|
64
|
+
return client;
|
|
65
|
+
})();
|
|
66
|
+
clientPromises.set(url, clientPromise);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const client = await clientPromise;
|
|
70
|
+
return client.isOpen ? client : void 0;
|
|
71
|
+
} catch {
|
|
72
|
+
if (clientPromises.get(url) === clientPromise) clientPromises.delete(url);
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function claimDedupKey(key, ttlMs) {
|
|
77
|
+
try {
|
|
78
|
+
const client = await getSharedRedisClient();
|
|
79
|
+
if (!client) return void 0;
|
|
80
|
+
const result = await client.set(key, "1", { NX: true, PX: ttlMs });
|
|
81
|
+
return result === "OK";
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function incrementRateCounter(key, windowMs) {
|
|
87
|
+
try {
|
|
88
|
+
const client = await getSharedRedisClient();
|
|
89
|
+
if (!client) return void 0;
|
|
90
|
+
const count = await client.incr(key);
|
|
91
|
+
if (count === 1) await client.pExpire(key, windowMs);
|
|
92
|
+
return count;
|
|
93
|
+
} catch {
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/services/rate-limit/RateLimitStore.ts
|
|
99
|
+
var INCREMENT_SCRIPT = `
|
|
100
|
+
local count = redis.call("INCR", KEYS[1])
|
|
101
|
+
local ttl = redis.call("PTTL", KEYS[1])
|
|
102
|
+
if count == 1 or ttl < 0 then
|
|
103
|
+
redis.call("PEXPIRE", KEYS[1], ARGV[1])
|
|
104
|
+
ttl = tonumber(ARGV[1])
|
|
105
|
+
end
|
|
106
|
+
return {count, ttl}
|
|
107
|
+
`;
|
|
108
|
+
var InMemoryRateLimitStore = class {
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
this.options = options;
|
|
111
|
+
this.counters = /* @__PURE__ */ new Map();
|
|
112
|
+
}
|
|
113
|
+
async increment(key, windowMs) {
|
|
114
|
+
const now = this.options.now?.() ?? Date.now();
|
|
115
|
+
const current = this.counters.get(key);
|
|
116
|
+
const next = !current || now >= current.resetAt ? { count: 1, resetAt: now + windowMs } : { count: current.count + 1, resetAt: current.resetAt };
|
|
117
|
+
this.counters.set(key, next);
|
|
118
|
+
this.evict(now);
|
|
119
|
+
return {
|
|
120
|
+
count: next.count,
|
|
121
|
+
ttlMs: Math.max(0, next.resetAt - now)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** Drop expired entries, then the oldest entries if still over the cap. */
|
|
125
|
+
evict(now) {
|
|
126
|
+
const max = this.options.maxEntries ?? 1e4;
|
|
127
|
+
if (this.counters.size <= max) return;
|
|
128
|
+
for (const [key, counter] of this.counters) {
|
|
129
|
+
if (counter.resetAt <= now) this.counters.delete(key);
|
|
130
|
+
}
|
|
131
|
+
if (this.counters.size <= max) return;
|
|
132
|
+
let excess = this.counters.size - max;
|
|
133
|
+
for (const key of this.counters.keys()) {
|
|
134
|
+
if (excess <= 0) break;
|
|
135
|
+
this.counters.delete(key);
|
|
136
|
+
excess -= 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var RedisRateLimitStore = class {
|
|
141
|
+
constructor(options = {}) {
|
|
142
|
+
this.options = options;
|
|
143
|
+
}
|
|
144
|
+
async increment(key, windowMs) {
|
|
145
|
+
try {
|
|
146
|
+
const client = await getSharedRedisClient(this.options.url);
|
|
147
|
+
if (!client) return void 0;
|
|
148
|
+
const result = await client.eval(INCREMENT_SCRIPT, {
|
|
149
|
+
keys: [key],
|
|
150
|
+
arguments: [String(windowMs)]
|
|
151
|
+
});
|
|
152
|
+
if (!Array.isArray(result) || result.length < 2) return void 0;
|
|
153
|
+
const count = Number(result[0]);
|
|
154
|
+
const ttlMs = Number(result[1]);
|
|
155
|
+
if (!Number.isFinite(count) || count < 1) return void 0;
|
|
156
|
+
return {
|
|
157
|
+
count,
|
|
158
|
+
ttlMs: Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : windowMs
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.options.onError?.(error);
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var HybridRateLimitStore = class {
|
|
167
|
+
constructor(redis, options = {}) {
|
|
168
|
+
this.redis = redis;
|
|
169
|
+
this.memory = new InMemoryRateLimitStore(options);
|
|
170
|
+
}
|
|
171
|
+
async increment(key, windowMs) {
|
|
172
|
+
if (this.redis) {
|
|
173
|
+
const shared = await this.redis.increment(key, windowMs);
|
|
174
|
+
if (shared) return shared;
|
|
175
|
+
}
|
|
176
|
+
return this.memory.increment(key, windowMs);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/services/rate-limit/RateLimitService.ts
|
|
181
|
+
var DIMENSIONS = /* @__PURE__ */ new Set([
|
|
182
|
+
"ip",
|
|
183
|
+
"token",
|
|
184
|
+
"user",
|
|
185
|
+
"tenant",
|
|
186
|
+
"credential",
|
|
187
|
+
"key",
|
|
188
|
+
"session"
|
|
189
|
+
]);
|
|
190
|
+
var DEFAULT_MAX_ENTRIES = 1e4;
|
|
191
|
+
function hashDimension(value) {
|
|
192
|
+
return createHash("sha256").update(value).digest("hex");
|
|
193
|
+
}
|
|
194
|
+
function normalizePolicy(name, value, base, logger = console) {
|
|
195
|
+
const limit = value.limit ?? base?.limit;
|
|
196
|
+
const windowMs = value.windowMs ?? base?.windowMs;
|
|
197
|
+
const dimensions = value.dimensions ?? base?.dimensions;
|
|
198
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0 || typeof windowMs !== "number" || !Number.isFinite(windowMs) || windowMs <= 0 || !Array.isArray(dimensions) || dimensions.length === 0 || dimensions.some((dimension) => !DIMENSIONS.has(dimension))) {
|
|
199
|
+
logger.warn(JSON.stringify({
|
|
200
|
+
event: "rate-limit:invalid-policy",
|
|
201
|
+
policy: name
|
|
202
|
+
}));
|
|
203
|
+
return base ? { ...base, dimensions: [...base.dimensions] } : void 0;
|
|
204
|
+
}
|
|
205
|
+
const dimensionLimits = {
|
|
206
|
+
...base?.dimensionLimits ?? {}
|
|
207
|
+
};
|
|
208
|
+
const rawDimensionLimits = value.dimensionLimits;
|
|
209
|
+
if (rawDimensionLimits !== void 0) {
|
|
210
|
+
if (typeof rawDimensionLimits !== "object" || rawDimensionLimits === null || Array.isArray(rawDimensionLimits)) {
|
|
211
|
+
logger.warn(JSON.stringify({
|
|
212
|
+
event: "rate-limit:invalid-dimension-limits",
|
|
213
|
+
policy: name
|
|
214
|
+
}));
|
|
215
|
+
} else {
|
|
216
|
+
for (const [dimension, raw] of Object.entries(rawDimensionLimits)) {
|
|
217
|
+
if (!DIMENSIONS.has(dimension) || typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) {
|
|
218
|
+
logger.warn(JSON.stringify({
|
|
219
|
+
event: "rate-limit:invalid-dimension-limit",
|
|
220
|
+
policy: name,
|
|
221
|
+
dimension
|
|
222
|
+
}));
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
dimensionLimits[dimension] = Math.floor(raw);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
limit: Math.floor(limit),
|
|
231
|
+
windowMs: Math.floor(windowMs),
|
|
232
|
+
dimensions: [...dimensions],
|
|
233
|
+
...Object.keys(dimensionLimits).length > 0 ? { dimensionLimits } : {},
|
|
234
|
+
...value.enabled !== void 0 ? { enabled: value.enabled } : base?.enabled !== void 0 ? { enabled: base.enabled } : {}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function parseRateLimitPoliciesEnv(raw, logger = console) {
|
|
238
|
+
if (!raw || raw.trim() === "") return {};
|
|
239
|
+
try {
|
|
240
|
+
const parsed = JSON.parse(raw);
|
|
241
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
242
|
+
throw new Error("expected an object");
|
|
243
|
+
}
|
|
244
|
+
return parsed;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.warn(JSON.stringify({
|
|
247
|
+
event: "rate-limit:invalid-env-policies",
|
|
248
|
+
error: error instanceof Error ? error.message : String(error)
|
|
249
|
+
}));
|
|
250
|
+
return {};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function mergeRateLimitPolicies(config = {}, logger = console) {
|
|
254
|
+
const merged = {};
|
|
255
|
+
for (const [name, policy] of Object.entries(DEFAULT_RATE_LIMIT_POLICIES)) {
|
|
256
|
+
merged[name] = { ...policy, dimensions: [...policy.dimensions] };
|
|
257
|
+
}
|
|
258
|
+
const overrides = [
|
|
259
|
+
legacyOpenRateLimitPolicies(process.env),
|
|
260
|
+
parseRateLimitPoliciesEnv(process.env.AXIOM_RATE_LIMIT_POLICIES, logger),
|
|
261
|
+
config.policies ?? {}
|
|
262
|
+
];
|
|
263
|
+
for (const layer of overrides) {
|
|
264
|
+
for (const [name, value] of Object.entries(layer)) {
|
|
265
|
+
const policy = normalizePolicy(name, value, merged[name], logger);
|
|
266
|
+
if (policy) merged[name] = policy;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return merged;
|
|
270
|
+
}
|
|
271
|
+
var RateLimitService = class {
|
|
272
|
+
constructor(options) {
|
|
273
|
+
this.options = options;
|
|
274
|
+
this.warnedPolicies = /* @__PURE__ */ new Set();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Consume one request against a named policy.
|
|
278
|
+
*
|
|
279
|
+
* @param policyName - Policy key (for example `mcp-app-share.rpc`).
|
|
280
|
+
* @param values - Dimension values for this request.
|
|
281
|
+
* @returns The decision; `allowed:false` means the caller should return 429.
|
|
282
|
+
*/
|
|
283
|
+
async consume(policyName, values) {
|
|
284
|
+
if (!this.options.enabled) {
|
|
285
|
+
return {
|
|
286
|
+
allowed: true,
|
|
287
|
+
policy: policyName,
|
|
288
|
+
limit: 0,
|
|
289
|
+
remaining: 0,
|
|
290
|
+
retryAfterSeconds: 0
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const policy = this.options.policies[policyName];
|
|
294
|
+
if (!policy && !this.warnedPolicies.has(policyName)) {
|
|
295
|
+
this.warnedPolicies.add(policyName);
|
|
296
|
+
this.options.logger?.warn(JSON.stringify({
|
|
297
|
+
event: "rate-limit:unknown-policy",
|
|
298
|
+
policy: policyName
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
if (!policy || policy.enabled === false) {
|
|
302
|
+
return {
|
|
303
|
+
allowed: true,
|
|
304
|
+
policy: policyName,
|
|
305
|
+
limit: policy?.limit ?? 0,
|
|
306
|
+
remaining: policy?.limit ?? 0,
|
|
307
|
+
retryAfterSeconds: 0
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
let remaining = policy.limit;
|
|
311
|
+
for (const dimension of policy.dimensions) {
|
|
312
|
+
const raw = values[dimension];
|
|
313
|
+
if (typeof raw !== "string" || raw === "") continue;
|
|
314
|
+
const key = `axiom:rate-limit:${policyName}:${dimension}:${hashDimension(raw)}`;
|
|
315
|
+
const result = await this.options.store.increment(key, policy.windowMs);
|
|
316
|
+
if (!result) continue;
|
|
317
|
+
const dimensionLimit = policy.dimensionLimits?.[dimension] ?? policy.limit;
|
|
318
|
+
remaining = Math.min(remaining, dimensionLimit - result.count);
|
|
319
|
+
if (result.count > dimensionLimit) {
|
|
320
|
+
return {
|
|
321
|
+
allowed: false,
|
|
322
|
+
policy: policyName,
|
|
323
|
+
limit: dimensionLimit,
|
|
324
|
+
remaining: 0,
|
|
325
|
+
retryAfterSeconds: Math.max(1, Math.ceil(result.ttlMs / 1e3)),
|
|
326
|
+
exceededDimension: dimension
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
allowed: true,
|
|
332
|
+
policy: policyName,
|
|
333
|
+
limit: policy.limit,
|
|
334
|
+
remaining: Math.max(0, remaining),
|
|
335
|
+
retryAfterSeconds: 0
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function createRateLimitService(config = {}, logger = console) {
|
|
340
|
+
const redisUrl = config.redisUrl ?? process.env.REDIS_URL?.trim();
|
|
341
|
+
const redis = redisUrl ? new RedisRateLimitStore({
|
|
342
|
+
url: redisUrl,
|
|
343
|
+
onError: (error) => logger.warn(JSON.stringify({
|
|
344
|
+
event: "rate-limit:redis-unavailable",
|
|
345
|
+
error: error instanceof Error ? error.message : String(error)
|
|
346
|
+
}))
|
|
347
|
+
}) : void 0;
|
|
348
|
+
return new RateLimitService({
|
|
349
|
+
store: new HybridRateLimitStore(redis, {
|
|
350
|
+
maxEntries: config.maxInMemoryEntries ?? DEFAULT_MAX_ENTRIES
|
|
351
|
+
}),
|
|
352
|
+
policies: mergeRateLimitPolicies(config, logger),
|
|
353
|
+
enabled: config.enabled ?? process.env.AXIOM_RATE_LIMIT_DISABLED !== "true",
|
|
354
|
+
logger
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
var disabledService = new RateLimitService({
|
|
358
|
+
store: new HybridRateLimitStore(void 0),
|
|
359
|
+
policies: {},
|
|
360
|
+
enabled: false
|
|
361
|
+
});
|
|
362
|
+
var defaultService;
|
|
363
|
+
function setRateLimitService(service) {
|
|
364
|
+
defaultService = service;
|
|
365
|
+
}
|
|
366
|
+
function getRateLimitService() {
|
|
367
|
+
return defaultService ?? disabledService;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export {
|
|
371
|
+
claimDedupKey,
|
|
372
|
+
incrementRateCounter,
|
|
373
|
+
createRateLimitService,
|
|
374
|
+
setRateLimitService,
|
|
375
|
+
getRateLimitService
|
|
376
|
+
};
|
|
377
|
+
//# sourceMappingURL=chunk-A7YNAU5L.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/rate-limit/RateLimitService.ts","../src/services/rate-limit/defaultPolicies.ts","../src/router/middlewares/channelSharedState.ts","../src/services/rate-limit/RateLimitStore.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n DEFAULT_RATE_LIMIT_POLICIES,\n legacyOpenRateLimitPolicies,\n} from \"./defaultPolicies\";\nimport {\n HybridRateLimitStore,\n RedisRateLimitStore,\n} from \"./RateLimitStore\";\nimport type {\n RateLimitConfig,\n RateLimitDecision,\n RateLimitDimension,\n RateLimitPolicy,\n RateLimitStore,\n} from \"./types\";\n\nconst DIMENSIONS: ReadonlySet<RateLimitDimension> = new Set([\n \"ip\",\n \"token\",\n \"user\",\n \"tenant\",\n \"credential\",\n \"key\",\n \"session\",\n]);\nconst DEFAULT_MAX_ENTRIES = 10_000;\n\n/** SHA-256 a dimension value so secrets never appear in keys. */\nfunction hashDimension(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\n/** Validate one policy before it can be used for enforcement. */\nfunction normalizePolicy(\n name: string,\n value: Partial<RateLimitPolicy>,\n base?: RateLimitPolicy,\n logger: Pick<Console, \"warn\"> = console,\n): RateLimitPolicy | undefined {\n const limit = value.limit ?? base?.limit;\n const windowMs = value.windowMs ?? base?.windowMs;\n const dimensions = value.dimensions ?? base?.dimensions;\n if (\n typeof limit !== \"number\" ||\n !Number.isFinite(limit) ||\n limit <= 0 ||\n typeof windowMs !== \"number\" ||\n !Number.isFinite(windowMs) ||\n windowMs <= 0 ||\n !Array.isArray(dimensions) ||\n dimensions.length === 0 ||\n dimensions.some((dimension) => !DIMENSIONS.has(dimension))\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-policy\",\n policy: name,\n }));\n return base ? { ...base, dimensions: [...base.dimensions] } : undefined;\n }\n const dimensionLimits: Partial<Record<RateLimitDimension, number>> = {\n ...(base?.dimensionLimits ?? {}),\n };\n const rawDimensionLimits = (value as { dimensionLimits?: unknown }).dimensionLimits;\n if (rawDimensionLimits !== undefined) {\n if (\n typeof rawDimensionLimits !== \"object\" ||\n rawDimensionLimits === null ||\n Array.isArray(rawDimensionLimits)\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-dimension-limits\",\n policy: name,\n }));\n } else {\n for (const [dimension, raw] of Object.entries(rawDimensionLimits)) {\n if (\n !DIMENSIONS.has(dimension as RateLimitDimension) ||\n typeof raw !== \"number\" ||\n !Number.isFinite(raw) ||\n raw <= 0\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-dimension-limit\",\n policy: name,\n dimension,\n }));\n continue;\n }\n dimensionLimits[dimension as RateLimitDimension] = Math.floor(raw);\n }\n }\n }\n return {\n limit: Math.floor(limit),\n windowMs: Math.floor(windowMs),\n dimensions: [...dimensions],\n ...(Object.keys(dimensionLimits).length > 0 ? { dimensionLimits } : {}),\n ...(value.enabled !== undefined\n ? { enabled: value.enabled }\n : base?.enabled !== undefined\n ? { enabled: base.enabled }\n : {}),\n };\n}\n\n/** Parse `AXIOM_RATE_LIMIT_POLICIES` as a partial policy map. */\nexport function parseRateLimitPoliciesEnv(\n raw: string | undefined,\n logger: Pick<Console, \"warn\"> = console,\n): Record<string, Partial<RateLimitPolicy>> {\n if (!raw || raw.trim() === \"\") return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"expected an object\");\n }\n return parsed as Record<string, Partial<RateLimitPolicy>>;\n } catch (error) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-env-policies\",\n error: error instanceof Error ? error.message : String(error),\n }));\n return {};\n }\n}\n\n/** Merge defaults, env, and host overrides into a validated policy map. */\nexport function mergeRateLimitPolicies(\n config: RateLimitConfig = {},\n logger: Pick<Console, \"warn\"> = console,\n): Record<string, RateLimitPolicy> {\n const merged: Record<string, RateLimitPolicy> = {};\n for (const [name, policy] of Object.entries(DEFAULT_RATE_LIMIT_POLICIES)) {\n merged[name] = { ...policy, dimensions: [...policy.dimensions] };\n }\n const overrides = [\n legacyOpenRateLimitPolicies(process.env),\n parseRateLimitPoliciesEnv(process.env.AXIOM_RATE_LIMIT_POLICIES, logger),\n config.policies ?? {},\n ];\n for (const layer of overrides) {\n for (const [name, value] of Object.entries(layer)) {\n const policy = normalizePolicy(name, value, merged[name], logger);\n if (policy) merged[name] = policy;\n }\n }\n return merged;\n}\n\n/** Dependencies for {@link RateLimitService}. */\nexport interface RateLimitServiceOptions {\n store: RateLimitStore;\n policies: Record<string, RateLimitPolicy>;\n enabled: boolean;\n logger?: Pick<Console, \"warn\">;\n}\n\n/**\n * Reusable fixed-window rate limiter.\n *\n * Each policy declares dimensions; a request is allowed only when every\n * supplied dimension stays under the policy limit. Missing dimension values are\n * skipped, so routes should provide at least one meaningful dimension.\n */\nexport class RateLimitService {\n private readonly warnedPolicies = new Set<string>();\n\n constructor(private readonly options: RateLimitServiceOptions) {}\n\n /**\n * Consume one request against a named policy.\n *\n * @param policyName - Policy key (for example `mcp-app-share.rpc`).\n * @param values - Dimension values for this request.\n * @returns The decision; `allowed:false` means the caller should return 429.\n */\n async consume(\n policyName: string,\n values: Partial<Record<RateLimitDimension, string | undefined>>,\n ): Promise<RateLimitDecision> {\n if (!this.options.enabled) {\n return {\n allowed: true,\n policy: policyName,\n limit: 0,\n remaining: 0,\n retryAfterSeconds: 0,\n };\n }\n const policy = this.options.policies[policyName];\n if (!policy && !this.warnedPolicies.has(policyName)) {\n this.warnedPolicies.add(policyName);\n this.options.logger?.warn(JSON.stringify({\n event: \"rate-limit:unknown-policy\",\n policy: policyName,\n }));\n }\n if (!policy || policy.enabled === false) {\n return {\n allowed: true,\n policy: policyName,\n limit: policy?.limit ?? 0,\n remaining: policy?.limit ?? 0,\n retryAfterSeconds: 0,\n };\n }\n\n let remaining = policy.limit;\n for (const dimension of policy.dimensions) {\n const raw = values[dimension];\n if (typeof raw !== \"string\" || raw === \"\") continue;\n const key = `axiom:rate-limit:${policyName}:${dimension}:${hashDimension(raw)}`;\n const result = await this.options.store.increment(key, policy.windowMs);\n if (!result) continue;\n const dimensionLimit = policy.dimensionLimits?.[dimension] ?? policy.limit;\n remaining = Math.min(remaining, dimensionLimit - result.count);\n if (result.count > dimensionLimit) {\n return {\n allowed: false,\n policy: policyName,\n limit: dimensionLimit,\n remaining: 0,\n retryAfterSeconds: Math.max(1, Math.ceil(result.ttlMs / 1000)),\n exceededDimension: dimension,\n };\n }\n }\n\n return {\n allowed: true,\n policy: policyName,\n limit: policy.limit,\n remaining: Math.max(0, remaining),\n retryAfterSeconds: 0,\n };\n }\n}\n\n/**\n * Build the gateway rate-limit service from config + environment.\n */\nexport function createRateLimitService(\n config: RateLimitConfig = {},\n logger: Pick<Console, \"warn\"> = console,\n): RateLimitService {\n const redisUrl = config.redisUrl ?? process.env.REDIS_URL?.trim();\n const redis = redisUrl\n ? new RedisRateLimitStore({\n url: redisUrl,\n onError: (error) => logger.warn(JSON.stringify({\n event: \"rate-limit:redis-unavailable\",\n error: error instanceof Error ? error.message : String(error),\n })),\n })\n : undefined;\n return new RateLimitService({\n store: new HybridRateLimitStore(redis, {\n maxEntries: config.maxInMemoryEntries ?? DEFAULT_MAX_ENTRIES,\n }),\n policies: mergeRateLimitPolicies(config, logger),\n enabled:\n config.enabled\n ?? process.env.AXIOM_RATE_LIMIT_DISABLED !== \"true\",\n logger,\n });\n}\n\nconst disabledService = new RateLimitService({\n store: new HybridRateLimitStore(undefined),\n policies: {},\n enabled: false,\n});\nlet defaultService: RateLimitService | undefined;\n\n/** Install the gateway-wide rate-limit service (called by gateway startup). */\nexport function setRateLimitService(service: RateLimitService): void {\n defaultService = service;\n}\n\n/** Reset to the disabled default; intended for tests. */\nexport function resetRateLimitService(): void {\n defaultService = undefined;\n}\n\n/**\n * Read the gateway-wide rate-limit service.\n *\n * Direct route unit tests (which do not start the gateway) get a disabled\n * service; production startup installs the configured one via\n * {@link setRateLimitService}.\n */\nexport function getRateLimitService(): RateLimitService {\n return defaultService ?? disabledService;\n}\n","import type { RateLimitPolicy } from \"./types\";\n\n/**\n * Gateway default rate-limit policies.\n *\n * Routes opt in explicitly; this table is not a blanket limit for every API.\n * Deployment overrides merge over these values (see `RateLimitConfig`).\n */\nexport const DEFAULT_RATE_LIMIT_POLICIES: Record<string, RateLimitPolicy> = {\n \"mcp-app-share.rpc\": {\n limit: 600,\n windowMs: 60_000,\n dimensions: [\"token\", \"ip\"],\n dimensionLimits: { ip: 60 },\n },\n \"mcp-app-share.unlock\": {\n limit: 5,\n windowMs: 60_000,\n dimensions: [\"token\", \"ip\"],\n },\n \"mcp-app-share.bootstrap\": {\n limit: 300,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.login\": {\n limit: 10,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"open.api\": {\n limit: 60_000,\n windowMs: 60_000,\n dimensions: [\"credential\"],\n },\n \"open.agent\": {\n limit: 2_000,\n windowMs: 60_000,\n dimensions: [\"credential\"],\n },\n \"a2a.jsonrpc\": {\n limit: 600,\n windowMs: 60_000,\n dimensions: [\"key\", \"ip\"],\n dimensionLimits: { ip: 300 },\n },\n};\n\n/**\n * Legacy Open rate-limit env as a fallback layer.\n *\n * `OPEN_RATE_LIMIT_PER_MINUTE` / `OPEN_AGENT_RATE_LIMIT_PER_MINUTE` predate the\n * gateway policy table. They sit below `AXIOM_RATE_LIMIT_POLICIES` and host\n * overrides so new configuration can supersede them.\n */\nexport function legacyOpenRateLimitPolicies(\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, Partial<RateLimitPolicy>> {\n const open = Number(env.OPEN_RATE_LIMIT_PER_MINUTE);\n const agent = Number(env.OPEN_AGENT_RATE_LIMIT_PER_MINUTE);\n return {\n ...(Number.isFinite(open) && open > 0 ? { \"open.api\": { limit: open } } : {}),\n ...(Number.isFinite(agent) && agent > 0\n ? { \"open.agent\": { limit: agent } }\n : {}),\n };\n}\n","import { createClient } from \"redis\";\n\nexport interface SharedRedis {\n readonly isOpen: boolean;\n connect(): Promise<void>;\n on(event: \"error\", listener: (error: unknown) => void): unknown;\n set(key: string, value: string, options: { NX: true; PX: number }): Promise<string | null>;\n incr(key: string): Promise<number>;\n pExpire(key: string, ms: number): Promise<boolean>;\n eval(script: string, options: { keys: string[]; arguments: string[] }): Promise<unknown>;\n}\n\nconst clientPromises = new Map<string, Promise<SharedRedis>>();\n\n/** Lazily connect a shared Redis client; defaults to `REDIS_URL`. */\nexport async function getSharedRedisClient(\n url: string | undefined = process.env.REDIS_URL?.trim(),\n): Promise<SharedRedis | undefined> {\n if (!url) return undefined;\n let clientPromise = clientPromises.get(url);\n if (!clientPromise) {\n clientPromise = (async () => {\n const client = createClient({ url }) as unknown as SharedRedis;\n client.on(\"error\", () => undefined);\n await client.connect();\n return client;\n })();\n clientPromises.set(url, clientPromise);\n }\n try {\n const client = await clientPromise;\n return client.isOpen ? client : undefined;\n } catch {\n if (clientPromises.get(url) === clientPromise) clientPromises.delete(url);\n return undefined;\n }\n}\n\n/** Atomically claim a dedup key. `undefined` means Redis is unavailable. */\nexport async function claimDedupKey(key: string, ttlMs: number): Promise<boolean | undefined> {\n try {\n const client = await getSharedRedisClient();\n if (!client) return undefined;\n const result = await client.set(key, \"1\", { NX: true, PX: ttlMs });\n return result === \"OK\";\n } catch {\n return undefined;\n }\n}\n\n/** Atomically increment a rate counter. `undefined` means Redis is unavailable. */\nexport async function incrementRateCounter(key: string, windowMs: number): Promise<number | undefined> {\n try {\n const client = await getSharedRedisClient();\n if (!client) return undefined;\n const count = await client.incr(key);\n if (count === 1) await client.pExpire(key, windowMs);\n return count;\n } catch {\n return undefined;\n }\n}\n","import { getSharedRedisClient } from \"../../router/middlewares/channelSharedState\";\nimport type { RateLimitStore } from \"./types\";\n\n/** Fixed-window counter incremented atomically in Redis. */\nconst INCREMENT_SCRIPT = `\nlocal count = redis.call(\"INCR\", KEYS[1])\nlocal ttl = redis.call(\"PTTL\", KEYS[1])\nif count == 1 or ttl < 0 then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[1])\n ttl = tonumber(ARGV[1])\nend\nreturn {count, ttl}\n`;\n\n/**\n * Process-local fixed-window counters.\n *\n * Used directly in single-process deployments and as the fallback when Redis\n * is unavailable. Entries expire lazily and the map is bounded.\n */\nexport class InMemoryRateLimitStore implements RateLimitStore {\n private readonly counters = new Map<string, { count: number; resetAt: number }>();\n\n constructor(\n private readonly options: {\n maxEntries?: number;\n now?: () => number;\n } = {},\n ) {}\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number }> {\n const now = this.options.now?.() ?? Date.now();\n const current = this.counters.get(key);\n const next =\n !current || now >= current.resetAt\n ? { count: 1, resetAt: now + windowMs }\n : { count: current.count + 1, resetAt: current.resetAt };\n this.counters.set(key, next);\n this.evict(now);\n return {\n count: next.count,\n ttlMs: Math.max(0, next.resetAt - now),\n };\n }\n\n /** Drop expired entries, then the oldest entries if still over the cap. */\n private evict(now: number): void {\n const max = this.options.maxEntries ?? 10_000;\n if (this.counters.size <= max) return;\n for (const [key, counter] of this.counters) {\n if (counter.resetAt <= now) this.counters.delete(key);\n }\n if (this.counters.size <= max) return;\n // Insertion order approximates reset order for fixed windows; avoid an\n // O(n log n) sort on a hot path under attack.\n let excess = this.counters.size - max;\n for (const key of this.counters.keys()) {\n if (excess <= 0) break;\n this.counters.delete(key);\n excess -= 1;\n }\n }\n}\n\n/** Redis-backed fixed-window counters. Returns `undefined` on any failure. */\nexport class RedisRateLimitStore implements RateLimitStore {\n constructor(\n private readonly options: {\n url?: string;\n onError?: (error: unknown) => void;\n } = {},\n ) {}\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number } | undefined> {\n try {\n const client = await getSharedRedisClient(this.options.url);\n if (!client) return undefined;\n const result = await client.eval(INCREMENT_SCRIPT, {\n keys: [key],\n arguments: [String(windowMs)],\n });\n if (!Array.isArray(result) || result.length < 2) return undefined;\n const count = Number(result[0]);\n const ttlMs = Number(result[1]);\n if (!Number.isFinite(count) || count < 1) return undefined;\n return {\n count,\n ttlMs: Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : windowMs,\n };\n } catch (error) {\n this.options.onError?.(error);\n return undefined;\n }\n }\n}\n\n/**\n * Use Redis when available, otherwise fall back to the process-local store.\n *\n * A Redis failure is treated as \"unavailable\", not \"unlimited\": the request is\n * still counted in memory.\n */\nexport class HybridRateLimitStore implements RateLimitStore {\n private readonly memory: InMemoryRateLimitStore;\n\n constructor(\n private readonly redis: RateLimitStore | undefined,\n options: { maxEntries?: number; now?: () => number } = {},\n ) {\n this.memory = new InMemoryRateLimitStore(options);\n }\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number }> {\n if (this.redis) {\n const shared = await this.redis.increment(key, windowMs);\n if (shared) return shared;\n }\n return this.memory.increment(key, windowMs);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isConnectionBacked
|
|
3
|
+
} from "./chunk-Y7OVVPVU.mjs";
|
|
4
|
+
import {
|
|
5
|
+
ConnectionNotAllowedError,
|
|
2
6
|
grantCovers,
|
|
3
7
|
openLattice
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MFWTGNQR.mjs";
|
|
5
9
|
|
|
6
10
|
// src/services/open/registerExposedPlugins.ts
|
|
7
11
|
import { PluginRegistry } from "@axiom-lattice/core";
|
|
@@ -11,6 +15,30 @@ import { z } from "zod";
|
|
|
11
15
|
function schemaOf(tool) {
|
|
12
16
|
return tool.lc_kwargs?.schema ?? tool.schema ?? z.object({}).passthrough();
|
|
13
17
|
}
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
function schemaHasConnectionKey(schema) {
|
|
22
|
+
const definition = isRecord(schema._def) ? schema._def : void 0;
|
|
23
|
+
const shape = definition?.shape;
|
|
24
|
+
const fields = typeof shape === "function" ? shape() : shape;
|
|
25
|
+
return isRecord(fields) && Object.prototype.hasOwnProperty.call(fields, "connectionKey");
|
|
26
|
+
}
|
|
27
|
+
function acceptsConnectionKey(tool) {
|
|
28
|
+
const schema = schemaOf(tool);
|
|
29
|
+
if (!isRecord(schema)) return false;
|
|
30
|
+
if (schemaHasConnectionKey(schema)) return true;
|
|
31
|
+
const definition = isRecord(schema._def) ? schema._def : void 0;
|
|
32
|
+
if (Array.isArray(definition?.options) && definition.options.length > 0) {
|
|
33
|
+
return definition.options.every(
|
|
34
|
+
(option) => isRecord(option) && schemaHasConnectionKey(option)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function asArgs(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
41
|
+
}
|
|
14
42
|
function createPluginProvider(domain, tools, options = {}) {
|
|
15
43
|
function toAction(info) {
|
|
16
44
|
return {
|
|
@@ -34,6 +62,7 @@ function createPluginProvider(domain, tools, options = {}) {
|
|
|
34
62
|
label: options.label ?? domain,
|
|
35
63
|
source: "plugin",
|
|
36
64
|
scopeKind: "tenant",
|
|
65
|
+
connectionBacked: options.connectionBacked === true,
|
|
37
66
|
actions,
|
|
38
67
|
execute: async (action, args, ctx) => {
|
|
39
68
|
const info = tools.find((t) => t.name === action);
|
|
@@ -43,16 +72,45 @@ function createPluginProvider(domain, tools, options = {}) {
|
|
|
43
72
|
isError: true
|
|
44
73
|
};
|
|
45
74
|
}
|
|
75
|
+
const connectionKeys = ctx.connectionKeys;
|
|
76
|
+
let effectiveArgs = args;
|
|
77
|
+
if (connectionKeys?.length) {
|
|
78
|
+
const argsRecord = asArgs(args);
|
|
79
|
+
const provided = argsRecord.connectionKey;
|
|
80
|
+
if (typeof provided === "string" && !connectionKeys.includes(provided)) {
|
|
81
|
+
throw new ConnectionNotAllowedError(`${domain}: ${provided}`);
|
|
82
|
+
}
|
|
83
|
+
if (acceptsConnectionKey(info.tool)) {
|
|
84
|
+
effectiveArgs = {
|
|
85
|
+
...argsRecord,
|
|
86
|
+
connectionKey: typeof provided === "string" ? provided : connectionKeys[0]
|
|
87
|
+
};
|
|
88
|
+
} else if ("connectionKey" in argsRecord) {
|
|
89
|
+
const stripped = { ...argsRecord };
|
|
90
|
+
delete stripped.connectionKey;
|
|
91
|
+
effectiveArgs = stripped;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
46
94
|
const exeConfig = {
|
|
47
95
|
configurable: {
|
|
48
96
|
runConfig: {
|
|
49
97
|
tenantId: ctx.tenantId,
|
|
50
98
|
workspaceId: ctx.workspaceId,
|
|
51
|
-
projectId: ctx.projectId
|
|
99
|
+
projectId: ctx.projectId,
|
|
100
|
+
...connectionKeys?.length ? {
|
|
101
|
+
capabilityRuntime: {
|
|
102
|
+
middleware: {
|
|
103
|
+
[domain]: {
|
|
104
|
+
enabled: true,
|
|
105
|
+
config: { connections: connectionKeys, connectAll: false }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} : {}
|
|
52
110
|
}
|
|
53
111
|
}
|
|
54
112
|
};
|
|
55
|
-
const output = await info.tool.invoke(
|
|
113
|
+
const output = await info.tool.invoke(effectiveArgs, exeConfig);
|
|
56
114
|
return { content: [{ type: "text", text: output }] };
|
|
57
115
|
}
|
|
58
116
|
};
|
|
@@ -102,7 +160,10 @@ async function registerExposedPluginTools() {
|
|
|
102
160
|
};
|
|
103
161
|
}).filter(Boolean);
|
|
104
162
|
if (matched.length === 0) continue;
|
|
105
|
-
const provider = createPluginProvider(key, matched, {
|
|
163
|
+
const provider = createPluginProvider(key, matched, {
|
|
164
|
+
label: plugin.meta.name,
|
|
165
|
+
connectionBacked: isConnectionBacked(plugin)
|
|
166
|
+
});
|
|
106
167
|
openLattice.register(provider);
|
|
107
168
|
} catch {
|
|
108
169
|
}
|
|
@@ -140,4 +201,4 @@ export {
|
|
|
140
201
|
registerExposedPluginTools,
|
|
141
202
|
collectExposedPluginUiResources
|
|
142
203
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
204
|
+
//# sourceMappingURL=chunk-BOPNXTMO.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/open/registerExposedPlugins.ts","../src/services/open/pluginExposeAdapter.ts"],"sourcesContent":["import { PluginRegistry } from \"@axiom-lattice/core\";\nimport type { OpenGrant, Plugin, PluginToolUi } from \"@axiom-lattice/protocols\";\nimport { grantCovers, openLattice } from \"./openLattice\";\nimport { createPluginProvider } from \"./pluginExposeAdapter\";\nimport { isConnectionBacked } from \"./pluginConnectionMetadata\";\nimport type { OpenUiResource } from \"./openMcpServer\";\n\n/** A single exposed-tool declaration (annotation hints optional). */\ninterface ExposedDeclaration {\n name: string;\n description: string;\n readOnly?: boolean;\n destructive?: boolean;\n}\n\n/**\n * Declaration source: meta.openExpose (preferred, standalone) or\n * meta.tools[].expose (per-tool flags).\n */\nfunction exposedDeclarations(plugin: Plugin): ExposedDeclaration[] {\n const openExpose = plugin.meta.openExpose;\n if (openExpose) {\n return openExpose.map((entry) =>\n typeof entry === \"string\"\n ? { name: entry, description: \"\" }\n : {\n name: entry.name,\n description: \"\",\n readOnly: entry.readOnly,\n destructive: entry.destructive,\n },\n );\n }\n return (plugin.meta.tools ?? []).filter((t) => t.expose);\n}\n\n/** Walk PluginRegistry and register every exposed tool as an Open provider. */\nexport async function registerExposedPluginTools(): Promise<void> {\n for (const key of PluginRegistry.list()) {\n const plugin = PluginRegistry.get(key);\n if (!plugin?.middleware) continue;\n const exposed = exposedDeclarations(plugin);\n if (exposed.length === 0) continue;\n // Tool-level UI declarations keyed by tool name (raw meta, unlike listMeta).\n const uiByTool = new Map<string, PluginToolUi>();\n for (const toolMeta of plugin.meta.tools ?? []) {\n if (toolMeta.ui) uiByTool.set(toolMeta.name, toolMeta.ui);\n }\n try {\n // Open-door semantics: resolve the tenant's connections at call time\n // (credential → tenant → tenant's own Connection Store entries).\n const middleware = await plugin.middleware({\n ...(plugin.meta.defaultConfig ?? {}),\n connectAll: true,\n });\n const toolObjects = (middleware as { tools?: Array<{ name: string; description: string; invoke: (input: unknown, config?: unknown) => Promise<string> }> }).tools ?? [];\n const matched = exposed\n .map((tm) => {\n const tool = toolObjects.find((t: { name: string }) => t.name === tm.name);\n if (!tool) return null;\n return {\n name: tm.name,\n description: tm.description,\n tool,\n readOnly: tm.readOnly,\n destructive: tm.destructive,\n ui: uiByTool.get(tm.name),\n };\n })\n .filter(Boolean) as Array<{\n name: string;\n description?: string;\n tool: { name: string; description: string; invoke: (input: unknown, config?: unknown) => Promise<string> };\n readOnly?: boolean;\n destructive?: boolean;\n ui?: PluginToolUi;\n }>;\n if (matched.length === 0) continue;\n const provider = createPluginProvider(key, matched, {\n label: plugin.meta.name,\n connectionBacked: isConnectionBacked(plugin),\n });\n openLattice.register(provider);\n } catch {\n // skip plugins whose middleware fails to instantiate\n }\n }\n}\n\n/**\n * Inline `ui://` resources declared by GLOBAL plugins (raw meta, not listMeta).\n *\n * `serializePluginMeta` is a whitelist and drops `meta.uiResources`, so the raw\n * plugin is read back from `PluginRegistry.get(type)`. A resource is included\n * only when an **exposed** tool of that domain declares it (`meta.tools[].ui`)\n * **and** the credential's grant covers that exact tool (item-level) — a domain\n * grant alone must not leak another tool's UI document, and an unexposed or\n * unauthorized plugin must not leak any. `mimeType` defaults to\n * `text/html;profile=mcp-app` (the `PluginUiResource` contract).\n *\n * Synchronous: `PluginRegistry` is synchronous. The returned `read` callback\n * serves the plugin's inline HTML lazily per `resources/read`.\n *\n * @param grants - Credential grants; an item not granted (or unknown resource)\n * is skipped.\n * @returns Map of `ui://` URI → Open session UI resource.\n */\nexport function collectExposedPluginUiResources(grants: OpenGrant[]): Map<string, OpenUiResource> {\n const resources = new Map<string, OpenUiResource>();\n for (const type of PluginRegistry.list()) {\n const plugin = PluginRegistry.get(type);\n if (!plugin?.middleware) continue;\n const exposedNames = new Set(exposedDeclarations(plugin).map((d) => d.name));\n if (exposedNames.size === 0) continue;\n // Only resources owned by an exposed, item-level-granted tool are reachable.\n const allowedUris = new Set<string>();\n for (const toolMeta of plugin.meta.tools ?? []) {\n const uri = toolMeta.ui?.resource;\n if (!uri) continue;\n if (!exposedNames.has(toolMeta.name)) continue;\n if (!grantCovers(grants, type, toolMeta.name)) continue;\n allowedUris.add(uri);\n }\n if (allowedUris.size === 0) continue;\n for (const [uri, res] of Object.entries(plugin.meta.uiResources ?? {})) {\n if (!allowedUris.has(uri)) continue;\n resources.set(uri, {\n mimeType: res.mimeType ?? \"text/html;profile=mcp-app\",\n csp: res.csp,\n read: async () => ({ html: res.html, csp: res.csp }),\n });\n }\n }\n return resources;\n}\n","/**\n * Plugin expose adapter: converts a plugin's middleware-emitted tools into an\n * OpenProvider, so `expose: true` tools surface on the Open API catalog.\n *\n * The LangChain tool emits schema via .lc_kwargs.schema (ZodType) and runs via\n * .invoke(input, exeConfig). We synthesize the exeConfig the way the Agent\n * path would — injecting run identity (tenantId/workspaceId/projectId) into\n * runConfig. Plugin tools resolve their own connections from the\n * tenant-scoped Connection Store at invocation time.\n */\n\nimport { z } from \"zod\";\nimport type { OpenExecutionContext, PluginToolUi } from \"@axiom-lattice/protocols\";\nimport { ConnectionNotAllowedError } from \"./openLattice\";\nimport type { OpenProviderDeclaration, OpenActionDeclaration } from \"./openLattice\";\n\nexport interface PluginToolLike {\n name: string;\n description: string;\n invoke: (input: unknown, exeConfig?: unknown) => Promise<string>;\n lc_kwargs?: { schema?: z.ZodTypeAny };\n schema?: z.ZodTypeAny;\n}\n\nfunction schemaOf(tool: PluginToolLike): z.ZodTypeAny {\n return tool.lc_kwargs?.schema ?? tool.schema ?? z.object({}).passthrough();\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Structural check: read a Zod object/union shape without `instanceof`. */\nfunction schemaHasConnectionKey(schema: Record<string, unknown>): boolean {\n const definition = isRecord(schema._def) ? schema._def : undefined;\n const shape = definition?.shape;\n const fields = typeof shape === \"function\" ? shape() : shape;\n return isRecord(fields) && Object.prototype.hasOwnProperty.call(fields, \"connectionKey\");\n}\n\n/** Whether the tool's schema accepts the standard `connectionKey` input. */\nfunction acceptsConnectionKey(tool: PluginToolLike): boolean {\n const schema: unknown = schemaOf(tool);\n if (!isRecord(schema)) return false;\n if (schemaHasConnectionKey(schema)) return true;\n const definition = isRecord(schema._def) ? schema._def : undefined;\n if (Array.isArray(definition?.options) && definition.options.length > 0) {\n return definition.options.every(\n (option) => isRecord(option) && schemaHasConnectionKey(option),\n );\n }\n return false;\n}\n\n/** Narrow unknown input to a plain data record, otherwise an empty record. */\nfunction asArgs(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/** Build an Open provider exposing the given plugin tools under a domain. */\nexport interface PluginExposedTool {\n name: string;\n description?: string;\n tool: PluginToolLike;\n /** MCP readOnlyHint (default false — conservative). */\n readOnly?: boolean;\n /** MCP destructiveHint (default false). */\n destructive?: boolean;\n /**\n * MCP App UI declaration from the plugin's tool meta. For global plugins\n * `resource` is already a `ui://` URI (unlike sandbox plugins, where it is a\n * file path relative to the plugin directory).\n */\n ui?: PluginToolUi;\n}\n\nexport interface PluginProviderOptions {\n label?: string;\n /** Whether the domain's tools resolve tenant connections. */\n connectionBacked?: boolean;\n}\n\nexport function createPluginProvider(\n domain: string,\n tools: PluginExposedTool[],\n options: PluginProviderOptions = {},\n): OpenProviderDeclaration {\n function toAction(info: PluginExposedTool): OpenActionDeclaration {\n return {\n name: info.name,\n description:\n info.description || info.tool.description || `Plugin tool ${info.name}`,\n schema: schemaOf(info.tool),\n annotations: {\n readOnlyHint: info.readOnly ?? false,\n destructiveHint: info.destructive ?? false,\n },\n ui: info.ui\n ? {\n resourceUri: info.ui.resource,\n csp: info.ui.csp,\n displayMode: info.ui.displayMode,\n }\n : undefined,\n };\n }\n\n const actions = tools.map(toAction);\n return {\n domain,\n label: options.label ?? domain,\n source: \"plugin\",\n scopeKind: \"tenant\",\n connectionBacked: options.connectionBacked === true,\n actions,\n execute: async (action: string, args: unknown, ctx: OpenExecutionContext) => {\n const info = tools.find((t) => t.name === action);\n if (!info) {\n return {\n content: [{ type: \"text\", text: `Unknown action: ${action}` }],\n isError: true,\n };\n }\n const connectionKeys = ctx.connectionKeys;\n // Pins are authoritative for every schema shape: reject out-of-set\n // caller keys and never forward a key the tool does not declare.\n let effectiveArgs = args;\n if (connectionKeys?.length) {\n const argsRecord = asArgs(args);\n const provided = argsRecord.connectionKey;\n if (\n typeof provided === \"string\"\n && !connectionKeys.includes(provided)\n ) {\n throw new ConnectionNotAllowedError(`${domain}: ${provided}`);\n }\n if (acceptsConnectionKey(info.tool)) {\n effectiveArgs = {\n ...argsRecord,\n connectionKey:\n typeof provided === \"string\" ? provided : connectionKeys[0],\n };\n } else if (\"connectionKey\" in argsRecord) {\n const stripped = { ...argsRecord };\n delete stripped.connectionKey;\n effectiveArgs = stripped;\n }\n }\n // Intentionally a partial policy shape for tools that read\n // runConfig.capabilityRuntime; Open does not run capabilityGuardMiddleware.\n const exeConfig = {\n configurable: {\n runConfig: {\n tenantId: ctx.tenantId,\n workspaceId: ctx.workspaceId,\n projectId: ctx.projectId,\n ...(connectionKeys?.length\n ? {\n capabilityRuntime: {\n middleware: {\n [domain]: {\n enabled: true,\n config: { connections: connectionKeys, connectAll: false },\n },\n },\n },\n }\n : {}),\n },\n },\n };\n const output = await info.tool.invoke(effectiveArgs, exeConfig);\n return { content: [{ type: \"text\", text: output }] };\n },\n };\n}"],"mappings":"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","names":[]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
grantCovers
|
|
3
|
-
} from "./chunk-HVIKCUOF.mjs";
|
|
4
1
|
import {
|
|
5
2
|
PluginToolScopeError,
|
|
6
3
|
invokePluginTool
|
|
7
4
|
} from "./chunk-XUWD4VKY.mjs";
|
|
5
|
+
import {
|
|
6
|
+
ConnectionNotAllowedError,
|
|
7
|
+
grantCovers
|
|
8
|
+
} from "./chunk-MFWTGNQR.mjs";
|
|
8
9
|
|
|
9
10
|
// src/services/open/tenantOpenPlugins.ts
|
|
10
11
|
import {
|
|
@@ -25,11 +26,14 @@ function defaultCatalog() {
|
|
|
25
26
|
readPluginFile: (tenantId, type, relPath) => getSandboxPluginCatalog().readPluginFile(tenantId, type, relPath)
|
|
26
27
|
};
|
|
27
28
|
}
|
|
29
|
+
function connectionResolutionConfig(connectionKeys) {
|
|
30
|
+
return connectionKeys && connectionKeys.length > 0 ? { connections: connectionKeys } : { connectAll: true };
|
|
31
|
+
}
|
|
28
32
|
async function defaultResolveConnections(pluginType, ctx) {
|
|
29
33
|
try {
|
|
30
34
|
return await resolveSandboxPluginConnections(
|
|
31
35
|
pluginType,
|
|
32
|
-
|
|
36
|
+
connectionResolutionConfig(ctx.connectionKeys),
|
|
33
37
|
{
|
|
34
38
|
tenantId: ctx.tenantId,
|
|
35
39
|
workspaceId: ctx.workspaceId,
|
|
@@ -122,7 +126,21 @@ async function buildTenantOpenPlugins(tenantId, grants, deps = {}) {
|
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
try {
|
|
125
|
-
const
|
|
129
|
+
const resolution = await resolveConnections(entry.pluginType, ctx);
|
|
130
|
+
const requested = ctx.connectionKeys;
|
|
131
|
+
let connection = resolution.connection;
|
|
132
|
+
let connections = resolution.connections;
|
|
133
|
+
if (requested?.length) {
|
|
134
|
+
const found = new Set((connections ?? []).map((item) => item.key));
|
|
135
|
+
const missing = requested.filter((key) => !found.has(key));
|
|
136
|
+
if (missing.length > 0) {
|
|
137
|
+
throw new ConnectionNotAllowedError(
|
|
138
|
+
`${entry.pluginType}: ${missing.join(", ")}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
connections = (connections ?? []).filter((item) => requested.includes(item.key));
|
|
142
|
+
connection = connections[0]?.config;
|
|
143
|
+
}
|
|
126
144
|
const result = await invokePluginTool(
|
|
127
145
|
{ catalog },
|
|
128
146
|
ctx.tenantId,
|
|
@@ -154,4 +172,4 @@ export {
|
|
|
154
172
|
mergeOpenCatalog,
|
|
155
173
|
buildTenantOpenPlugins
|
|
156
174
|
};
|
|
157
|
-
//# sourceMappingURL=chunk-
|
|
175
|
+
//# sourceMappingURL=chunk-JGQURU44.mjs.map
|