@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.
- package/dist/adapters/node/index.d.ts +3 -3
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -3
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/kv.d.ts +0 -1
- package/dist/adapters/node/kv.d.ts.map +1 -1
- package/dist/adapters/node/kv.js +0 -17
- package/dist/adapters/node/kv.js.map +1 -1
- package/dist/db/migrations/0077_drop_workspace_plan.sql +4 -0
- package/dist/db/schema.d.ts +0 -19
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +0 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/engine/inboundWebhook.d.ts +10 -0
- package/dist/engine/inboundWebhook.d.ts.map +1 -1
- package/dist/engine/inboundWebhook.js +18 -1
- package/dist/engine/inboundWebhook.js.map +1 -1
- package/dist/engine/workspace.d.ts +0 -2
- package/dist/engine/workspace.d.ts.map +1 -1
- package/dist/engine/workspace.js +0 -1
- package/dist/engine/workspace.js.map +1 -1
- package/dist/engine/workspaceDeliveryPolicy.d.ts +1 -2
- package/dist/engine/workspaceDeliveryPolicy.d.ts.map +1 -1
- package/dist/engine/workspaceDeliveryPolicy.js +2 -5
- package/dist/engine/workspaceDeliveryPolicy.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +2 -2
- package/dist/engine.js.map +1 -1
- package/dist/entrypoints/node.d.ts +2 -2
- package/dist/entrypoints/node.d.ts.map +1 -1
- package/dist/entrypoints/node.js +1 -1
- package/dist/entrypoints/node.js.map +1 -1
- package/dist/env.d.ts +5 -5
- package/dist/env.d.ts.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/admissionError.d.ts +11 -0
- package/dist/lib/admissionError.d.ts.map +1 -0
- package/dist/lib/admissionError.js +14 -0
- package/dist/lib/admissionError.js.map +1 -0
- package/dist/lib/httpError.d.ts.map +1 -1
- package/dist/lib/httpError.js +4 -3
- package/dist/lib/httpError.js.map +1 -1
- package/dist/middleware/admission.d.ts +42 -0
- package/dist/middleware/admission.d.ts.map +1 -0
- package/dist/middleware/admission.js +82 -0
- package/dist/middleware/admission.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +2 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -1
- package/dist/middleware/rateLimit.js +22 -25
- package/dist/middleware/rateLimit.js.map +1 -1
- package/dist/ports/admission.d.ts +59 -0
- package/dist/ports/admission.d.ts.map +1 -0
- package/dist/ports/admission.js +2 -0
- package/dist/ports/admission.js.map +1 -0
- package/dist/ports/index.d.ts +10 -4
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/kv.d.ts +1 -16
- package/dist/ports/kv.d.ts.map +1 -1
- package/dist/ports/workspaceDelivery.d.ts +3 -4
- package/dist/ports/workspaceDelivery.d.ts.map +1 -1
- package/dist/routes/dm.js +3 -3
- package/dist/routes/dm.js.map +1 -1
- package/dist/routes/groupDm.js +3 -3
- package/dist/routes/groupDm.js.map +1 -1
- package/dist/routes/inboundWebhook.js +8 -0
- package/dist/routes/inboundWebhook.js.map +1 -1
- package/dist/routes/message.js +3 -3
- package/dist/routes/message.js.map +1 -1
- package/dist/routes/thread.js +3 -3
- package/dist/routes/thread.js.map +1 -1
- package/package.json +3 -3
- package/dist/engine/usage.d.ts +0 -35
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js +0 -59
- package/dist/engine/usage.js.map +0 -1
- package/dist/lib/planLimitError.d.ts +0 -26
- package/dist/lib/planLimitError.d.ts.map +0 -1
- package/dist/lib/planLimitError.js +0 -16
- package/dist/lib/planLimitError.js.map +0 -1
- package/dist/middleware/planLimits.d.ts +0 -36
- package/dist/middleware/planLimits.d.ts.map +0 -1
- package/dist/middleware/planLimits.js +0 -190
- package/dist/middleware/planLimits.js.map +0 -1
- package/dist/middleware/usageTracker.d.ts +0 -3
- package/dist/middleware/usageTracker.d.ts.map +0 -1
- package/dist/middleware/usageTracker.js +0 -11
- package/dist/middleware/usageTracker.js.map +0 -1
- package/dist/ports/entitlements.d.ts +0 -65
- package/dist/ports/entitlements.d.ts.map +0 -1
- package/dist/ports/entitlements.js +0 -2
- package/dist/ports/entitlements.js.map +0 -1
- package/dist/providers/static-entitlements.d.ts +0 -25
- package/dist/providers/static-entitlements.d.ts.map +0 -1
- package/dist/providers/static-entitlements.js +0 -55
- 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 +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 +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"}
|