@kb-labs/core-tenant 2.82.0 → 2.87.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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StateBroker } from '@kb-labs/core-state-broker';
1
+ import { ICache } from '@kb-labs/core-platform';
2
2
 
3
3
  /**
4
4
  * @module @kb-labs/tenant/types
@@ -22,6 +22,8 @@ interface TenantQuotas {
22
22
  maxStorageMB: number;
23
23
  /** Plugin executions per day (-1 = unlimited) */
24
24
  pluginExecutionsPerDay: number;
25
+ /** LLM tokens per day across all requests (-1 = unlimited) */
26
+ tokensPerDay: number;
25
27
  }
26
28
  /**
27
29
  * Tenant configuration
@@ -61,13 +63,10 @@ declare function getQuotasForTier(tier: TenantTier): TenantQuotas;
61
63
 
62
64
  /**
63
65
  * @module @kb-labs/tenant/rate-limiter
64
- * Tenant rate limiter using existing State Broker infrastructure
66
+ * Tenant rate limiter backed by platform.cache (ICache).
65
67
  *
66
- * Benefits:
67
- * - Zero new dependencies
68
- * - TTL cleanup already implemented (30s interval)
69
- * - Consistent with state management patterns
70
- * - Same backend as plugin state (in-memory or HTTP daemon)
68
+ * Uses the same cache adapter as all other platform components —
69
+ * no extra dependencies, swappable backend (memory / Redis / etc.).
71
70
  */
72
71
 
73
72
  /**
@@ -83,64 +82,69 @@ interface RateLimitResult {
83
82
  /** Limit for current window */
84
83
  limit: number;
85
84
  }
85
+ /**
86
+ * Token consumption result
87
+ */
88
+ interface TokenLimitResult {
89
+ /** Whether token budget allows the request */
90
+ allowed: boolean;
91
+ /** Tokens consumed so far today */
92
+ consumed: number;
93
+ /** Daily token limit (-1 = unlimited) */
94
+ limit: number;
95
+ /** Timestamp when the daily window resets (start of next UTC day, Unix ms) */
96
+ resetAt: number;
97
+ }
86
98
  /**
87
99
  * Rate limit resource types
88
100
  */
89
101
  type RateLimitResource = 'requests' | 'workflows' | 'plugins';
90
102
  /**
91
- * Tenant rate limiter
92
- * Uses State Broker for distributed rate limiting with TTL
103
+ * Tenant rate limiter backed by platform.cache.
104
+ *
105
+ * All counters use cache TTL for cleanup — no background timers needed.
93
106
  */
