@relaycast/engine 11.1.1 → 12.0.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.
Files changed (98) hide show
  1. package/dist/adapters/node/index.d.ts +3 -3
  2. package/dist/adapters/node/index.d.ts.map +1 -1
  3. package/dist/adapters/node/index.js +1 -3
  4. package/dist/adapters/node/index.js.map +1 -1
  5. package/dist/adapters/node/kv.d.ts +0 -1
  6. package/dist/adapters/node/kv.d.ts.map +1 -1
  7. package/dist/adapters/node/kv.js +0 -17
  8. package/dist/adapters/node/kv.js.map +1 -1
  9. package/dist/db/migrations/0077_drop_workspace_plan.sql +4 -0
  10. package/dist/db/schema.d.ts +0 -19
  11. package/dist/db/schema.d.ts.map +1 -1
  12. package/dist/db/schema.js +0 -1
  13. package/dist/db/schema.js.map +1 -1
  14. package/dist/engine/inboundWebhook.d.ts +10 -0
  15. package/dist/engine/inboundWebhook.d.ts.map +1 -1
  16. package/dist/engine/inboundWebhook.js +18 -1
  17. package/dist/engine/inboundWebhook.js.map +1 -1
  18. package/dist/engine/workspace.d.ts +0 -2
  19. package/dist/engine/workspace.d.ts.map +1 -1
  20. package/dist/engine/workspace.js +0 -1
  21. package/dist/engine/workspace.js.map +1 -1
  22. package/dist/engine/workspaceDeliveryPolicy.d.ts +1 -2
  23. package/dist/engine/workspaceDeliveryPolicy.d.ts.map +1 -1
  24. package/dist/engine/workspaceDeliveryPolicy.js +2 -5
  25. package/dist/engine/workspaceDeliveryPolicy.js.map +1 -1
  26. package/dist/engine.d.ts +1 -1
  27. package/dist/engine.js +2 -2
  28. package/dist/engine.js.map +1 -1
  29. package/dist/entrypoints/node.d.ts +2 -2
  30. package/dist/entrypoints/node.d.ts.map +1 -1
  31. package/dist/entrypoints/node.js +1 -1
  32. package/dist/entrypoints/node.js.map +1 -1
  33. package/dist/env.d.ts +5 -5
  34. package/dist/env.d.ts.map +1 -1
  35. package/dist/index.d.ts +1 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +0 -5
  38. package/dist/index.js.map +1 -1
  39. package/dist/lib/admissionError.d.ts +11 -0
  40. package/dist/lib/admissionError.d.ts.map +1 -0
  41. package/dist/lib/admissionError.js +14 -0
  42. package/dist/lib/admissionError.js.map +1 -0
  43. package/dist/lib/httpError.d.ts.map +1 -1
  44. package/dist/lib/httpError.js +4 -3
  45. package/dist/lib/httpError.js.map +1 -1
  46. package/dist/middleware/admission.d.ts +42 -0
  47. package/dist/middleware/admission.d.ts.map +1 -0
  48. package/dist/middleware/admission.js +82 -0
  49. package/dist/middleware/admission.js.map +1 -0
  50. package/dist/middleware/rateLimit.d.ts +2 -0
  51. package/dist/middleware/rateLimit.d.ts.map +1 -1
  52. package/dist/middleware/rateLimit.js +22 -25
  53. package/dist/middleware/rateLimit.js.map +1 -1
  54. package/dist/ports/admission.d.ts +59 -0
  55. package/dist/ports/admission.d.ts.map +1 -0
  56. package/dist/ports/admission.js +2 -0
  57. package/dist/ports/admission.js.map +1 -0
  58. package/dist/ports/index.d.ts +10 -4
  59. package/dist/ports/index.d.ts.map +1 -1
  60. package/dist/ports/kv.d.ts +1 -16
  61. package/dist/ports/kv.d.ts.map +1 -1
  62. package/dist/ports/workspaceDelivery.d.ts +3 -4
  63. package/dist/ports/workspaceDelivery.d.ts.map +1 -1
  64. package/dist/routes/dm.js +3 -3
  65. package/dist/routes/dm.js.map +1 -1
  66. package/dist/routes/groupDm.js +3 -3
  67. package/dist/routes/groupDm.js.map +1 -1
  68. package/dist/routes/inboundWebhook.js +8 -0
  69. package/dist/routes/inboundWebhook.js.map +1 -1
  70. package/dist/routes/message.js +3 -3
  71. package/dist/routes/message.js.map +1 -1
  72. package/dist/routes/thread.js +3 -3
  73. package/dist/routes/thread.js.map +1 -1
  74. package/package.json +3 -3
  75. package/dist/engine/usage.d.ts +0 -35
  76. package/dist/engine/usage.d.ts.map +0 -1
  77. package/dist/engine/usage.js +0 -59
  78. package/dist/engine/usage.js.map +0 -1
  79. package/dist/lib/planLimitError.d.ts +0 -26
  80. package/dist/lib/planLimitError.d.ts.map +0 -1
  81. package/dist/lib/planLimitError.js +0 -16
  82. package/dist/lib/planLimitError.js.map +0 -1
  83. package/dist/middleware/planLimits.d.ts +0 -36
  84. package/dist/middleware/planLimits.d.ts.map +0 -1
  85. package/dist/middleware/planLimits.js +0 -190
  86. package/dist/middleware/planLimits.js.map +0 -1
  87. package/dist/middleware/usageTracker.d.ts +0 -3
  88. package/dist/middleware/usageTracker.d.ts.map +0 -1
  89. package/dist/middleware/usageTracker.js +0 -11
  90. package/dist/middleware/usageTracker.js.map +0 -1
  91. package/dist/ports/entitlements.d.ts +0 -65
  92. package/dist/ports/entitlements.d.ts.map +0 -1
  93. package/dist/ports/entitlements.js +0 -2
  94. package/dist/ports/entitlements.js.map +0 -1
  95. package/dist/providers/static-entitlements.d.ts +0 -25
  96. package/dist/providers/static-entitlements.d.ts.map +0 -1
  97. package/dist/providers/static-entitlements.js +0 -55
  98. package/dist/providers/static-entitlements.js.map +0 -1
