@tangle-network/agent-app 0.44.32 → 0.44.33
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/assistant/index.d.ts +2 -0
- package/dist/assistant/index.js +1 -1
- package/dist/billing-BibxgALe.d.ts +193 -0
- package/dist/{chunk-GCH3BUAZ.js → chunk-NDVTYHLN.js} +46 -18
- package/dist/{chunk-GCH3BUAZ.js.map → chunk-NDVTYHLN.js.map} +1 -1
- package/dist/platform/index.d.ts +3 -177
- package/dist/platform/index.js +27 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/web-react/index.d.ts +7 -4
- package/dist/web-react/index.js +1 -1
- package/package.json +1 -1
package/dist/platform/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { c as AdminGuardOptions, d as AssertBillableBalanceOptions, A as AuthGuard, e as AuthGuardOptions, B as BetterAuthSessionCookieMinterOptions, f as BetterAuthSessionCookieSource, g as BillableBalanceState, G as GuardResolution, S as SsoStateConfig, b as TangleSsoAccountStore, a as TangleSsoAuthClient, h as TangleSsoExchangeResult, i as TangleSsoHandlerOptions, T as TangleSsoHandlers, j as TangleSsoSessionCookieArgs, k as TangleSsoUserCreateError, l as assertBillableBalance, m as createAdminGuard, n as createAuthGuard, o as createBetterAuthSessionCookieMinter, p as createSignedSsoState, q as createTangleSsoHandlers, r as guardResolution, s as parseAdminEmails, t as signSessionCookieValue, v as verifySignedSsoState } from '../sso-Cm00S61u.js';
|
|
2
2
|
import { a as TangleExecutionEnvironment, b as TangleExecutionKeySource } from '../model-CdCDfBA9.js';
|
|
3
|
-
|
|
3
|
+
export { D as DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR, a as DEFAULT_TANGLE_TIER_POLICY, F as FREE_TIER_SPEND_CAP_USD, P as PlatformBalanceSnapshot, b as PlatformBillingHttp, c as PlatformBillingHttpError, d as PlatformBillingHttpOptions, e as PlatformIdentityStore, f as PlatformSubscriptionInfo, g as PlatformUsageProductRow, h as ProductEntitlement, i as ProductSeatOffer, j as ProductSeatOfferPeriod, S as SeatBillingFlagOptions, k as SeatStatus, T as TanglePlanTier, l as TangleTierPolicy, m as TangleTierState, n as createPlatformBillingHttp, o as createTanglePlatformBillingClient, p as getProductEntitlement, q as isPlatformBillingHttpError, r as isProductEntitled, s as isSeatBillingEnabled, t as normalizeTanglePlanTier, u as readTangleTierState, v as seatCheckoutUrl } from '../billing-BibxgALe.js';
|
|
4
|
+
import '../billing/index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Integrations-hub proxy routes: the app-side surface that forwards an
|
|
@@ -110,179 +111,4 @@ interface HubProxyRoutes {
|
|
|
110
111
|
/** Resolve hub proxy routes with authentication and error handling based on the given context */
|
|
111
112
|
declare function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes;
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
* Platform billing HTTP transport + tier state for apps on the shared
|
|
115
|
-
* Tangle balance model (id.tangle.tools). Reads authenticate as the user via
|
|
116
|
-
* their per-user platform key (the platform resolves the caller from the
|
|
117
|
-
* key; service or impersonation headers on read routes are rejected). The
|
|
118
|
-
* deduct write authenticates as the product service (`Bearer <serviceToken>`
|
|
119
|
-
* + `X-Service-Name`) and names the target user in the body. Also provides a
|
|
120
|
-
* fetch-backed implementation of the `/billing` module's
|
|
121
|
-
* `PlatformBillingClient` seam (type-only import — no runtime coupling).
|
|
122
|
-
*/
|
|
123
|
-
|
|
124
|
-
/** Define available subscription tiers for the TanglePlan service */
|
|
125
|
-
type TanglePlanTier = 'free' | 'pro' | 'enterprise';
|
|
126
|
-
/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */
|
|
127
|
-
declare function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier;
|
|
128
|
-
/** Represent platform billing HTTP errors with status code and detailed message */
|
|
129
|
-
declare class PlatformBillingHttpError extends Error {
|
|
130
|
-
readonly status: number;
|
|
131
|
-
constructor(status: number, detail: string);
|
|
132
|
-
}
|
|
133
|
-
/** Structural guard (name + numeric status) — robust across module instances. */
|
|
134
|
-
declare function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError;
|
|
135
|
-
/** Define HTTP options for platform billing including base URL, service token, product slug, fetch implementation, and timeout */
|
|
136
|
-
interface PlatformBillingHttpOptions {
|
|
137
|
-
/** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */
|
|
138
|
-
baseUrl: string;
|
|
139
|
-
/** Used only by `deduct()`; resolved lazily so reads never require it.
|
|
140
|
-
* Throws at call time when empty. */
|
|
141
|
-
serviceToken: string | (() => string);
|
|
142
|
-
/** Product slug — the `X-Service-Name` header and the deduct `product` field. */
|
|
143
|
-
productSlug: string;
|
|
144
|
-
fetchImpl?: typeof fetch;
|
|
145
|
-
/** Default 10 000. */
|
|
146
|
-
timeoutMs?: number;
|
|
147
|
-
}
|
|
148
|
-
/** Describe subscription tier and status information for a platform user */
|
|
149
|
-
interface PlatformSubscriptionInfo {
|
|
150
|
-
tier: TanglePlanTier;
|
|
151
|
-
status: string | null;
|
|
152
|
-
}
|
|
153
|
-
/** Describe the platform balance and lifetime spending with an optional update timestamp */
|
|
154
|
-
interface PlatformBalanceSnapshot {
|
|
155
|
-
balance: number;
|
|
156
|
-
lifetimeSpent: number;
|
|
157
|
-
updatedAt?: string;
|
|
158
|
-
}
|
|
159
|
-
/** Describe a product's usage and spending metrics on the platform */
|
|
160
|
-
interface PlatformUsageProductRow {
|
|
161
|
-
product: string | null;
|
|
162
|
-
totalSpent: number;
|
|
163
|
-
count: number;
|
|
164
|
-
}
|
|
165
|
-
/** Lifecycle of a per-product seat subscription, mirroring the Stripe states
|
|
166
|
-
* the platform persists. 'none' = the user has never held this seat. */
|
|
167
|
-
type SeatStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'canceled';
|
|
168
|
-
/**
|
|
169
|
-
* Per-product entitlement snapshot from the platform — the single read that
|
|
170
|
-
* tells a product whether to show its workspace or the seat paywall. Shape
|
|
171
|
-
* matches `GET /v1/billing/product-entitlement?product=<id>`.
|
|
172
|
-
*
|
|
173
|
-
* `hasSeat` and `onFreeTier` are computed platform-side from the raw seat row
|
|
174
|
-
* + cumulative spend so the gate is identical across all five products:
|
|
175
|
-
* - `hasSeat` — an active/trialing seat whose period has not lapsed.
|
|
176
|
-
* - `onFreeTier` — no active seat AND cumulative spend below the free cap
|
|
177
|
-
* ($2 / 200¢ lifetime). Keys off lifetime spend, not wallet
|
|
178
|
-
* balance, so a router top-up never re-opens free access.
|
|
179
|
-
*/
|
|
180
|
-
interface ProductEntitlement {
|
|
181
|
-
seatStatus: SeatStatus;
|
|
182
|
-
/** ISO timestamp the active seat's paid period runs until; null when none. */
|
|
183
|
-
currentPeriodEnd: string | null;
|
|
184
|
-
/** Cumulative inference spend across the whole suite, in dollars. */
|
|
185
|
-
lifetimeSpentUsd: number;
|
|
186
|
-
hasSeat: boolean;
|
|
187
|
-
onFreeTier: boolean;
|
|
188
|
-
}
|
|
189
|
-
/** Define methods to interact with platform billing endpoints using user or service authentication */
|
|
190
|
-
interface PlatformBillingHttp {
|
|
191
|
-
/** GET /v1/plans/current (user bearer). */
|
|
192
|
-
getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>;
|
|
193
|
-
/** GET /v1/billing/balance (user bearer). */
|
|
194
|
-
getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>;
|
|
195
|
-
/** GET /v1/billing/usage (user bearer). */
|
|
196
|
-
getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>;
|
|
197
|
-
/** GET /v1/billing/product-entitlement?product=<id> (user bearer). */
|
|
198
|
-
getProductEntitlement(userApiKey: string, productId: string): Promise<ProductEntitlement>;
|
|
199
|
-
/** POST /v1/billing/deduct (service token). */
|
|
200
|
-
deduct(input: {
|
|
201
|
-
platformUserId: string;
|
|
202
|
-
amountUsd: number;
|
|
203
|
-
type: string;
|
|
204
|
-
description: string;
|
|
205
|
-
referenceId: string;
|
|
206
|
-
}): Promise<void>;
|
|
207
|
-
/** Absolute URL of the platform's billing-management surface. */
|
|
208
|
-
billingUrl(): string;
|
|
209
|
-
/** Absolute URL of the $100/mo seat checkout for `productId`. */
|
|
210
|
-
seatCheckoutUrl(productId: string): string;
|
|
211
|
-
}
|
|
212
|
-
/** Create a PlatformBillingHttp instance configured with given options and default behaviors */
|
|
213
|
-
declare function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp;
|
|
214
|
-
/**
|
|
215
|
-
* Platform Stripe checkout URL for a product's $100/mo seat. One shared price
|
|
216
|
-
* carries `metadata.productId`; the platform distinguishes the product from
|
|
217
|
-
* the `product` query param (not five distinct prices). Mirrors the
|
|
218
|
-
* `billingUrl()` shape — a deterministic platform-rooted URL, no network call.
|
|
219
|
-
*/
|
|
220
|
-
declare function seatCheckoutUrl(baseUrl: string, productId: string): string;
|
|
221
|
-
/** Define policy settings for concurrency and overage allowance in a tangle tier */
|
|
222
|
-
interface TangleTierPolicy {
|
|
223
|
-
concurrency: number;
|
|
224
|
-
overageAllowed: boolean;
|
|
225
|
-
}
|
|
226
|
-
/** Define default concurrency and overage policies for each TanglePlanTier level */
|
|
227
|
-
declare const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy>;
|
|
228
|
-
/** Describe the state of a Tangle plan tier including subscription, balance, spending, and concurrency details */
|
|
229
|
-
interface TangleTierState {
|
|
230
|
-
tier: TanglePlanTier;
|
|
231
|
-
subscriptionStatus: string | null;
|
|
232
|
-
remainingBalanceUsd: number;
|
|
233
|
-
lifetimeSpentUsd: number;
|
|
234
|
-
concurrency: number;
|
|
235
|
-
overageAllowed: boolean;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Read subscription + balance and project them onto the tier policy. A
|
|
239
|
-
* null/absent key fails CLOSED (free tier, zero balance) — a billable run is
|
|
240
|
-
* never started against an unknown balance. Platform errors throw; callers
|
|
241
|
-
* on the billable path choose their posture explicitly.
|
|
242
|
-
*/
|
|
243
|
-
declare function readTangleTierState(http: PlatformBillingHttp, userApiKey: string | null | undefined, policy?: Record<TanglePlanTier, TangleTierPolicy>): Promise<TangleTierState>;
|
|
244
|
-
/** Lifetime free-tier cap: $2 (200¢) cumulative inference spend, expressed in
|
|
245
|
-
* dollars. Free product access ends once cumulative spend crosses this. */
|
|
246
|
-
declare const FREE_TIER_SPEND_CAP_USD = 2;
|
|
247
|
-
/**
|
|
248
|
-
* Default name of the per-app feature flag gating seat billing. While OFF the
|
|
249
|
-
* entitlement read is skipped and access fails OPEN (entitled) so nothing
|
|
250
|
-
* changes live until a product flips the flag.
|
|
251
|
-
*/
|
|
252
|
-
declare const DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = "SEAT_BILLING_ENABLED";
|
|
253
|
-
/** Define options to configure seat billing flag environment variables and override flag name */
|
|
254
|
-
interface SeatBillingFlagOptions {
|
|
255
|
-
env?: Record<string, string | undefined>;
|
|
256
|
-
/** Override the flag name; default {@link DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR}. */
|
|
257
|
-
flagEnvVar?: string;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Seat billing is OFF unless the flag is explicitly truthy ('true'/'1'/'on'/
|
|
261
|
-
* 'enabled'). Default OFF — pre-rollout, the paywall never engages. Returns
|
|
262
|
-
* false when no env is available (browser bundles) so the client stays
|
|
263
|
-
* fail-open there too.
|
|
264
|
-
*/
|
|
265
|
-
declare function isSeatBillingEnabled(opts?: SeatBillingFlagOptions): boolean;
|
|
266
|
-
/**
|
|
267
|
-
* Read a user's entitlement for one product. Fails OPEN: an absent key,
|
|
268
|
-
* disabled flag, or unreachable seat endpoint all return a permissive snapshot
|
|
269
|
-
* (`hasSeat: true`) so consumers never break pre-rollout. The platform owns the
|
|
270
|
-
* `hasSeat`/`onFreeTier` computation; this client only transports + degrades
|
|
271
|
-
* safely.
|
|
272
|
-
*
|
|
273
|
-
* @param flag — pass {@link isSeatBillingEnabled} (or your own boolean) so the
|
|
274
|
-
* product owns when the gate engages. When false, no network call is made.
|
|
275
|
-
*/
|
|
276
|
-
declare function getProductEntitlement(http: Pick<PlatformBillingHttp, 'getProductEntitlement'>, userApiKey: string | null | undefined, productId: string, flag?: boolean): Promise<ProductEntitlement>;
|
|
277
|
-
/** Entitled = holds an active seat OR is still inside the free tier. The one
|
|
278
|
-
* predicate all five products gate on. */
|
|
279
|
-
declare function isProductEntitled(ent: ProductEntitlement): boolean;
|
|
280
|
-
/** Define a contract for resolving platform identities based on user identifiers */
|
|
281
|
-
interface PlatformIdentityStore {
|
|
282
|
-
resolveIdentity(userId: string): Promise<PlatformIdentity | null>;
|
|
283
|
-
}
|
|
284
|
-
/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for
|
|
285
|
-
* `createPlatformBalanceManager` (from `/billing`). */
|
|
286
|
-
declare function createTanglePlatformBillingClient(http: PlatformBillingHttp, identity: PlatformIdentityStore): PlatformBillingClient<TanglePlanTier>;
|
|
287
|
-
|
|
288
|
-
export { DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR, DEFAULT_TANGLE_TIER_POLICY, FREE_TIER_SPEND_CAP_USD, type HubClientLike, type HubProxyContext, type HubProxyRouteArgs, type HubProxyRoutes, type PlatformBalanceSnapshot, type PlatformBillingHttp, PlatformBillingHttpError, type PlatformBillingHttpOptions, type PlatformIdentityStore, type PlatformSubscriptionInfo, type PlatformUsageProductRow, type ProductEntitlement, type ResolveUserTangleHubBearerForUserOptions, type ResolveUserTangleHubBearerOptions, type ResolvedTangleHubBearer, type SeatBillingFlagOptions, type SeatStatus, TangleBearerMissingError, type TangleHubBearerSource, type TanglePlanTier, type TangleTierPolicy, type TangleTierState, createHubProxyRoutes, createPlatformBillingHttp, createTanglePlatformBillingClient, getProductEntitlement, isPlatformBillingHttpError, isPlatformHubErrorLike, isProductEntitled, isSeatBillingEnabled, isTangleBearerMissingError, normalizeTanglePlanTier, readTangleTierState, resolveUserTangleHubBearer, resolveUserTangleHubBearerForUser, seatCheckoutUrl };
|
|
114
|
+
export { type HubClientLike, type HubProxyContext, type HubProxyRouteArgs, type HubProxyRoutes, type ResolveUserTangleHubBearerForUserOptions, type ResolveUserTangleHubBearerOptions, type ResolvedTangleHubBearer, TangleBearerMissingError, type TangleHubBearerSource, createHubProxyRoutes, isPlatformHubErrorLike, isTangleBearerMissingError, resolveUserTangleHubBearer, resolveUserTangleHubBearerForUser };
|
package/dist/platform/index.js
CHANGED
|
@@ -125,6 +125,30 @@ var PlatformBillingHttpError = class extends Error {
|
|
|
125
125
|
function isPlatformBillingHttpError(error) {
|
|
126
126
|
return error instanceof Error && error.name === "PlatformBillingHttpError" && typeof error.status === "number";
|
|
127
127
|
}
|
|
128
|
+
function productSeatOffer(value) {
|
|
129
|
+
if (!value || typeof value !== "object") return void 0;
|
|
130
|
+
const candidate = value;
|
|
131
|
+
if (candidate.currency !== "usd" || candidate.interval !== "month") return void 0;
|
|
132
|
+
const period = (input, allowZeroPrice) => {
|
|
133
|
+
if (!input || typeof input !== "object") return void 0;
|
|
134
|
+
const data = input;
|
|
135
|
+
const priceCents = data.priceCents;
|
|
136
|
+
const includedCreditsCents = data.includedCreditsCents;
|
|
137
|
+
if (typeof priceCents !== "number" || !Number.isSafeInteger(priceCents) || (allowZeroPrice ? priceCents < 0 : priceCents <= 0) || typeof includedCreditsCents !== "number" || !Number.isSafeInteger(includedCreditsCents) || includedCreditsCents < 0) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
return { priceCents, includedCreditsCents };
|
|
141
|
+
};
|
|
142
|
+
const recurring = period(candidate.recurring, false);
|
|
143
|
+
const introductory = candidate.introductory === null ? null : period(candidate.introductory, true);
|
|
144
|
+
if (!recurring || introductory === void 0) return void 0;
|
|
145
|
+
return {
|
|
146
|
+
currency: "usd",
|
|
147
|
+
interval: "month",
|
|
148
|
+
recurring,
|
|
149
|
+
introductory
|
|
150
|
+
};
|
|
151
|
+
}
|
|
128
152
|
function createPlatformBillingHttp(opts) {
|
|
129
153
|
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
130
154
|
if (!baseUrl) throw new Error("PlatformBillingHttpOptions.baseUrl is required");
|
|
@@ -180,13 +204,15 @@ function createPlatformBillingHttp(opts) {
|
|
|
180
204
|
const body = await userRead(userApiKey, `/v1/billing/product-entitlement?product=${slug}`);
|
|
181
205
|
const data = body.data ?? {};
|
|
182
206
|
const hasSeat = data.hasSeat === true;
|
|
207
|
+
const offer = productSeatOffer(data.offer);
|
|
183
208
|
return {
|
|
184
209
|
seatStatus: data.seatStatus ?? "none",
|
|
185
210
|
currentPeriodEnd: data.currentPeriodEnd ?? null,
|
|
186
211
|
lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,
|
|
187
212
|
hasSeat,
|
|
188
213
|
// Free access only when there is no seat AND the platform says so.
|
|
189
|
-
onFreeTier: !hasSeat && data.onFreeTier === true
|
|
214
|
+
onFreeTier: !hasSeat && data.onFreeTier === true,
|
|
215
|
+
...offer ? { offer } : {}
|
|
190
216
|
};
|
|
191
217
|
},
|
|
192
218
|
async deduct(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/platform/hub.ts","../../src/platform/billing.ts"],"sourcesContent":["/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nimport {\n resolveTangleDevOrUserKey,\n type TangleExecutionEnvironment,\n type TangleExecutionKeySource,\n} from '../runtime/model'\n\n/** Hub bearer provenance mirrors the execution-key source union. */\nexport type TangleHubBearerSource = TangleExecutionKeySource\n\n/** Represent a resolved bearer token with its associated TangleHub bearer source */\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\n/** Resolve options required to obtain a user's TangleHub bearer token including environment and API key retrieval */\nexport interface ResolveUserTangleHubBearerOptions {\n userId: string\n /** Deployment context. Only local development may use env credentials. */\n environment?: TangleExecutionEnvironment\n /** Env to read for the local-development bearer. */\n env?: Record<string, string | undefined>\n /** App-owned lookup for the caller's linked platform API key. */\n getUserApiKey: () => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Resolve options for retrieving a TangleHub bearer token for a specified user */\nexport interface ResolveUserTangleHubBearerForUserOptions<UserId = string> {\n userId: UserId\n environment?: TangleExecutionEnvironment\n env?: Record<string, string | undefined>\n getUserApiKey: (userId: UserId) => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Represent missing Tangle platform link error for a specified user ID */\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/**\n * Resolve the Tangle bearer used by the integration hub proxy.\n *\n * Local development may use a server env key so apps can exercise the hub\n * without completing cross-site SSO. Deployed contexts must use the caller's\n * linked platform key; this keeps integration ownership aligned with the user.\n */\nexport async function resolveUserTangleHubBearer(\n opts: ResolveUserTangleHubBearerOptions,\n): Promise<ResolvedTangleHubBearer> {\n const resolved = await resolveTangleDevOrUserKey({\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: opts.getUserApiKey,\n })\n if (resolved) return { bearer: resolved.apiKey, source: resolved.source }\n\n throw new TangleBearerMissingError(opts.userId)\n}\n\n/** Resolve the TangleHub bearer token for a specified user based on provided options */\nexport async function resolveUserTangleHubBearerForUser<UserId = string>(\n opts: ResolveUserTangleHubBearerForUserOptions<UserId>,\n): Promise<ResolvedTangleHubBearer> {\n return resolveUserTangleHubBearer({\n userId: String(opts.userId),\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: () => opts.getUserApiKey(opts.userId),\n })\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\n/** Define methods to require user ID, get bearer token, and create a hub client bound to the bearer */\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\n/** Define arguments for configuring a proxy route with request and optional parameters */\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\n/** Define routes for hub proxy handling catalog, connections, healthchecks, and authorization actions */\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\n/** Resolve hub proxy routes with authentication and error handling based on the given context */\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\n\n/** Define available subscription tiers for the TanglePlan service */\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\n/** Represent platform billing HTTP errors with status code and detailed message */\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Define HTTP options for platform billing including base URL, service token, product slug, fetch implementation, and timeout */\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\n/** Describe subscription tier and status information for a platform user */\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\n/** Describe the platform balance and lifetime spending with an optional update timestamp */\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\n/** Describe a product's usage and spending metrics on the platform */\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\n/** Lifecycle of a per-product seat subscription, mirroring the Stripe states\n * the platform persists. 'none' = the user has never held this seat. */\nexport type SeatStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'canceled'\n\n/**\n * Per-product entitlement snapshot from the platform — the single read that\n * tells a product whether to show its workspace or the seat paywall. Shape\n * matches `GET /v1/billing/product-entitlement?product=<id>`.\n *\n * `hasSeat` and `onFreeTier` are computed platform-side from the raw seat row\n * + cumulative spend so the gate is identical across all five products:\n * - `hasSeat` — an active/trialing seat whose period has not lapsed.\n * - `onFreeTier` — no active seat AND cumulative spend below the free cap\n * ($2 / 200¢ lifetime). Keys off lifetime spend, not wallet\n * balance, so a router top-up never re-opens free access.\n */\nexport interface ProductEntitlement {\n seatStatus: SeatStatus\n /** ISO timestamp the active seat's paid period runs until; null when none. */\n currentPeriodEnd: string | null\n /** Cumulative inference spend across the whole suite, in dollars. */\n lifetimeSpentUsd: number\n hasSeat: boolean\n onFreeTier: boolean\n}\n\n/** Define methods to interact with platform billing endpoints using user or service authentication */\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** GET /v1/billing/product-entitlement?product=<id> (user bearer). */\n getProductEntitlement(userApiKey: string, productId: string): Promise<ProductEntitlement>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n /** Absolute URL of the $100/mo seat checkout for `productId`. */\n seatCheckoutUrl(productId: string): string\n}\n\n/** Create a PlatformBillingHttp instance configured with given options and default behaviors */\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async getProductEntitlement(userApiKey, productId) {\n const slug = encodeURIComponent(productId)\n const body = await userRead<{\n success: boolean\n data?: {\n seatStatus?: SeatStatus | null\n currentPeriodEnd?: string | null\n lifetimeSpentUsd?: number | null\n hasSeat?: boolean | null\n onFreeTier?: boolean | null\n }\n }>(userApiKey, `/v1/billing/product-entitlement?product=${slug}`)\n const data = body.data ?? {}\n const hasSeat = data.hasSeat === true\n return {\n seatStatus: data.seatStatus ?? 'none',\n currentPeriodEnd: data.currentPeriodEnd ?? null,\n lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,\n hasSeat,\n // Free access only when there is no seat AND the platform says so.\n onFreeTier: !hasSeat && data.onFreeTier === true,\n }\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n\n seatCheckoutUrl(productId) {\n return seatCheckoutUrl(baseUrl, productId)\n },\n }\n}\n\n/**\n * Platform Stripe checkout URL for a product's $100/mo seat. One shared price\n * carries `metadata.productId`; the platform distinguishes the product from\n * the `product` query param (not five distinct prices). Mirrors the\n * `billingUrl()` shape — a deterministic platform-rooted URL, no network call.\n */\nexport function seatCheckoutUrl(baseUrl: string, productId: string): string {\n const root = baseUrl.replace(/\\/+$/, '')\n return `${root}/app/billing/seat/checkout?product=${encodeURIComponent(productId)}`\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\n/** Define policy settings for concurrency and overage allowance in a tangle tier */\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\n/** Define default concurrency and overage policies for each TanglePlanTier level */\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\n/** Describe the state of a Tangle plan tier including subscription, balance, spending, and concurrency details */\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Per-product seat entitlement ────────────────────────────────────────────\n\n/** Lifetime free-tier cap: $2 (200¢) cumulative inference spend, expressed in\n * dollars. Free product access ends once cumulative spend crosses this. */\nexport const FREE_TIER_SPEND_CAP_USD = 2\n\n/**\n * Default name of the per-app feature flag gating seat billing. While OFF the\n * entitlement read is skipped and access fails OPEN (entitled) so nothing\n * changes live until a product flips the flag.\n */\nexport const DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = 'SEAT_BILLING_ENABLED'\n\n/** Define options to configure seat billing flag environment variables and override flag name */\nexport interface SeatBillingFlagOptions {\n env?: Record<string, string | undefined>\n /** Override the flag name; default {@link DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR}. */\n flagEnvVar?: string\n}\n\n/**\n * Seat billing is OFF unless the flag is explicitly truthy ('true'/'1'/'on'/\n * 'enabled'). Default OFF — pre-rollout, the paywall never engages. Returns\n * false when no env is available (browser bundles) so the client stays\n * fail-open there too.\n */\nexport function isSeatBillingEnabled(opts: SeatBillingFlagOptions = {}): boolean {\n const env =\n opts.env ??\n (typeof process !== 'undefined' ? (process.env as Record<string, string | undefined>) : undefined)\n if (!env) return false\n const flag = env[opts.flagEnvVar ?? DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR]?.trim().toLowerCase()\n return flag === 'true' || flag === '1' || flag === 'on' || flag === 'enabled'\n}\n\n/**\n * Read a user's entitlement for one product. Fails OPEN: an absent key,\n * disabled flag, or unreachable seat endpoint all return a permissive snapshot\n * (`hasSeat: true`) so consumers never break pre-rollout. The platform owns the\n * `hasSeat`/`onFreeTier` computation; this client only transports + degrades\n * safely.\n *\n * @param flag — pass {@link isSeatBillingEnabled} (or your own boolean) so the\n * product owns when the gate engages. When false, no network call is made.\n */\nexport async function getProductEntitlement(\n http: Pick<PlatformBillingHttp, 'getProductEntitlement'>,\n userApiKey: string | null | undefined,\n productId: string,\n flag = true,\n): Promise<ProductEntitlement> {\n if (!flag || !userApiKey) return failOpenEntitlement()\n try {\n return await http.getProductEntitlement(userApiKey, productId)\n } catch {\n // Seat endpoint unavailable (pre-rollout platform, transient 5xx): never\n // wall a paying or grandfathered user on a transport hiccup.\n return failOpenEntitlement()\n }\n}\n\nfunction failOpenEntitlement(): ProductEntitlement {\n return {\n seatStatus: 'active',\n currentPeriodEnd: null,\n lifetimeSpentUsd: 0,\n hasSeat: true,\n onFreeTier: false,\n }\n}\n\n/** Entitled = holds an active seat OR is still inside the free tier. The one\n * predicate all five products gate on. */\nexport function isProductEntitled(ent: ProductEntitlement): boolean {\n return ent.hasSeat || ent.onFreeTier\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\n/** Define a contract for resolving platform identities based on user identifiers */\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/platform/hub.ts","../../src/platform/billing.ts"],"sourcesContent":["/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nimport {\n resolveTangleDevOrUserKey,\n type TangleExecutionEnvironment,\n type TangleExecutionKeySource,\n} from '../runtime/model'\n\n/** Hub bearer provenance mirrors the execution-key source union. */\nexport type TangleHubBearerSource = TangleExecutionKeySource\n\n/** Represent a resolved bearer token with its associated TangleHub bearer source */\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\n/** Resolve options required to obtain a user's TangleHub bearer token including environment and API key retrieval */\nexport interface ResolveUserTangleHubBearerOptions {\n userId: string\n /** Deployment context. Only local development may use env credentials. */\n environment?: TangleExecutionEnvironment\n /** Env to read for the local-development bearer. */\n env?: Record<string, string | undefined>\n /** App-owned lookup for the caller's linked platform API key. */\n getUserApiKey: () => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Resolve options for retrieving a TangleHub bearer token for a specified user */\nexport interface ResolveUserTangleHubBearerForUserOptions<UserId = string> {\n userId: UserId\n environment?: TangleExecutionEnvironment\n env?: Record<string, string | undefined>\n getUserApiKey: (userId: UserId) => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Represent missing Tangle platform link error for a specified user ID */\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/**\n * Resolve the Tangle bearer used by the integration hub proxy.\n *\n * Local development may use a server env key so apps can exercise the hub\n * without completing cross-site SSO. Deployed contexts must use the caller's\n * linked platform key; this keeps integration ownership aligned with the user.\n */\nexport async function resolveUserTangleHubBearer(\n opts: ResolveUserTangleHubBearerOptions,\n): Promise<ResolvedTangleHubBearer> {\n const resolved = await resolveTangleDevOrUserKey({\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: opts.getUserApiKey,\n })\n if (resolved) return { bearer: resolved.apiKey, source: resolved.source }\n\n throw new TangleBearerMissingError(opts.userId)\n}\n\n/** Resolve the TangleHub bearer token for a specified user based on provided options */\nexport async function resolveUserTangleHubBearerForUser<UserId = string>(\n opts: ResolveUserTangleHubBearerForUserOptions<UserId>,\n): Promise<ResolvedTangleHubBearer> {\n return resolveUserTangleHubBearer({\n userId: String(opts.userId),\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: () => opts.getUserApiKey(opts.userId),\n })\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\n/** Define methods to require user ID, get bearer token, and create a hub client bound to the bearer */\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\n/** Define arguments for configuring a proxy route with request and optional parameters */\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\n/** Define routes for hub proxy handling catalog, connections, healthchecks, and authorization actions */\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\n/** Resolve hub proxy routes with authentication and error handling based on the given context */\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\n\n/** Define available subscription tiers for the TanglePlan service */\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\n/** Represent platform billing HTTP errors with status code and detailed message */\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Define HTTP options for platform billing including base URL, service token, product slug, fetch implementation, and timeout */\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\n/** Describe subscription tier and status information for a platform user */\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\n/** Describe the platform balance and lifetime spending with an optional update timestamp */\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\n/** Describe a product's usage and spending metrics on the platform */\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\n/** Lifecycle of a per-product seat subscription, mirroring the Stripe states\n * the platform persists. 'none' = the user has never held this seat. */\nexport type SeatStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'canceled'\n\n/** Price and included shared-wallet credit for one seat billing period. */\nexport interface ProductSeatOfferPeriod {\n priceCents: number\n includedCreditsCents: number\n}\n\n/** Commercial terms returned by the platform's product catalog. Products use\n * this exact object for display instead of duplicating prices in UI copy. */\nexport interface ProductSeatOffer {\n currency: 'usd'\n interval: 'month'\n recurring: ProductSeatOfferPeriod\n introductory: ProductSeatOfferPeriod | null\n}\n\n/**\n * Per-product entitlement snapshot from the platform — the single read that\n * tells a product whether to show its workspace or the seat paywall. Shape\n * matches `GET /v1/billing/product-entitlement?product=<id>`.\n *\n * `hasSeat` and `onFreeTier` are computed platform-side from the raw seat row\n * + cumulative spend so the access rule is identical across products:\n * - `hasSeat` — an active/trialing seat whose period has not lapsed.\n * - `onFreeTier` — no active seat AND cumulative spend below the free cap\n * ($2 / 200¢ lifetime). Keys off lifetime spend, not wallet\n * balance, so a router top-up never re-opens free access.\n */\nexport interface ProductEntitlement {\n seatStatus: SeatStatus\n /** ISO timestamp the active seat's paid period runs until; null when none. */\n currentPeriodEnd: string | null\n /** Cumulative inference spend across the whole suite, in dollars. */\n lifetimeSpentUsd: number\n hasSeat: boolean\n onFreeTier: boolean\n /** Present when the platform exposes catalog-backed commercial terms. */\n offer?: ProductSeatOffer\n}\n\nfunction productSeatOffer(value: unknown): ProductSeatOffer | undefined {\n if (!value || typeof value !== 'object') return undefined\n const candidate = value as Record<string, unknown>\n if (candidate.currency !== 'usd' || candidate.interval !== 'month') return undefined\n\n const period = (input: unknown, allowZeroPrice: boolean): ProductSeatOfferPeriod | undefined => {\n if (!input || typeof input !== 'object') return undefined\n const data = input as Record<string, unknown>\n const priceCents = data.priceCents\n const includedCreditsCents = data.includedCreditsCents\n if (\n typeof priceCents !== 'number' ||\n !Number.isSafeInteger(priceCents) ||\n (allowZeroPrice ? priceCents < 0 : priceCents <= 0) ||\n typeof includedCreditsCents !== 'number' ||\n !Number.isSafeInteger(includedCreditsCents) ||\n includedCreditsCents < 0\n ) {\n return undefined\n }\n return { priceCents, includedCreditsCents }\n }\n\n const recurring = period(candidate.recurring, false)\n const introductory =\n candidate.introductory === null\n ? null\n : period(candidate.introductory, true)\n if (!recurring || introductory === undefined) return undefined\n return {\n currency: 'usd',\n interval: 'month',\n recurring,\n introductory,\n }\n}\n\n/** Define methods to interact with platform billing endpoints using user or service authentication */\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** GET /v1/billing/product-entitlement?product=<id> (user bearer). */\n getProductEntitlement(userApiKey: string, productId: string): Promise<ProductEntitlement>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n /** Absolute URL of the catalog-backed seat checkout for `productId`. */\n seatCheckoutUrl(productId: string): string\n}\n\n/** Create a PlatformBillingHttp instance configured with given options and default behaviors */\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async getProductEntitlement(userApiKey, productId) {\n const slug = encodeURIComponent(productId)\n const body = await userRead<{\n success: boolean\n data?: {\n seatStatus?: SeatStatus | null\n currentPeriodEnd?: string | null\n lifetimeSpentUsd?: number | null\n hasSeat?: boolean | null\n onFreeTier?: boolean | null\n offer?: unknown\n }\n }>(userApiKey, `/v1/billing/product-entitlement?product=${slug}`)\n const data = body.data ?? {}\n const hasSeat = data.hasSeat === true\n const offer = productSeatOffer(data.offer)\n return {\n seatStatus: data.seatStatus ?? 'none',\n currentPeriodEnd: data.currentPeriodEnd ?? null,\n lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,\n hasSeat,\n // Free access only when there is no seat AND the platform says so.\n onFreeTier: !hasSeat && data.onFreeTier === true,\n ...(offer ? { offer } : {}),\n }\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n\n seatCheckoutUrl(productId) {\n return seatCheckoutUrl(baseUrl, productId)\n },\n }\n}\n\n/**\n * Platform Stripe checkout URL for a product's catalog-backed seat. The\n * platform resolves the product-specific price and introductory offer from the\n * `product` query param. Mirrors the `billingUrl()` shape — a deterministic\n * platform-rooted URL with no client-side pricing decisions.\n */\nexport function seatCheckoutUrl(baseUrl: string, productId: string): string {\n const root = baseUrl.replace(/\\/+$/, '')\n return `${root}/app/billing/seat/checkout?product=${encodeURIComponent(productId)}`\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\n/** Define policy settings for concurrency and overage allowance in a tangle tier */\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\n/** Define default concurrency and overage policies for each TanglePlanTier level */\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\n/** Describe the state of a Tangle plan tier including subscription, balance, spending, and concurrency details */\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Per-product seat entitlement ────────────────────────────────────────────\n\n/** Lifetime free-tier cap: $2 (200¢) cumulative inference spend, expressed in\n * dollars. Free product access ends once cumulative spend crosses this. */\nexport const FREE_TIER_SPEND_CAP_USD = 2\n\n/**\n * Default name of the per-app feature flag gating seat billing. While OFF the\n * entitlement read is skipped and access fails OPEN (entitled) so nothing\n * changes live until a product flips the flag.\n */\nexport const DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = 'SEAT_BILLING_ENABLED'\n\n/** Define options to configure seat billing flag environment variables and override flag name */\nexport interface SeatBillingFlagOptions {\n env?: Record<string, string | undefined>\n /** Override the flag name; default {@link DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR}. */\n flagEnvVar?: string\n}\n\n/**\n * Seat billing is OFF unless the flag is explicitly truthy ('true'/'1'/'on'/\n * 'enabled'). Default OFF — pre-rollout, the paywall never engages. Returns\n * false when no env is available (browser bundles) so the client stays\n * fail-open there too.\n */\nexport function isSeatBillingEnabled(opts: SeatBillingFlagOptions = {}): boolean {\n const env =\n opts.env ??\n (typeof process !== 'undefined' ? (process.env as Record<string, string | undefined>) : undefined)\n if (!env) return false\n const flag = env[opts.flagEnvVar ?? DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR]?.trim().toLowerCase()\n return flag === 'true' || flag === '1' || flag === 'on' || flag === 'enabled'\n}\n\n/**\n * Read a user's entitlement for one product. Fails OPEN: an absent key,\n * disabled flag, or unreachable seat endpoint all return a permissive snapshot\n * (`hasSeat: true`) so consumers never break pre-rollout. The platform owns the\n * `hasSeat`/`onFreeTier` computation; this client only transports + degrades\n * safely.\n *\n * @param flag — pass {@link isSeatBillingEnabled} (or your own boolean) so the\n * product owns when the gate engages. When false, no network call is made.\n */\nexport async function getProductEntitlement(\n http: Pick<PlatformBillingHttp, 'getProductEntitlement'>,\n userApiKey: string | null | undefined,\n productId: string,\n flag = true,\n): Promise<ProductEntitlement> {\n if (!flag || !userApiKey) return failOpenEntitlement()\n try {\n return await http.getProductEntitlement(userApiKey, productId)\n } catch {\n // Seat endpoint unavailable (pre-rollout platform, transient 5xx): never\n // wall a paying or grandfathered user on a transport hiccup.\n return failOpenEntitlement()\n }\n}\n\nfunction failOpenEntitlement(): ProductEntitlement {\n return {\n seatStatus: 'active',\n currentPeriodEnd: null,\n lifetimeSpentUsd: 0,\n hasSeat: true,\n onFreeTier: false,\n }\n}\n\n/** Entitled = holds an active seat OR is still inside the free tier. The one\n * predicate every product uses. */\nexport function isProductEntitled(ent: ProductEntitlement): boolean {\n return ent.hasSeat || ent.onFreeTier\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\n/** Define a contract for resolving platform identities based on user identifiers */\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,10 +16,12 @@ import { F as FlowTrace } from '../flow-types-CJxEmaRy.js';
|
|
|
16
16
|
import { a as ReviewQueueItem, c as ReviewQueueState } from '../queue-VTBA5ONX.js';
|
|
17
17
|
export { p as parseReviewQueueItem } from '../queue-VTBA5ONX.js';
|
|
18
18
|
export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-ChNEdHF8.js';
|
|
19
|
+
import { i as ProductSeatOffer } from '../billing-BibxgALe.js';
|
|
19
20
|
import { CatalogModel } from '../catalog/index.js';
|
|
20
21
|
import { Harness } from '../harness/index.js';
|
|
21
22
|
export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-CNkH91Gs.js';
|
|
22
23
|
export { a as attachmentPartKey } from '../stream-normalizer-CnPnMaTp.js';
|
|
24
|
+
import '../billing/index.js';
|
|
23
25
|
|
|
24
26
|
/** Represent durable plan decisions as either approved or rejected */
|
|
25
27
|
type DurablePlanDecision = 'approved' | 'rejected';
|
|
@@ -1045,9 +1047,7 @@ declare function ProvenanceStamp({ provenance, backtest, className }: Provenance
|
|
|
1045
1047
|
|
|
1046
1048
|
/**
|
|
1047
1049
|
* `SeatPaywall` — the shared "unlock this product" screen every agent app
|
|
1048
|
-
* shows when a user has no active seat and has spent past the free tier.
|
|
1049
|
-
* component, adopted by all five products (gtm / creative / tax / legal /
|
|
1050
|
-
* insurance) in ~2 lines.
|
|
1050
|
+
* shows when a user has no active seat and has spent past the free tier.
|
|
1051
1051
|
*
|
|
1052
1052
|
* Copy contract (design §6.8): the included monthly AI usage is framed as a
|
|
1053
1053
|
* BENEFIT the buyer receives — never the ratio, never the word "margin", never
|
|
@@ -1069,6 +1069,9 @@ interface SeatPaywallProps {
|
|
|
1069
1069
|
priceUsd?: number;
|
|
1070
1070
|
/** Included monthly AI usage in whole dollars. Default 50. */
|
|
1071
1071
|
includedUsageUsd?: number;
|
|
1072
|
+
/** Platform catalog terms. When present, these override the legacy dollar
|
|
1073
|
+
* props and show any introductory period without product-local price copy. */
|
|
1074
|
+
offer?: ProductSeatOffer;
|
|
1072
1075
|
/** Optional one-line value prop under the headline. */
|
|
1073
1076
|
tagline?: string;
|
|
1074
1077
|
/** CTA label. Default "Unlock {product}". */
|
|
@@ -1084,7 +1087,7 @@ interface SeatPaywallProps {
|
|
|
1084
1087
|
* "$100/mo · includes $50/mo of AI usage" so the included allowance anchors the
|
|
1085
1088
|
* value without ever exposing the ratio.
|
|
1086
1089
|
*/
|
|
1087
|
-
declare function SeatPaywall({ product, onCheckout, priceUsd, includedUsageUsd, tagline, ctaLabel, benefits, footnote, }: SeatPaywallProps): ReactNode;
|
|
1090
|
+
declare function SeatPaywall({ product, onCheckout, priceUsd, includedUsageUsd, offer, tagline, ctaLabel, benefits, footnote, }: SeatPaywallProps): ReactNode;
|
|
1088
1091
|
|
|
1089
1092
|
/**
|
|
1090
1093
|
* Keyboard + pointer model for a trigger-and-popover pair, dependency-free.
|
package/dist/web-react/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.33",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|