94
107
  declare class TenantRateLimiter {
95
- private broker;
108
+ private cache;
96
109
  private quotas;
97
- constructor(broker: StateBroker, quotas?: Map<string, TenantQuotas>);
110
+ constructor(cache: ICache, quotas?: Map<string, TenantQuotas>);
98
111
  /**
99
- * Check rate limit for a tenant
112
+ * Check (and increment) the per-minute rate limit for a tenant.
100
113
  *
101
114
  * @param tenantId - Tenant identifier
102
115
  * @param resource - Resource type to rate limit
103
116
  * @returns Rate limit check result
104
- *
105
- * @example
106
- * const result = await limiter.checkLimit('acme', 'requests');
107
- * if (!result.allowed) {
108
- * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);
109
- * }
110
117
  */
111
118
  checkLimit(tenantId: string, resource: RateLimitResource): Promise<RateLimitResult>;
112
119
  /**
113
- * Set quotas for a tenant
120
+ * Record consumed tokens and check whether the daily token budget is exceeded.
114
121
  *
115
- * @param tenantId - Tenant identifier
116
- * @param quotas - Tenant quotas
117
- */
118
- setQuotas(tenantId: string, quotas: TenantQuotas): void;
119
- /**
120
- * Set quotas for a tenant tier
122
+ * Call this after a successful LLM response when the token count is known.
123
+ * Returns allowed=false when the budget was already exceeded BEFORE this call
124
+ * (i.e. the previous total was already >= limit). The current call's tokens
125
+ * are still recorded so the counter stays accurate.
121
126
  *
122
- * @param tenantId - Tenant identifier
123
- * @param tier - Tenant tier
124
- */
125
- setTier(tenantId: string, tier: TenantTier): void;
126
- /**
127
- * Get current window identifier (minute precision)
128
- * @returns ISO timestamp truncated to minutes
127
+ * @param tenantId - Tenant identifier
128
+ * @param tokens - Number of tokens consumed in this request (prompt + completion)
129
129
  */
130
- private getWindow;
130
+ trackTokens(tenantId: string, tokens: number): Promise<TokenLimitResult>;
131
131
  /**
132
- * Get limit for resource type
133
- *
134
- * @param quota - Tenant quotas
135
- * @param resource - Resource type
136
- * @returns Limit value
132
+ * Read current token consumption without incrementing.
137
133
  */
134
+ getTokenUsage(tenantId: string): Promise<TokenLimitResult>;
135
+ /** Set quotas for a specific tenant (overrides tier default). */
136
+ setQuotas(tenantId: string, quotas: TenantQuotas): void;
137
+ /** Set quotas for a tenant by tier. */
138
+ setTier(tenantId: string, tier: TenantTier): void;
139
+ /** Current minute window identifier: YYYY-MM-DDTHH:MM */
140
+ private getMinuteWindow;
141
+ /** Unix ms when the current minute window ends. */
142
+ private getMinuteResetTime;
143
+ /** Current day window identifier: YYYY-MM-DD (UTC) */
144
+ private getDayWindow;
145
+ /** Unix ms for start of next UTC day. */
146
+ private getDayResetTime;
138
147
  private getLimit;
139
- /**
140
- * Get reset time for current window
141
- * @returns Unix timestamp in milliseconds
142
- */
143
- private getResetTime;
144
148
  }
145
149
 
146
- export { DEFAULT_QUOTAS, type RateLimitResource, type RateLimitResult, type TenantConfig, type TenantQuotas, TenantRateLimiter, type TenantTier, getDefaultTenantId, getDefaultTenantTier, getQuotasForTier };
150
+ export { DEFAULT_QUOTAS, type RateLimitResource, type RateLimitResult, type TenantConfig, type TenantQuotas, TenantRateLimiter, type TenantTier, type TokenLimitResult, getDefaultTenantId, getDefaultTenantTier, getQuotasForTier };
package/dist/index.js CHANGED
@@ -5,23 +5,24 @@ var DEFAULT_QUOTAS = {
5
5
  requestsPerDay: 1e3,
6
6
  maxConcurrentWorkflows: 1,
7
7
  maxStorageMB: 10,
8
- pluginExecutionsPerDay: 100
8
+ pluginExecutionsPerDay: 100,
9
+ tokensPerDay: 1e5
9
10
  },
10
11
  pro: {
11
12
  requestsPerMinute: 100,
12
13
  requestsPerDay: 1e5,
13
14
  maxConcurrentWorkflows: 10,
14
15
  maxStorageMB: 1e3,
15
- pluginExecutionsPerDay: 1e4
16
+ pluginExecutionsPerDay: 1e4,
17
+ tokensPerDay: 1e7
16
18
  },
17
19
  enterprise: {
18
20
  requestsPerMinute: 1e3,
19
21
  requestsPerDay: -1,
20
- // unlimited
21
22
  maxConcurrentWorkflows: 100,
22
23
  maxStorageMB: 1e4,
23
- pluginExecutionsPerDay: -1
24
- // unlimited
24
+ pluginExecutionsPerDay: -1,
25
+ tokensPerDay: -1
25
26
  }
26
27
  };
