billing-constants.mjs
246 lines 8.5 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Hosted billing constants — Phase 16 tier model. See hub/gateway/billing-*.mjs and docs/TOKEN-SAVINGS.md (billing hooks).
3 *
4 * Tiers: free · plus ($9) · growth ($17) · pro ($25)
5 * Legacy aliases: beta (internal dev/no-cap), starter (→ plus), team (reserved for future seats)
6 */
7
8 /**
9 * Monthly included credit budget in cents for legacy metered ops (search, note_write, proposal_write).
10 * This parallel ledger still runs; the primary per-period limit is MONTHLY_INDEXING_TOKENS_INCLUDED_BY_TIER.
11 */
12 export const MONTHLY_INCLUDED_CENTS_BY_TIER = {
13 beta: 0,
14 free: 3 * 100,
15 plus: 9 * 100,
16 starter: 9 * 100, // legacy alias → plus
17 growth: 17 * 100,
18 pro: 25 * 100,
19 team: 80 * 100, // reserved for future team/seats tier
20 };
21
22 /**
23 * Monthly indexing allowance (embedding input tokens) per tier.
24 * `pro` = null → unlimited (no enforcement cap).
25 * `beta` = null → unlimited (internal dev).
26 */
27 export const MONTHLY_INDEXING_TOKENS_INCLUDED_BY_TIER = {
28 free: 5_000_000,
29 plus: 36_000_000,
30 starter: 36_000_000, // legacy alias → plus
31 growth: 68_000_000,
32 pro: null, // unlimited
33 team: 400_000_000, // reserved
34 };
35
36 /**
37 * Note count caps per tier. null = unlimited (no hard cap enforced).
38 * Enforcement: 402 STORAGE_QUOTA_EXCEEDED on POST /api/v1/notes when BILLING_ENFORCE=true.
39 */
40 export const NOTE_CAP_BY_TIER = {
41 beta: null,
42 free: 200,
43 plus: 2_000,
44 starter: 2_000, // legacy alias → plus
45 growth: 5_000,
46 pro: null,
47 team: null,
48 };
49
50 /** Shown on GET /api/v1/billing/summary and Hub billing UI. */
51 export const INDEXING_TOKENS_POLICY =
52 'Semantic search is included (fair use). Indexing is measured in embedding input tokens per billing period; add-on token packs roll over when billing is fully enabled.';
53
54 /** Token amounts granted per pack (matches Stripe price metadata `indexing_tokens`). */
55 export const PACK_TOKENS = {
56 small: 20_000_000,
57 medium: 60_000_000,
58 large: 150_000_000,
59 };
60
61 /** Memory consolidation passes granted per pack purchase. */
62 export const PACK_CONSOLIDATIONS = {
63 small: 50,
64 medium: 150,
65 large: 350,
66 };
67
68 /**
69 * Stripe Price id → subscription tier.
70 * Reads STRIPE_PRICE_PLUS, STRIPE_PRICE_GROWTH, STRIPE_PRICE_PRO from env (set in Netlify).
71 * Legacy STRIPE_PRICE_STARTER still maps to 'plus' for backward compat during migration.
72 */
73 export function tierFromEnvPriceId(priceId) {
74 if (!priceId) return null;
75 if (process.env.STRIPE_PRICE_PLUS && priceId === process.env.STRIPE_PRICE_PLUS) return 'plus';
76 if (process.env.STRIPE_PRICE_GROWTH && priceId === process.env.STRIPE_PRICE_GROWTH) return 'growth';
77 if (process.env.STRIPE_PRICE_PRO && priceId === process.env.STRIPE_PRICE_PRO) return 'pro';
78 if (process.env.STRIPE_PRICE_STARTER && priceId === process.env.STRIPE_PRICE_STARTER) return 'plus';
79 if (process.env.STRIPE_PRICE_TEAM && priceId === process.env.STRIPE_PRICE_TEAM) return 'team';
80 return null;
81 }
82
83 /**
84 * Returns true if a given price ID is a known subscription price (for checkout mode selection).
85 */
86 export function isSubscriptionPriceId(priceId) {
87 return tierFromEnvPriceId(priceId) !== null;
88 }
89
90 /**
91 * Returns true if a given price ID is a known token pack price (one-time payment).
92 */
93 export function isPackPriceId(priceId) {
94 if (!priceId) return false;
95 return Boolean(
96 (process.env.STRIPE_PRICE_PACK_10 && priceId === process.env.STRIPE_PRICE_PACK_10) ||
97 (process.env.STRIPE_PRICE_PACK_25 && priceId === process.env.STRIPE_PRICE_PACK_25) ||
98 (process.env.STRIPE_PRICE_PACK_50 && priceId === process.env.STRIPE_PRICE_PACK_50),
99 );
100 }
101
102 /** Stripe Price id → add-on credits in cents (legacy credit ledger). */
103 export function addonCentsFromPackPriceId(priceId) {
104 if (!priceId) return null;
105 if (process.env.STRIPE_PRICE_PACK_10 && priceId === process.env.STRIPE_PRICE_PACK_10) return 10 * 100;
106 if (process.env.STRIPE_PRICE_PACK_25 && priceId === process.env.STRIPE_PRICE_PACK_25) return 25 * 100;
107 if (process.env.STRIPE_PRICE_PACK_50 && priceId === process.env.STRIPE_PRICE_PACK_50) return 50 * 100;
108 return null;
109 }
110
111 /**
112 * Stripe Price id → indexing token grant for pack purchase.
113 * Matches PACK_TOKENS amounts and Stripe price metadata `indexing_tokens`.
114 */
115 export function addonTokensFromPackPriceId(priceId) {
116 if (!priceId) return null;
117 if (process.env.STRIPE_PRICE_PACK_10 && priceId === process.env.STRIPE_PRICE_PACK_10) return PACK_TOKENS.small;
118 if (process.env.STRIPE_PRICE_PACK_25 && priceId === process.env.STRIPE_PRICE_PACK_25) return PACK_TOKENS.medium;
119 if (process.env.STRIPE_PRICE_PACK_50 && priceId === process.env.STRIPE_PRICE_PACK_50) return PACK_TOKENS.large;
120 return null;
121 }
122
123 /**
124 * Stripe Price id → memory consolidation pass grant for pack purchase.
125 * Matches PACK_CONSOLIDATIONS amounts.
126 */
127 export function addonConsolidationsFromPackPriceId(priceId) {
128 if (!priceId) return null;
129 if (process.env.STRIPE_PRICE_PACK_10 && priceId === process.env.STRIPE_PRICE_PACK_10) return PACK_CONSOLIDATIONS.small;
130 if (process.env.STRIPE_PRICE_PACK_25 && priceId === process.env.STRIPE_PRICE_PACK_25) return PACK_CONSOLIDATIONS.medium;
131 if (process.env.STRIPE_PRICE_PACK_50 && priceId === process.env.STRIPE_PRICE_PACK_50) return PACK_CONSOLIDATIONS.large;
132 return null;
133 }
134
135 /**
136 * Resolve a tier shorthand (e.g. 'plus', 'growth', 'pro') to its Stripe Price ID from env.
137 * Returns null if the env var is not set (Stripe not configured yet).
138 */
139 export function priceIdFromTierShorthand(tier) {
140 const t = String(tier || '').toLowerCase();
141 if (t === 'plus' || t === 'starter') return process.env.STRIPE_PRICE_PLUS || process.env.STRIPE_PRICE_STARTER || null;
142 if (t === 'growth') return process.env.STRIPE_PRICE_GROWTH || null;
143 if (t === 'pro') return process.env.STRIPE_PRICE_PRO || null;
144 return null;
145 }
146
147 /**
148 * Monthly search request allowance by tier.
149 * null = unlimited. Pending calibration from shadow-log data before BILLING_ENFORCE=true.
150 */
151 export const MONTHLY_SEARCHES_INCLUDED_BY_TIER = {
152 beta: null,
153 free: 100,
154 plus: 2_000,
155 starter: 2_000,
156 growth: 8_000,
157 pro: null,
158 team: null,
159 };
160
161 /**
162 * Monthly index-job allowance by tier.
163 * null = unlimited. Pending calibration from shadow-log data before BILLING_ENFORCE=true.
164 */
165 export const MONTHLY_INDEX_JOBS_INCLUDED_BY_TIER = {
166 beta: null,
167 free: 5,
168 plus: 50,
169 starter: 50,
170 growth: 200,
171 pro: null,
172 team: null,
173 };
174
175 /**
176 * Monthly consolidation pass allowance by tier.
177 * null = unlimited. free = 0 → no hosted consolidation on free tier.
178 */
179 export const CONSOLIDATION_PASSES_BY_TIER = {
180 beta: null, // unlimited (internal dev)
181 free: 0, // no hosted consolidation on free
182 plus: 30,
183 starter: 30, // legacy alias → plus
184 growth: 100,
185 pro: 300, // 300/mo; rate limit enforced server-side (30-min cooldown)
186 team: 300,
187 };
188
189 /** Metered operation → cost in cents (legacy credit ledger). Shadow-log only until BILLING_ENFORCE=true. */
190 export const COST_CENTS = {
191 search: 1,
192 index: 50,
193 note_write: 2,
194 proposal_write: 2,
195 consolidation: 5,
196 };
197
198 /**
199 * User-facing cost transparency (shown in billing summary for Hub UI).
200 * cost_usd_display is for display; internal ledger uses cost_cents.
201 */
202 export const COST_BREAKDOWN = [
203 {
204 operation: 'search',
205 label: 'Semantic search (one request)',
206 cost_cents: COST_CENTS.search,
207 relates_to: 'Bridge vector search + CPU',
208 },
209 {
210 operation: 'index',
211 label: 'Re-index vault (one job)',
212 cost_cents: COST_CENTS.index,
213 relates_to: 'Embedding API + storage (largest variable cost)',
214 },
215 {
216 operation: 'note_write',
217 label: 'Create or update a note',
218 cost_cents: COST_CENTS.note_write,
219 relates_to: 'Canister write + storage',
220 },
221 {
222 operation: 'proposal_write',
223 label: 'Create a proposal',
224 cost_cents: COST_CENTS.proposal_write,
225 relates_to: 'Canister write + storage',
226 },
227 {
228 operation: 'consolidation',
229 label: 'Memory consolidation pass (hosted)',
230 cost_cents: COST_CENTS.consolidation,
231 relates_to: 'Hub LLM (gpt-4o-mini) — merge, verify, discover passes',
232 },
233 ].map((row) => ({
234 ...row,
235 cost_usd_display: (row.cost_cents / 100).toFixed(2),
236 credits_display: (row.cost_cents / 100).toFixed(2),
237 }));
238
239 export function billingEnforced() {
240 return process.env.BILLING_ENFORCE === 'true' || process.env.BILLING_ENFORCE === '1';
241 }
242
243 /** Structured JSON logs for usage research (gateway stdout → log drains). */
244 export function billingShadowLogEnabled() {
245 return process.env.BILLING_SHADOW_LOG === '1' || process.env.BILLING_SHADOW_LOG === 'true';
246 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago