@axiom-lattice/gateway 4.10.1 → 4.10.2

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.
@@ -0,0 +1,42 @@
1
+ // src/services/open/openRateLimiter.ts
2
+ var OpenRateLimiter = class {
3
+ constructor(options = {}) {
4
+ this.buckets = /* @__PURE__ */ new Map();
5
+ this.defaultPerMinute = options.defaultPerMinute ?? 6e4;
6
+ this.agentPerMinute = options.agentPerMinute ?? 2e3;
7
+ }
8
+ bucketClass(domain) {
9
+ return domain === "agent" ? "agent" : "default";
10
+ }
11
+ limitFor(domain) {
12
+ return domain === "agent" ? this.agentPerMinute : this.defaultPerMinute;
13
+ }
14
+ /** Consume one token; denied calls report how long to wait. */
15
+ check(credentialId, domain) {
16
+ const limit = this.limitFor(domain);
17
+ const key = `${this.bucketClass(domain)}:${credentialId}`;
18
+ const now = Date.now();
19
+ const bucket = this.buckets.get(key) ?? { tokens: limit, updatedAt: now };
20
+ const refillPerMs = limit / 6e4;
21
+ const elapsed = now - bucket.updatedAt;
22
+ bucket.tokens = Math.min(limit, bucket.tokens + elapsed * refillPerMs);
23
+ bucket.updatedAt = now;
24
+ if (bucket.tokens >= 1) {
25
+ bucket.tokens -= 1;
26
+ this.buckets.set(key, bucket);
27
+ return { allowed: true };
28
+ }
29
+ this.buckets.set(key, bucket);
30
+ const deficit = 1 - bucket.tokens;
31
+ return { allowed: false, retryAfterMs: Math.ceil(deficit / refillPerMs) };
32
+ }
33
+ /** Test helper. */
34
+ reset() {
35
+ this.buckets.clear();
36
+ }
37
+ };
38
+
39
+ export {
40
+ OpenRateLimiter
41
+ };
42
+ //# sourceMappingURL=chunk-GXZFO3VU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/open/openRateLimiter.ts"],"sourcesContent":["/**\n * In-memory per-credential rate limiter (token bucket). The agent domain gets\n * a stricter bucket because each call consumes LLM budget.\n *\n * Process-local by design (V1 single-gateway); a distributed limiter is V2.\n */\n\nexport interface RateLimitDecision {\n allowed: boolean;\n retryAfterMs?: number;\n}\n\ninterface Bucket {\n tokens: number;\n updatedAt: number;\n}\n\nexport interface OpenRateLimiterOptions {\n /** Default requests per minute per credential. */\n defaultPerMinute?: number;\n /** Stricter limit for the agent domain. */\n agentPerMinute?: number;\n}\n\nexport class OpenRateLimiter {\n private readonly buckets = new Map<string, Bucket>();\n private readonly defaultPerMinute: number;\n private readonly agentPerMinute: number;\n\n constructor(options: OpenRateLimiterOptions = {}) {\n this.defaultPerMinute = options.defaultPerMinute ?? 60000;\n this.agentPerMinute = options.agentPerMinute ?? 2000;\n }\n\n private bucketClass(domain: string): string {\n return domain === \"agent\" ? \"agent\" : \"default\";\n }\n\n private limitFor(domain: string): number {\n return domain === \"agent\" ? this.agentPerMinute : this.defaultPerMinute;\n }\n\n /** Consume one token; denied calls report how long to wait. */\n check(credentialId: string, domain: string): RateLimitDecision {\n const limit = this.limitFor(domain);\n const key = `${this.bucketClass(domain)}:${credentialId}`;\n const now = Date.now();\n const bucket = this.buckets.get(key) ?? { tokens: limit, updatedAt: now };\n\n const refillPerMs = limit / 60_000;\n const elapsed = now - bucket.updatedAt;\n bucket.tokens = Math.min(limit, bucket.tokens + elapsed * refillPerMs);\n bucket.updatedAt = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n this.buckets.set(key, bucket);\n return { allowed: true };\n }\n\n this.buckets.set(key, bucket);\n const deficit = 1 - bucket.tokens;\n return { allowed: false, retryAfterMs: Math.ceil(deficit / refillPerMs) };\n }\n\n /** Test helper. */\n reset(): void {\n this.buckets.clear();\n }\n}\n"],"mappings":";AAwBO,IAAM,kBAAN,MAAsB;AAAA,EAK3B,YAAY,UAAkC,CAAC,GAAG;AAJlD,SAAiB,UAAU,oBAAI,IAAoB;AAKjD,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,iBAAiB,QAAQ,kBAAkB;AAAA,EAClD;AAAA,EAEQ,YAAY,QAAwB;AAC1C,WAAO,WAAW,UAAU,UAAU;AAAA,EACxC;AAAA,EAEQ,SAAS,QAAwB;AACvC,WAAO,WAAW,UAAU,KAAK,iBAAiB,KAAK;AAAA,EACzD;AAAA;AAAA,EAGA,MAAM,cAAsB,QAAmC;AAC7D,UAAM,QAAQ,KAAK,SAAS,MAAM;AAClC,UAAM,MAAM,GAAG,KAAK,YAAY,MAAM,CAAC,IAAI,YAAY;AACvD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,QAAQ,OAAO,WAAW,IAAI;AAExE,UAAM,cAAc,QAAQ;AAC5B,UAAM,UAAU,MAAM,OAAO;AAC7B,WAAO,SAAS,KAAK,IAAI,OAAO,OAAO,SAAS,UAAU,WAAW;AACrE,WAAO,YAAY;AAEnB,QAAI,OAAO,UAAU,GAAG;AACtB,aAAO,UAAU;AACjB,WAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,SAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,UAAM,UAAU,IAAI,OAAO;AAC3B,WAAO,EAAE,SAAS,OAAO,cAAc,KAAK,KAAK,UAAU,WAAW,EAAE;AAAA,EAC1E;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;","names":[]}
package/dist/index.d.mts CHANGED
@@ -175,6 +175,20 @@ declare class MessageRouter {
175
175
  * thin HTTP shell.
176
176
  */
177
177
 
178
+ /**
179
+ * A model the deployment registered for execution, as the registry holds it.
180
+ *
181
+ * The config travels with the key because the catalog needs it: a bare key is
182
+ * not something to offer a user. `GET /api/models` used to call
183
+ * `lattice.client.getConfig()` for exactly this — the deployment's display
184
+ * name, and the upstream model it names. Handing back keys alone left catalog
185
+ * entries with nothing to render, so the deployment's `default` alias showed up
186
+ * as a row literally labelled `default`.
187
+ */
188
+ interface RegisteredModel {
189
+ modelKey: string;
190
+ config: LLMConfig;
191
+ }
178
192
  /** Narrow persistence and registry dependencies of the service. */
179
193
  interface ModelProviderServiceDeps {
180
194
  providerStore: ModelProviderStore;
@@ -183,7 +197,7 @@ interface ModelProviderServiceDeps {
183
197
  register(tenantId: string, modelKey: string, config: LLMConfig): void;
184
198
  has(tenantId: string, modelKey: string): boolean;
185
199
  remove(tenantId: string, modelKey: string): boolean;
186
- listRegistered(tenantId: string): string[];
200
+ listRegistered(tenantId: string): RegisteredModel[];
187
201
  /**
188
202
  * Drop the registry's cached "this tenant has no default model" answer.
189
203
  *
package/dist/index.d.ts CHANGED
@@ -175,6 +175,20 @@ declare class MessageRouter {
175
175
  * thin HTTP shell.
176
176
  */
177
177
 
178
+ /**
179
+ * A model the deployment registered for execution, as the registry holds it.
180
+ *
181
+ * The config travels with the key because the catalog needs it: a bare key is
182
+ * not something to offer a user. `GET /api/models` used to call
183
+ * `lattice.client.getConfig()` for exactly this — the deployment's display
184
+ * name, and the upstream model it names. Handing back keys alone left catalog
185
+ * entries with nothing to render, so the deployment's `default` alias showed up
186
+ * as a row literally labelled `default`.
187
+ */
188
+ interface RegisteredModel {
189
+ modelKey: string;
190
+ config: LLMConfig;
191
+ }
178
192
  /** Narrow persistence and registry dependencies of the service. */
179
193
  interface ModelProviderServiceDeps {
180
194
  providerStore: ModelProviderStore;
@@ -183,7 +197,7 @@ interface ModelProviderServiceDeps {
183
197
  register(tenantId: string, modelKey: string, config: LLMConfig): void;
184
198
  has(tenantId: string, modelKey: string): boolean;
185
199
  remove(tenantId: string, modelKey: string): boolean;
186
- listRegistered(tenantId: string): string[];
200
+ listRegistered(tenantId: string): RegisteredModel[];
187
201
  /**
188
202
  * Drop the registry's cached "this tenant has no default model" answer.
189
203
  *
package/dist/index.js CHANGED
@@ -3540,6 +3540,10 @@ var init_openAuditService = __esm({
3540
3540
  });
3541
3541
 
3542
3542
  // src/services/open/openRateLimiter.ts
3543
+ var openRateLimiter_exports = {};
3544
+ __export(openRateLimiter_exports, {
3545
+ OpenRateLimiter: () => OpenRateLimiter
3546
+ });
3543
3547
  var OpenRateLimiter;
3544
3548
  var init_openRateLimiter = __esm({
3545
3549
  "src/services/open/openRateLimiter.ts"() {
@@ -3547,8 +3551,8 @@ var init_openRateLimiter = __esm({
3547
3551
  OpenRateLimiter = class {
3548
3552
  constructor(options = {}) {
3549
3553
  this.buckets = /* @__PURE__ */ new Map();
3550
- this.defaultPerMinute = options.defaultPerMinute ?? 60;
3551
- this.agentPerMinute = options.agentPerMinute ?? 10;
3554
+ this.defaultPerMinute = options.defaultPerMinute ?? 6e4;
3555
+ this.agentPerMinute = options.agentPerMinute ?? 2e3;
3552
3556
  }
3553
3557
  bucketClass(domain) {
3554
3558
  return domain === "agent" ? "agent" : "default";
@@ -21846,11 +21850,16 @@ var ModelProviderService = class {
21846
21850
  )
21847
21851
  }));
21848
21852
  if (models.length === 0) {
21849
- for (const modelKey2 of this.deps.registry.listRegistered(
21853
+ for (const { modelKey: modelKey2, config } of this.deps.registry.listRegistered(
21850
21854
  PHASE1_REGISTRY_TENANT
21851
21855
  )) {
21852
21856
  models.push({
21853
21857
  modelKey: modelKey2,
21858
+ // Carry the registered config's name, the way `GET /api/models` did.
21859
+ // Without it these rows have nothing to render but the registry key —
21860
+ // which is how the deployment's `default` alias came to be offered as
21861
+ // a model literally called "default".
21862
+ displayName: config.displayName || config.model || modelKey2,
21854
21863
  providerId: "",
21855
21864
  providerName: "registered",
21856
21865
  source: "registered",
@@ -21914,7 +21923,7 @@ var ModelProviderService = class {
21914
21923
  const provider = await this.deps.providerStore.getProviderById(tenantId, providerId2);
21915
21924
  if (!provider) {
21916
21925
  const prefix = `${providerId2}/`;
21917
- for (const modelKey2 of this.deps.registry.listRegistered(tenantId)) {
21926
+ for (const { modelKey: modelKey2 } of this.deps.registry.listRegistered(tenantId)) {
21918
21927
  if (modelKey2.startsWith(prefix)) {
21919
21928
  this.deps.registry.remove(tenantId, modelKey2);
21920
21929
  }
@@ -21923,7 +21932,7 @@ var ModelProviderService = class {
21923
21932
  }
21924
21933
  const models = await this.deps.providerStore.listModels(tenantId, providerId2);
21925
21934
  const current = new Set(models.map((model) => (0, import_protocols16.toModelKey)(providerId2, model.modelId)));
21926
- for (const modelKey2 of this.deps.registry.listRegistered(tenantId)) {
21935
+ for (const { modelKey: modelKey2 } of this.deps.registry.listRegistered(tenantId)) {
21927
21936
  if (modelKey2.startsWith(`${providerId2}/`) && !current.has(modelKey2)) {
21928
21937
  this.deps.registry.remove(tenantId, modelKey2);
21929
21938
  }
@@ -28909,7 +28918,15 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
28909
28918
  register: (tenantId, modelKey2, config) => import_core57.modelLatticeManager.registerLatticeWithTenant(tenantId, modelKey2, config),
28910
28919
  has: (tenantId, modelKey2) => import_core57.modelLatticeManager.hasLatticeWithTenant(tenantId, modelKey2),
28911
28920
  remove: (tenantId, modelKey2) => import_core57.modelLatticeManager.removeLatticeWithTenant(tenantId, modelKey2),
28912
- listRegistered: (tenantId) => import_core57.modelLatticeManager.getLatticeKeysByTenant(tenantId),
28921
+ listRegistered: (tenantId) => (
28922
+ // The config comes along so the catalog can name these rows: a bare
28923
+ // registry key is not something to offer a user. Same call the legacy
28924
+ // `GET /api/models` made.
28925
+ import_core57.modelLatticeManager.getAllLatticesByTenant(tenantId).map((lattice) => ({
28926
+ modelKey: lattice.key,
28927
+ config: lattice.client.getConfig()
28928
+ }))
28929
+ ),
28913
28930
  forgetTenantDefaultMiss: (tenantId) => import_core57.modelLatticeManager.forgetTenantDefaultMiss(tenantId)
28914
28931
  },
28915
28932
  onChanged: (tenantId, providerId2) => broadcastModelProviderChanged(tenantId, providerId2)
@@ -33628,6 +33645,7 @@ var start = async (config) => {
33628
33645
  const { registerOpenAuditRoutes: registerOpenAuditRoutes2 } = await Promise.resolve().then(() => (init_open_audit(), open_audit_exports));
33629
33646
  const { registerOpenExposureRoutes: registerOpenExposureRoutes2 } = await Promise.resolve().then(() => (init_open_exposure(), open_exposure_exports));
33630
33647
  const { openLattice: openLattice2 } = await Promise.resolve().then(() => (init_openLattice(), openLattice_exports));
33648
+ const { OpenRateLimiter: OpenRateLimiter2 } = await Promise.resolve().then(() => (init_openRateLimiter(), openRateLimiter_exports));
33631
33649
  const { createKbProvider: createKbProvider2 } = await Promise.resolve().then(() => (init_kbProvider(), kbProvider_exports));
33632
33650
  const { createOpenSandboxProvider: createOpenSandboxProvider2 } = await Promise.resolve().then(() => (init_sandboxProvider(), sandboxProvider_exports));
33633
33651
  const { createAgentProvider: createAgentProvider2 } = await Promise.resolve().then(() => (init_agentProvider(), agentProvider_exports));
@@ -33647,7 +33665,18 @@ var start = async (config) => {
33647
33665
  })
33648
33666
  );
33649
33667
  await registerExposedPluginTools2();
33650
- registerOpenRoutes2(app);
33668
+ const openRateLimitPerMinute = Number(
33669
+ process.env.OPEN_RATE_LIMIT_PER_MINUTE
33670
+ );
33671
+ const openAgentRateLimitPerMinute = Number(
33672
+ process.env.OPEN_AGENT_RATE_LIMIT_PER_MINUTE
33673
+ );
33674
+ registerOpenRoutes2(app, {
33675
+ rateLimiter: new OpenRateLimiter2({
33676
+ ...Number.isFinite(openRateLimitPerMinute) && openRateLimitPerMinute > 0 ? { defaultPerMinute: openRateLimitPerMinute } : {},
33677
+ ...Number.isFinite(openAgentRateLimitPerMinute) && openAgentRateLimitPerMinute > 0 ? { agentPerMinute: openAgentRateLimitPerMinute } : {}
33678
+ })
33679
+ });
33651
33680
  registerOpenSessionRoutes2(app);
33652
33681
  registerOpenCatalogRoutes2(app);
33653
33682
  registerOpenFilesRoutes2(app);