27
28
  function getDefaultTenantId() {
@@ -40,78 +41,119 @@ function getQuotasForTier(tier) {
40
41
 
41
42
  // src/rate-limiter.ts
42
43
  var TenantRateLimiter = class {
43
- constructor(broker, quotas = /* @__PURE__ */ new Map()) {
44
- this.broker = broker;
44
+ constructor(cache, quotas = /* @__PURE__ */ new Map()) {
45
+ this.cache = cache;
45
46
  this.quotas = quotas;
46
47
  }
47
- broker;
48
+ cache;
48
49
  quotas;
49
50
  /**
50
- * Check rate limit for a tenant
51
+ * Check (and increment) the per-minute rate limit for a tenant.
51
52
  *
52
53
  * @param tenantId - Tenant identifier
53
54
  * @param resource - Resource type to rate limit
54
55
  * @returns Rate limit check result
55
- *
56
- * @example
57
- * const result = await limiter.checkLimit('acme', 'requests');
58
- * if (!result.allowed) {
59
- * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);
60
- * }
61
56
  */
62
57
  async checkLimit(tenantId, resource) {
63
58
  const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;
64
- const key = `ratelimit:tenant:${tenantId}:${resource}:${this.getWindow()}`;
65
- const current = await this.broker.get(key) ?? 0;
66
59
  const limit = this.getLimit(quota, resource);
60
+ const window = this.getMinuteWindow();
61
+ const key = `ratelimit:tenant:${tenantId}:${resource}:${window}`;
62
+ const ttl = 6e4;
63
+ const current = await this.cache.get(key) ?? 0;
67
64
  if (current >= limit) {
68
65
  return {
69
66
  allowed: false,
70
67
  remaining: 0,
71
- resetAt: this.getResetTime(),
68
+ resetAt: this.getMinuteResetTime(),
72
69
  limit
73
70
  };
74
71
  }
75
- await this.broker.set(key, current + 1, 60 * 1e3);
72
+ await this.cache.set(key, current + 1, ttl);
76
73
  return {
77
74
  allowed: true,
78
75
  remaining: limit - current - 1,
79
- resetAt: this.getResetTime(),
76
+ resetAt: this.getMinuteResetTime(),
80
77
  limit
81
78
  };
82
79
  }
83
80
  /**
84
- * Set quotas for a tenant
81
+ * Record consumed tokens and check whether the daily token budget is exceeded.
85
82
  *
86
- * @param tenantId - Tenant identifier
87
- * @param quotas - Tenant quotas
83
+ * Call this after a successful LLM response when the token count is known.
84
+ * Returns allowed=false when the budget was already exceeded BEFORE this call
85
+ * (i.e. the previous total was already >= limit). The current call's tokens
86
+ * are still recorded so the counter stays accurate.
87
+ *
88
+ * @param tenantId - Tenant identifier
89
+ * @param tokens - Number of tokens consumed in this request (prompt + completion)
88
90
  */
89
- setQuotas(tenantId, quotas) {
90
- this.quotas.set(tenantId, quotas);
91
+ async trackTokens(tenantId, tokens) {
92
+ const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;
93
+ const limit = quota.tokensPerDay;
94
+ const day = this.getDayWindow();
95
+ const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;
96
+ const ttl = 25 * 60 * 60 * 1e3;
97
+ const consumed = await this.cache.get(key) ?? 0;
98
+ const newTotal = consumed + tokens;
99
+ await this.cache.set(key, newTotal, ttl);
100
+ if (limit === -1) {
101
+ return { allowed: true, consumed: newTotal, limit, resetAt: this.getDayResetTime() };
102
+ }
103
+ return {
104
+ allowed: consumed < limit,
105
+ // allowed if budget wasn't exhausted before this call
106
+ consumed: newTotal,
107
+ limit,
108
+ resetAt: this.getDayResetTime()
109
+ };
91
110
  }
92
111
  /**
93
- * Set quotas for a tenant tier
94
- *
95
- * @param tenantId - Tenant identifier
96
- * @param tier - Tenant tier
112
+ * Read current token consumption without incrementing.
97
113
  */
114
+ async getTokenUsage(tenantId) {
115
+ const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;
116
+ const limit = quota.tokensPerDay;
117
+ const day = this.getDayWindow();
118
+ const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;
119
+ const consumed = await this.cache.get(key) ?? 0;
120
+ return {
121
+ allowed: limit === -1 || consumed < limit,
122
+ consumed,
123
+ limit,
124
+ resetAt: this.getDayResetTime()
125
+ };
126
+ }
127
+ /** Set quotas for a specific tenant (overrides tier default). */
128
+ setQuotas(tenantId, quotas) {
129
+ this.quotas.set(tenantId, quotas);
130
+ }
131
+ /** Set quotas for a tenant by tier. */
98
132
  setTier(tenantId, tier) {
99
133
  this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });
100
134
  }
101
- /**
102
- * Get current window identifier (minute precision)
103
- * @returns ISO timestamp truncated to minutes
104
- */
105
- getWindow() {
135
+ // ── private helpers ────────────────────────────────────────────────────────
136
+ /** Current minute window identifier: YYYY-MM-DDTHH:MM */
137
+ getMinuteWindow() {
106
138
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 16);
107
139
  }
108
- /**
109
- * Get limit for resource type
110
- *
111
- * @param quota - Tenant quotas
112
- * @param resource - Resource type
113
- * @returns Limit value
114
- */
140
+ /** Unix ms when the current minute window ends. */
141
+ getMinuteResetTime() {
142
+ const now = /* @__PURE__ */ new Date();
143
+ now.setSeconds(0, 0);
144
+ now.setMinutes(now.getMinutes() + 1);
145
+ return now.getTime();
146
+ }
147
+ /** Current day window identifier: YYYY-MM-DD (UTC) */
148
+ getDayWindow() {
149
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
150
+ }
151
+ /** Unix ms for start of next UTC day. */
152
+ getDayResetTime() {
153
+ const d = /* @__PURE__ */ new Date();
154
+ d.setUTCHours(24, 0, 0, 0);
155
+ return d.getTime();
156
+ }
115
157
  getLimit(quota, resource) {
116
158
  switch (resource) {
117
159
  case "requests":
@@ -124,16 +166,6 @@ var TenantRateLimiter = class {
124
166
  return 100;
125
167
  }
126
168
  }
127
- /**
128
- * Get reset time for current window
129
- * @returns Unix timestamp in milliseconds
130
- */
131
- getResetTime() {
132
- const now = /* @__PURE__ */ new Date();
133
- now.setSeconds(0, 0);
134
- now.setMinutes(now.getMinutes() + 1);
135
- return now.getTime();
136
- }
137
169
  };
138
170
 
139
171
  export { DEFAULT_QUOTAS, TenantRateLimiter, getDefaultTenantId, getDefaultTenantTier, getQuotasForTier };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/tenant/types\n * Multi-tenancy types and quota definitions\n */\n\n/**\n * Tenant tier levels\n */\nexport type TenantTier = 'free' | 'pro' | 'enterprise';\n\n/**\n * Tenant resource quotas\n */\nexport interface TenantQuotas {\n /** Requests per minute limit */\n requestsPerMinute: number;\n /** Requests per day limit (-1 = unlimited) */\n requestsPerDay: number;\n /** Maximum concurrent workflows */\n maxConcurrentWorkflows: number;\n /** Maximum storage in MB */\n maxStorageMB: number;\n /** Plugin executions per day (-1 = unlimited) */\n pluginExecutionsPerDay: number;\n}\n\n/**\n * Tenant configuration\n */\nexport interface TenantConfig {\n /** Unique tenant identifier */\n id: string;\n /** Tenant tier */\n tier: TenantTier;\n /** Resource quotas */\n quotas: TenantQuotas;\n /** Creation timestamp */\n createdAt: string;\n /** Last update timestamp */\n updatedAt: string;\n}\n\n/**\n * Default quotas by tier\n */\nexport const DEFAULT_QUOTAS: Record<TenantTier, TenantQuotas> = {\n free: {\n requestsPerMinute: 10,\n requestsPerDay: 1000,\n maxConcurrentWorkflows: 1,\n maxStorageMB: 10,\n pluginExecutionsPerDay: 100,\n },\n pro: {\n requestsPerMinute: 100,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1, // unlimited\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1, // unlimited\n },\n};\n\n/**\n * Get default tenant ID from environment\n * @returns Tenant ID (defaults to 'default' for single-tenant deployments)\n */\nexport function getDefaultTenantId(): string {\n return process.env.KB_TENANT_ID || 'default';\n}\n\n/**\n * Get default tenant tier from environment\n * @returns Tenant tier (defaults to 'free')\n */\nexport function getDefaultTenantTier(): TenantTier {\n const tier = process.env.KB_TENANT_DEFAULT_TIER?.toLowerCase();\n if (tier === 'pro' || tier === 'enterprise') {\n return tier;\n }\n return 'free';\n}\n\n/**\n * Get tenant quotas for a tier\n * @param tier - Tenant tier\n * @returns Quotas for the tier\n */\nexport function getQuotasForTier(tier: TenantTier): TenantQuotas {\n return { ...DEFAULT_QUOTAS[tier] };\n}\n","/**\n * @module @kb-labs/tenant/rate-limiter\n * Tenant rate limiter using existing State Broker infrastructure\n *\n * Benefits:\n * - Zero new dependencies\n * - TTL cleanup already implemented (30s interval)\n * - Consistent with state management patterns\n * - Same backend as plugin state (in-memory or HTTP daemon)\n */\n\nimport type { StateBroker } from '@kb-labs/core-state-broker';\nimport type { TenantQuotas, TenantTier } from './types';\nimport { DEFAULT_QUOTAS } from './types';\n\n/**\n * Rate limit check result\n */\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Timestamp when the limit resets (Unix ms) */\n resetAt: number;\n /** Limit for current window */\n limit: number;\n}\n\n/**\n * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter\n * Uses State Broker for distributed rate limiting with TTL\n */\nexport class TenantRateLimiter {\n constructor(\n private broker: StateBroker,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check rate limit for a tenant\n *\n * @param tenantId - Tenant identifier\n * @param resource - Resource type to rate limit\n * @returns Rate limit check result\n *\n * @example\n * const result = await limiter.checkLimit('acme', 'requests');\n * if (!result.allowed) {\n * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);\n * }\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n\n // Key pattern: ratelimit:tenant:default:requests:2025-01-15T10:30\n // Follows existing namespace:key pattern from State Broker\n const key = `ratelimit:tenant:${tenantId}:${resource}:${this.getWindow()}`;\n\n const current = (await this.broker.get<number>(key)) ?? 0;\n const limit = this.getLimit(quota, resource);\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n // Increment counter with 60s TTL\n // State Broker cleanup (30s interval) will remove expired entries\n await this.broker.set(key, current + 1, 60 * 1000);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n /**\n * Set quotas for a tenant\n *\n * @param tenantId - Tenant identifier\n * @param quotas - Tenant quotas\n */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /**\n * Set quotas for a tenant tier\n *\n * @param tenantId - Tenant identifier\n * @param tier - Tenant tier\n */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n /**\n * Get current window identifier (minute precision)\n * @returns ISO timestamp truncated to minutes\n */\n private getWindow(): string {\n return new Date().toISOString().slice(0, 16); // YYYY-MM-DDTHH:MM\n }\n\n /**\n * Get limit for resource type\n *\n * @param quota - Tenant quotas\n * @param resource - Resource type\n * @returns Limit value\n */\n private getLimit(quota: TenantQuotas, resource: RateLimitResource): number {\n switch (resource) {\n case 'requests':\n return quota.requestsPerMinute;\n case 'workflows':\n return quota.maxConcurrentWorkflows;\n case 'plugins':\n // Convert daily limit to per-minute (1440 minutes in a day)\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100; // default fallback\n }\n }\n\n /**\n * Get reset time for current window\n * @returns Unix timestamp in milliseconds\n */\n private getResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/tenant/types\n * Multi-tenancy types and quota definitions\n */\n\n/**\n * Tenant tier levels\n */\nexport type TenantTier = 'free' | 'pro' | 'enterprise';\n\n/**\n * Tenant resource quotas\n */\nexport interface TenantQuotas {\n /** Requests per minute limit */\n requestsPerMinute: number;\n /** Requests per day limit (-1 = unlimited) */\n requestsPerDay: number;\n /** Maximum concurrent workflows */\n maxConcurrentWorkflows: number;\n /** Maximum storage in MB */\n maxStorageMB: number;\n /** Plugin executions per day (-1 = unlimited) */\n pluginExecutionsPerDay: number;\n /** LLM tokens per day across all requests (-1 = unlimited) */\n tokensPerDay: number;\n}\n\n/**\n * Tenant configuration\n */\nexport interface TenantConfig {\n /** Unique tenant identifier */\n id: string;\n /** Tenant tier */\n tier: TenantTier;\n /** Resource quotas */\n quotas: TenantQuotas;\n /** Creation timestamp */\n createdAt: string;\n /** Last update timestamp */\n updatedAt: string;\n}\n\n/**\n * Default quotas by tier\n */\nexport const DEFAULT_QUOTAS: Record<TenantTier, TenantQuotas> = {\n free: {\n requestsPerMinute: 10,\n requestsPerDay: 1000,\n maxConcurrentWorkflows: 1,\n maxStorageMB: 10,\n pluginExecutionsPerDay: 100,\n tokensPerDay: 100_000,\n },\n pro: {\n requestsPerMinute: 100,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n tokensPerDay: 10_000_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1,\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1,\n tokensPerDay: -1,\n },\n};\n\n/**\n * Get default tenant ID from environment\n * @returns Tenant ID (defaults to 'default' for single-tenant deployments)\n */\nexport function getDefaultTenantId(): string {\n return process.env.KB_TENANT_ID || 'default';\n}\n\n/**\n * Get default tenant tier from environment\n * @returns Tenant tier (defaults to 'free')\n */\nexport function getDefaultTenantTier(): TenantTier {\n const tier = process.env.KB_TENANT_DEFAULT_TIER?.toLowerCase();\n if (tier === 'pro' || tier === 'enterprise') {\n return tier;\n }\n return 'free';\n}\n\n/**\n * Get tenant quotas for a tier\n * @param tier - Tenant tier\n * @returns Quotas for the tier\n */\nexport function getQuotasForTier(tier: TenantTier): TenantQuotas {\n return { ...DEFAULT_QUOTAS[tier] };\n}\n","/**\n * @module @kb-labs/tenant/rate-limiter\n * Tenant rate limiter backed by platform.cache (ICache).\n *\n * Uses the same cache adapter as all other platform components —\n * no extra dependencies, swappable backend (memory / Redis / etc.).\n */\n\nimport type { ICache } from '@kb-labs/core-platform';\nimport type { TenantQuotas, TenantTier } from './types.js';\nimport { DEFAULT_QUOTAS } from './types.js';\n\n/**\n * Rate limit check result\n */\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Timestamp when the limit resets (Unix ms) */\n resetAt: number;\n /** Limit for current window */\n limit: number;\n}\n\n/**\n * Token consumption result\n */\nexport interface TokenLimitResult {\n /** Whether token budget allows the request */\n allowed: boolean;\n /** Tokens consumed so far today */\n consumed: number;\n /** Daily token limit (-1 = unlimited) */\n limit: number;\n /** Timestamp when the daily window resets (start of next UTC day, Unix ms) */\n resetAt: number;\n}\n\n/**\n * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter backed by platform.cache.\n *\n * All counters use cache TTL for cleanup — no background timers needed.\n */\nexport class TenantRateLimiter {\n constructor(\n private cache: ICache,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check (and increment) the per-minute rate limit for a tenant.\n *\n * @param tenantId - Tenant identifier\n * @param resource - Resource type to rate limit\n * @returns Rate limit check result\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = this.getLimit(quota, resource);\n const window = this.getMinuteWindow();\n // key: ratelimit:tenant:<id>:<resource>:<YYYY-MM-DDTHH:MM>\n const key = `ratelimit:tenant:${tenantId}:${resource}:${window}`;\n const ttl = 60_000; // 1 minute\n\n const current = (await this.cache.get<number>(key)) ?? 0;\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n await this.cache.set(key, current + 1, ttl);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n /**\n * Record consumed tokens and check whether the daily token budget is exceeded.\n *\n * Call this after a successful LLM response when the token count is known.\n * Returns allowed=false when the budget was already exceeded BEFORE this call\n * (i.e. the previous total was already >= limit). The current call's tokens\n * are still recorded so the counter stays accurate.\n *\n * @param tenantId - Tenant identifier\n * @param tokens - Number of tokens consumed in this request (prompt + completion)\n */\n async trackTokens(tenantId: string, tokens: number): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n // key: ratelimit:tenant:<id>:tokens:<YYYY-MM-DD>\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const ttl = 25 * 60 * 60 * 1000; // 25 h — outlasts the day window with margin\n\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n const newTotal = consumed + tokens;\n await this.cache.set(key, newTotal, ttl);\n\n if (limit === -1) {\n return { allowed: true, consumed: newTotal, limit, resetAt: this.getDayResetTime() };\n }\n\n return {\n allowed: consumed < limit, // allowed if budget wasn't exhausted before this call\n consumed: newTotal,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /**\n * Read current token consumption without incrementing.\n */\n async getTokenUsage(tenantId: string): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n\n return {\n allowed: limit === -1 || consumed < limit,\n consumed,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /** Set quotas for a specific tenant (overrides tier default). */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /** Set quotas for a tenant by tier. */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n // ── private helpers ────────────────────────────────────────────────────────\n\n /** Current minute window identifier: YYYY-MM-DDTHH:MM */\n private getMinuteWindow(): string {\n return new Date().toISOString().slice(0, 16);\n }\n\n /** Unix ms when the current minute window ends. */\n private getMinuteResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\n }\n\n /** Current day window identifier: YYYY-MM-DD (UTC) */\n private getDayWindow(): string {\n return new Date().toISOString().slice(0, 10);\n }\n\n /** Unix ms for start of next UTC day. */\n private getDayResetTime(): number {\n const d = new Date();\n d.setUTCHours(24, 0, 0, 0);\n return d.getTime();\n }\n\n private getLimit(quota: TenantQuotas, resource: RateLimitResource): number {\n switch (resource) {\n case 'requests':\n return quota.requestsPerMinute;\n case 'workflows':\n return quota.maxConcurrentWorkflows;\n case 'plugins':\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100;\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kb-labs/core-tenant",
3
3
  "description": "Multi-tenancy primitives for KB Labs",
4
- "version": "2.82.0",
4
+ "version": "2.87.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@kb-labs/core-state-broker": "2.82.0"
20
+ "@kb-labs/core-platform": "2.87.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^24.3.3",
@@ -25,7 +25,7 @@
25
25
  "tsup": "^8.5.0",
26
26
  "typescript": "^5.6.3",
27
27
  "vitest": "^3.2.4",
28
- "@kb-labs/devkit": "2.82.0"
28
+ "@kb-labs/devkit": "2.87.0"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=18.18.0",