@@ -1,190 +0,0 @@
1
- import { createMiddleware } from 'hono/factory';
2
- import { getLifetimeUsageMetric, incrementLifetimeUsage, usagePeriodResetAt } from '../engine/usage.js';
3
- import { setRetryContract } from '../lib/throttle.js';
4
- import { PlanLimitExceededError } from '../lib/planLimitError.js';
5
- import { runInBackground } from '../routes/background.js';
6
- // Re-exported for back-compat with callers/tests that imported the table here.
7
- export { PLAN_LIMITS } from '../providers/static-entitlements.js';
8
- /**
9
- * A provider billing on its own cycle reports that cycle's end. An absent or
10
- * failing hook falls back to the engine's UTC-month period, so how the reset is
11
- * *reported* can never change the throttling decision itself.
12
- */
13
- async function resolveUsageResetAt(entitlements, workspaceId, metric) {
14
- try {
15
- const reported = await entitlements.getUsageResetAt?.(workspaceId, metric);
16
- if (typeof reported === 'number' && Number.isFinite(reported))
17
- return reported;
18
- }
19
- catch { /* fall through to the engine period */ }
20
- return usagePeriodResetAt();
21
- }
22
- /**
23
- * Where the caller can lift the limit. Advisory only: an absent or failing hook,
24
- * or a non-absolute URL, omits the guidance and never changes the decision.
25
- */
26
- async function resolveUpgradeGuidance(entitlements, workspace, metric) {
27
- try {
28
- const guidance = await entitlements.getUpgradeGuidance?.(workspace, metric);
29
- if (!guidance || typeof guidance.url !== 'string')
30
- return null;
31
- const url = new URL(guidance.url);
32
- if (url.protocol !== 'https:' && url.protocol !== 'http:')
33
- return null;
34
- return guidance;
35
- }
36
- catch {
37
- return null;
38
- }
39
- }
40
- /**
41
- * The `plan_limit_exceeded` body. The envelope stays `{ ok, error: { code,
42
- * message } }`; `metric`, `limit`, `usage`, `plan` and — when the host knows
43
- * one — `upgrade_url` / `upgrade_action` are additive fields on `error` so a
44
- * client can render "sign in" or "upgrade" without parsing the message.
45
- */
46
- function planLimitExceededBody(input) {
47
- const { guidance } = input;
48
- const nextStep = guidance
49
- ? `${guidance.hint ?? (guidance.action === 'sign_in' ? 'Sign in to keep going:' : 'Upgrade your plan to increase limits:')} ${guidance.url}`
50
- : 'Upgrade your plan to increase limits.';
51
- return {
52
- ok: false,
53
- error: {
54
- code: 'plan_limit_exceeded',
55
- message: `${input.message} ${nextStep}`,
56
- metric: input.metric,
57
- plan: input.plan,
58
- limit: input.limit,
59
- usage: input.usage,
60
- upgrade_url: guidance?.url ?? null,
61
- upgrade_action: guidance?.action ?? null,
62
- },
63
- };
64
- }
65
- export function checkPlanLimit(metric) {
66
- return createMiddleware(async (c, next) => {
67
- const workspace = c.get('workspace');
68
- if (!workspace) {
69
- await next();
70
- return;
71
- }
72
- const { entitlements } = c.get('engine');
73
- let limits;
74
- try {
75
- limits = await entitlements.getLimits(workspace);
76
- }
77
- catch {
78
- await next();
79
- return;
80
- }
81
- const limit = limits[metric];
82
- if (typeof limit !== 'number' || limit === Infinity) {
83
- await next();
84
- return;
85
- }
86
- try {
87
- const current = await entitlements.getUsage(workspace.id, metric);
88
- if (current >= limit) {
89
- const guidance = await resolveUpgradeGuidance(entitlements, workspace, metric);
90
- const plan = guidance?.plan ?? (workspace.plan || 'free');
91
- // Quota clears on the billing period, not on a backoff. Advertising the
92
- // real reset lets a caller with a bounded retry budget fail fast instead
93
- // of spending it on a condition retrying cannot fix.
94
- const resetAt = await resolveUsageResetAt(entitlements, workspace.id, metric);
95
- setRetryContract(c, resetAt);
96
- return c.json(planLimitExceededBody({
97
- metric,
98
- plan,
99
- limit,
100
- usage: current,
101
- message: `Plan limit exceeded for ${metric}. Current plan: ${plan}. Limit: ${limit} per usage period. Current usage: ${current}. Quota resets at ${new Date(resetAt).toISOString()}.`,
102
- guidance,
103
- }), 429);
104
- }
105
- }
106
- catch { /* fail open */ }
107
- await next();
108
- });
109
- }
110
- /**
111
- * Enforce a plan's one-time `lifetime_messages` allowance on message sends.
112
- *
113
- * Mounted after `rateLimit` on the routes that create a message (channel post,
114
- * DM, group DM, thread reply). Plans without a finite allowance — every tier
115
- * except a hosted anonymous trial, and always `selfhost` — skip it without
116
- * touching the counter. The allowance never resets, so the 429 carries no
117
- * `Retry-After`: retrying cannot clear it; signing in or upgrading does.
118
- *
119
- * Idempotency is preserved. Only a fresh send that succeeds (2xx without
120
- * `Idempotency-Replayed`) is counted, so retries never spend the allowance.
121
- * Once the allowance is spent, a request without an `Idempotency-Key` is
122
- * refused here; a keyed request reaches the route so a replay of an earlier
123
- * successful send still returns its stored receipt, and the route refuses only
124
- * a fresh send ({@link assertMessageAllowance}, called inside the idempotent
125
- * operation).
126
- *
127
- * The count is kept alive past the response with `runInBackground`
128
- * (`waitUntil` on Workers). Check-then-count is not atomic: concurrent sends at
129
- * the boundary can overshoot by the number in flight, which a soft trial
130
- * allowance tolerates. Fails open on entitlement or counter errors, matching
131
- * `checkPlanLimit`.
132
- */
133
- export const checkMessageAllowance = createMiddleware(async (c, next) => {
134
- const workspace = c.get('workspace');
135
- if (!workspace) {
136
- await next();
137
- return;
138
- }
139
- const { entitlements, kv } = c.get('engine');
140
- let allowance;
141
- try {
142
- allowance = (await entitlements.getLimits(workspace)).lifetime_messages;
143
- }
144
- catch {
145
- await next();
146
- return;
147
- }
148
- if (typeof allowance !== 'number' || !Number.isFinite(allowance)) {
149
- await next();
150
- return;
151
- }
152
- let used;
153
- try {
154
- used = await getLifetimeUsageMetric(kv, workspace.id, 'messages');
155
- }
156
- catch {
157
- await next();
158
- return;
159
- }
160
- if (used >= allowance) {
161
- const guidance = await resolveUpgradeGuidance(entitlements, workspace, 'messages');
162
- const plan = guidance?.plan ?? (workspace.plan || 'free');
163
- const body = planLimitExceededBody({
164
- metric: 'messages',
165
- plan,
166
- limit: allowance,
167
- usage: used,
168
- message: `Message allowance used up: this workspace has sent ${used} of its ${allowance} messages (current plan: ${plan}). The allowance does not reset.`,
169
- guidance,
170
- });
171
- if (!c.req.header('Idempotency-Key'))
172
- return c.json(body, 429);
173
- c.set('messageAllowanceDenial', body);
174
- }
175
- await next();
176
- if (c.res.status >= 200 && c.res.status < 300 && c.res.headers.get('Idempotency-Replayed') !== 'true') {
177
- runInBackground(c, incrementLifetimeUsage(kv, workspace.id, 'messages'), 'lifetime message usage increment');
178
- }
179
- });
180
- /**
181
- * Refuse a fresh send when `checkMessageAllowance` found the allowance spent.
182
- * Call it first inside a message route's idempotent `operation`, which runs
183
- * only for a new send — never for a replay of a stored receipt.
184
- */
185
- export function assertMessageAllowance(c) {
186
- const denial = c.get('messageAllowanceDenial');
187
- if (denial)
188
- throw new PlanLimitExceededError(denial);
189
- }
190
- //# sourceMappingURL=planLimits.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"planLimits.js","sourceRoot":"","sources":["../../src/middleware/planLimits.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import type { AppEnv } from '../env.js';
2
- export declare const usageTracker: import("hono").MiddlewareHandler<AppEnv, string, {}, Response>;
3
- //# sourceMappingURL=usageTracker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"usageTracker.d.ts","sourceRoot":"","sources":["../../src/middleware/usageTracker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,eAAO,MAAM,YAAY,gEAOvB,CAAC"}
@@ -1,11 +0,0 @@
1
- import { createMiddleware } from 'hono/factory';
2
- import { incrementUsage } from '../engine/usage.js';
3
- export const usageTracker = createMiddleware(async (c, next) => {
4
- const workspace = c.get('workspace');
5
- if (workspace) {
6
- // Fire-and-forget atomic usage increment via the key/value port.
7
- incrementUsage(c.get('engine').kv, workspace.id, 'api_calls').catch(() => { });
8
- }
9
- await next();
10
- });
11
- //# sourceMappingURL=usageTracker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"usageTracker.js","sourceRoot":"","sources":["../../src/middleware/usageTracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAS,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACrE,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,SAAS,EAAE,CAAC;QACd,iEAAiE;QACjE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC,CAAC"}
@@ -1,65 +0,0 @@
1
- import type { Workspace } from './auth.js';
2
- export type UsageMetric = 'messages' | 'agents' | 'file_bytes' | 'api_calls';
3
- export interface PlanLimits {
4
- messages: number;
5
- agents: number;
6
- file_bytes: number;
7
- /** Total authenticated API calls allowed for the current billing window. */
8
- api_calls: number;
9
- /** Global requests-per-minute ceiling, fed into the rate limiter. */
10
- rate_per_min: number;
11
- /**
12
- * One-time message allowance that never resets. When set to a finite number,
13
- * message sends (channel posts, DMs, group DMs, thread replies) are refused
14
- * with `plan_limit_exceeded` once the workspace has sent this many messages
15
- * over its whole lifetime. Unset (or `Infinity`) means no lifetime cap — the
16
- * case for every tier except a hosted anonymous trial.
17
- */
18
- lifetime_messages?: number;
19
- }
20
- /**
21
- * Where a workspace that hit a limit should go next. Returned by
22
- * {@link EntitlementsProvider.getUpgradeGuidance} and surfaced on the
23
- * `plan_limit_exceeded` error body as `upgrade_url` / `upgrade_action`.
24
- */
25
- export interface UpgradeGuidance {
26
- /** An absolute URL the caller can open to lift the limit. */
27
- url: string;
28
- /** `sign_in` when signing in (free) lifts the limit, `upgrade` when a paid plan does. */
29
- action: 'sign_in' | 'upgrade';
30
- /** Effective plan name to report, when it differs from the stored `workspace.plan`. */
31
- plan?: string;
32
- /** One sentence appended to the error message, e.g. how to sign in. */
33
- hint?: string;
34
- }
35
- /**
36
- * Pluggable plan limits / usage — the billing seam.
37
- *
38
- * Self-host uses `StaticEntitlementsProvider`, seeded from today's hardcoded
39
- * `PLAN_LIMITS` (free/connect/pro/enterprise), defaulting single-tenant deployments to a
40
- * generous/unlimited tier. The cloud product injects a provider that resolves
41
- * quotas from its billing system (e.g. a Stripe subscription) per workspace and
42
- * reports metered usage.
43
- */
44
- export interface EntitlementsProvider {
45
- /** Resolve the active plan limits for a workspace. */
46
- getLimits(workspace: Workspace): Promise<PlanLimits>;
47
- /** Current usage for a metric, compared against the limit before allowing an action. */
48
- getUsage(workspaceId: string, metric: UsageMetric): Promise<number>;
49
- /**
50
- * Unix ms at which `metric`'s usage window rolls over, used to put an honest
51
- * `Retry-After` on a quota 429 so callers can tell a wait-and-retry condition
52
- * from one that no retry will clear. Optional: a provider billing on its own
53
- * cycle reports that cycle's end; omitting it falls back to the engine's
54
- * UTC-month period.
55
- */
56
- getUsageResetAt?(workspaceId: string, metric: UsageMetric): Promise<number>;
57
- /**
58
- * Where a workspace that hit `metric`'s limit can lift it (sign in, upgrade).
59
- * Optional: self-host deployments have no upgrade path, and an absent or
60
- * failing hook only omits `upgrade_url` from the error — it never changes
61
- * the limit decision.
62
- */
63
- getUpgradeGuidance?(workspace: Workspace, metric: UsageMetric): Promise<UpgradeGuidance | null>;
64
- }
65
- //# sourceMappingURL=entitlements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/ports/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErD,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpE;;;;;;OAMG;IACH,eAAe,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5E;;;;;OAKG;IACH,kBAAkB,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACjG"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=entitlements.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../src/ports/entitlements.ts"],"names":[],"mappings":""}
@@ -1,25 +0,0 @@
1
- import type { EntitlementsProvider, PlanLimits, UsageMetric, Workspace } from '../ports/index.js';
2
- import type { KeyValueStore } from '../ports/kv.js';
3
- /**
4
- * The default plan limits, lifted from the original `middleware/planLimits.ts`.
5
- * Self-host deployments typically run the `selfhost` tier (everything
6
- * unlimited); the trial/free/connect/pro/enterprise tiers are kept so the same code serves a
7
- * tiered deployment if a workspace's `plan` column is set.
8
- */
9
- export declare const PLAN_LIMITS: Record<string, PlanLimits>;
10
- /**
11
- * Static, table-driven entitlements — the self-host default. Resolves limits
12
- * from {@link PLAN_LIMITS} keyed by `workspace.plan`, and reads usage counters
13
- * from the key/value store (the same `usage:<wid>:<metric>:<period>` keys the
14
- * usage tracker increments, so both agree on the current billing period). The
15
- * cloud product injects a billing-backed provider.
16
- */
17
- export declare class StaticEntitlementsProvider implements EntitlementsProvider {
18
- private readonly kv?;
19
- private readonly defaultPlan;
20
- constructor(kv?: KeyValueStore | undefined, defaultPlan?: string);
21
- getLimits(workspace: Workspace): Promise<PlanLimits>;
22
- getUsage(workspaceId: string, metric: UsageMetric): Promise<number>;
23
- getUsageResetAt(): Promise<number>;
24
- }
25
- //# sourceMappingURL=static-entitlements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"static-entitlements.d.ts","sourceRoot":"","sources":["../../src/providers/static-entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAclD,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,0BAA2B,YAAW,oBAAoB;IAEnE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBADX,EAAE,CAAC,EAAE,aAAa,YAAA,EAClB,WAAW,GAAE,MAAmB;IAG7C,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAKpD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IASnE,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;CAGzC"}
@@ -1,55 +0,0 @@
1
- import { getUsageMetric, usagePeriodResetAt } from '../engine/usage.js';
2
- /**
3
- * The default plan limits, lifted from the original `middleware/planLimits.ts`.
4
- * Self-host deployments typically run the `selfhost` tier (everything
5
- * unlimited); the trial/free/connect/pro/enterprise tiers are kept so the same code serves a
6
- * tiered deployment if a workspace's `plan` column is set.
7
- */
8
- export const PLAN_LIMITS = {
9
- // Hosted anonymous trial: a workspace created without signing in. A one-time
10
- // 100-message allowance (`lifetime_messages`, never resets) is enough to prove
11
- // agents on two machines can talk; signing in moves the workspace to `free`.
12
- // The other limits match `free` so the trial is only ever narrower.
13
- trial: { messages: 100, lifetime_messages: 100, agents: 5, file_bytes: 100 * 1024 * 1024, api_calls: 100_000, rate_per_min: 300 },
14
- free: { messages: 10_000, agents: 5, file_bytes: 100 * 1024 * 1024, api_calls: 100_000, rate_per_min: 300 },
15
- // Short-lived collaboration rooms (Relay Connect): a handful of seats that
16
- // poll their inbox continuously, so api_calls dominates while messages stay
17
- // low. Sized for a two-seat room polling every few seconds for a full month.
18
- connect: { messages: 100_000, agents: 10, file_bytes: 1024 * 1024 * 1024, api_calls: 10_000_000, rate_per_min: 1200 },
19
- pro: { messages: 1_000_000, agents: 100, file_bytes: 50 * 1024 * 1024 * 1024, api_calls: 10_000_000, rate_per_min: 6000 },
20
- enterprise: { messages: Infinity, agents: Infinity, file_bytes: 500 * 1024 * 1024 * 1024, api_calls: Infinity, rate_per_min: 30000 },
21
- selfhost: { messages: Infinity, agents: Infinity, file_bytes: Infinity, api_calls: Infinity, rate_per_min: 30000 },
22
- };
23
- /**
24
- * Static, table-driven entitlements — the self-host default. Resolves limits
25
- * from {@link PLAN_LIMITS} keyed by `workspace.plan`, and reads usage counters
26
- * from the key/value store (the same `usage:<wid>:<metric>:<period>` keys the
27
- * usage tracker increments, so both agree on the current billing period). The
28
- * cloud product injects a billing-backed provider.
29
- */
30
- export class StaticEntitlementsProvider {
31
- kv;
32
- defaultPlan;
33
- constructor(kv, defaultPlan = 'selfhost') {
34
- this.kv = kv;
35
- this.defaultPlan = defaultPlan;
36
- }
37
- async getLimits(workspace) {
38
- const plan = workspace.plan || this.defaultPlan;
39
- return PLAN_LIMITS[plan] ?? PLAN_LIMITS[this.defaultPlan];
40
- }
41
- async getUsage(workspaceId, metric) {
42
- if (!this.kv)
43
- return 0;
44
- try {
45
- return await getUsageMetric(this.kv, workspaceId, metric);
46
- }
47
- catch {
48
- return 0;
49
- }
50
- }
51
- async getUsageResetAt() {
52
- return usagePeriodResetAt();
53
- }
54
- }
55
- //# sourceMappingURL=static-entitlements.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"static-entitlements.js","sourceRoot":"","sources":["../../src/providers/static-entitlements.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAA+B;IACrD,6EAA6E;IAC7E,+EAA+E;IAC/E,6EAA6E;IAC7E,oEAAoE;IACpE,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE;IACjI,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE;IAC3G,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE;IACrH,GAAG,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE;IACzH,UAAU,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE;IACpI,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE;CACnH,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,OAAO,0BAA0B;IAElB;IACA;IAFnB,YACmB,EAAkB,EAClB,cAAsB,UAAU;QADhC,OAAE,GAAF,EAAE,CAAgB;QAClB,gBAAW,GAAX,WAAW,CAAqB;IAChD,CAAC;IAEJ,KAAK,CAAC,SAAS,CAAC,SAAoB;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC;QAChD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,MAAmB;QACrD,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,CAAC,CAAC;QACvB,IAAI,CAAC;YACH,OAAO,MAAM,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;CACF"}