@tangle-network/agent-app 0.43.25 → 0.43.27
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/bin/preflight.mjs +47 -0
- package/dist/app-auth/index.d.ts +1 -1
- package/dist/app-auth/index.js +1 -1
- package/dist/assistant/index.d.ts +1 -0
- package/dist/assistant/index.js +2 -1
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-routes/index.d.ts +276 -0
- package/dist/chat-routes/index.js +564 -0
- package/dist/chat-routes/index.js.map +1 -0
- package/dist/chat-store/index.d.ts +3 -2
- package/dist/chat-store/index.js +7 -3
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-4H77LX3V.js → chunk-5EQCITY3.js} +2 -20
- package/dist/chunk-5EQCITY3.js.map +1 -0
- package/dist/chunk-5SV5PSU7.js +80 -0
- package/dist/chunk-5SV5PSU7.js.map +1 -0
- package/dist/chunk-7LNGJDNA.js +1 -0
- package/dist/chunk-7LNGJDNA.js.map +1 -0
- package/dist/{chunk-77RLNLFP.js → chunk-ATRJULKZ.js} +41 -5
- package/dist/chunk-ATRJULKZ.js.map +1 -0
- package/dist/{chunk-PEPXQTJ3.js → chunk-FCQP75JT.js} +16 -5
- package/dist/chunk-FCQP75JT.js.map +1 -0
- package/dist/chunk-I2R2XT4M.js +62 -0
- package/dist/chunk-I2R2XT4M.js.map +1 -0
- package/dist/chunk-NYATNLRK.js +99 -0
- package/dist/chunk-NYATNLRK.js.map +1 -0
- package/dist/chunk-Q4TKVF3L.js +240 -0
- package/dist/chunk-Q4TKVF3L.js.map +1 -0
- package/dist/{chunk-AVBANQ67.js → chunk-TKVJE63N.js} +10 -1
- package/dist/{chunk-AVBANQ67.js.map → chunk-TKVJE63N.js.map} +1 -1
- package/dist/{chunk-U7DLCPJ6.js → chunk-Y4QHNQ75.js} +1 -1
- package/dist/core-7qIM7svy.d.ts +21 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +125 -104
- package/dist/interactions/index.js +2 -1
- package/dist/{parts-BeRnK54I.d.ts → parts-BcbitSNp.d.ts} +12 -21
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +3 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/preflight/index.d.ts +141 -0
- package/dist/preflight/index.js +15 -0
- package/dist/preflight/index.js.map +1 -0
- package/dist/{sso-Df4wtL8D.d.ts → sso-CNOsARMJ.d.ts} +16 -1
- package/dist/stream/index.js +1 -1
- package/dist/teams-react/index.js +3 -3
- package/dist/theme-contract/cli.d.ts +1 -0
- package/dist/theme-contract/cli.js +79 -0
- package/dist/theme-contract/cli.js.map +1 -0
- package/dist/theme-contract/index.d.ts +90 -0
- package/dist/theme-contract/index.js +7 -0
- package/dist/theme-contract/index.js.map +1 -0
- package/dist/web-react/index.d.ts +24 -4
- package/dist/web-react/index.js +5 -1
- package/dist/wire-BaUF66AS.d.ts +61 -0
- package/package.json +20 -1
- package/dist/chunk-4H77LX3V.js.map +0 -1
- package/dist/chunk-77RLNLFP.js.map +0 -1
- package/dist/chunk-PEPXQTJ3.js.map +0 -1
- /package/dist/{chunk-U7DLCPJ6.js.map → chunk-Y4QHNQ75.js.map} +0 -0
|
@@ -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\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\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\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\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\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\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\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\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\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\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\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\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\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\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\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\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\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\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\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\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\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\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\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\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\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\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\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\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\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\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\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\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\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\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\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\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\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\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\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\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\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\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":";;;;;;;;;;;;;;;;;;;AAuCO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YAAqB,QAAgB;AACnC,UAAM,oCAAoC,MAAM,EAAE;AAD/B;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AASA,eAAsB,2BACpB,MACkC;AAClC,QAAM,WAAW,MAAM,0BAA0B;AAAA,IAC/C,aAAa,KAAK;AAAA,IAClB,KAAK,KAAK;AAAA,IACV,eAAe,KAAK;AAAA,EACtB,CAAC;AACD,MAAI,SAAU,QAAO,EAAE,QAAQ,SAAS,QAAQ,QAAQ,SAAS,OAAO;AAExE,QAAM,IAAI,yBAAyB,KAAK,MAAM;AAChD;AAEA,eAAsB,kCACpB,MACkC;AAClC,SAAO,2BAA2B;AAAA,IAChC,QAAQ,OAAO,KAAK,MAAM;AAAA,IAC1B,aAAa,KAAK;AAAA,IAClB,KAAK,KAAK;AAAA,IACV,eAAe,MAAM,KAAK,cAAc,KAAK,MAAM;AAAA,EACrD,CAAC;AACH;AAIO,SAAS,2BAA2B,OAAmD;AAC5F,SACE,iBAAiB,SACjB,MAAM,SAAS,8BACf,OAAQ,MAA+B,WAAW;AAEtD;AAGO,SAAS,uBAAuB,OAAoE;AACzG,SACE,iBAAiB,SACjB,MAAM,SAAS,sBACf,OAAQ,MAA+B,WAAW;AAEtD;AAoDO,SAAS,qBAAqB,KAAsC;AAGzE,iBAAe,MAAM,SAAkB,MAAoE;AACzG,UAAM,SAAS,MAAM,IAAI,cAAc,OAAO;AAC9C,QAAI;AACF,YAAM,SAAS,MAAM,IAAI,UAAU,MAAM;AACzC,aAAO,MAAM,KAAK,IAAI,gBAAgB,MAAM,CAAC;AAAA,IAC/C,SAAS,KAAK;AACZ,UAAI,2BAA2B,GAAG,GAAG;AACnC,eAAO,SAAS,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACzE;AACA,UAAI,uBAAuB,GAAG,GAAG;AAC/B,eAAO,SAAS,KAAK,EAAE,OAAO,IAAI,SAAS,MAAM,IAAI,KAAK,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,MACrF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,QAAQ,MAAM,MAAM,SAAS,OAAO,QAAQ,SAAS,KAAK,EAAE,SAAS,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC;AAAA,IAEvG,aAAa,CAAC,EAAE,QAAQ,MACtB,MAAM,SAAS,OAAO,QAAQ,SAAS,KAAK,EAAE,aAAa,MAAM,IAAI,gBAAgB,EAAE,CAAC,CAAC;AAAA,IAE3F,kBAAkB,OAAO,EAAE,SAAS,OAAO,MAAM;AAC/C,UAAI,QAAQ,WAAW,UAAU;AAC/B,eAAO,SAAS,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACvE;AACA,aAAO,MAAM,SAAS,OAAO,QAAQ,SAAS,KAAK,MAAM,IAAI,iBAAiB,OAAO,YAAY,CAAC,CAAC;AAAA,IACrG;AAAA,IAEA,cAAc,CAAC,EAAE,QAAQ,MACvB,MAAM,SAAS,OAAO,QAAQ,SAAS,KAAK,EAAE,cAAc,MAAM,IAAI,iBAAiB,EAAE,CAAC,CAAC;AAAA,IAE7F,WAAW,OAAO,EAAE,QAAQ,MAAM;AAChC,UAAI,QAAQ,WAAW,QAAQ;AAC7B,eAAO,SAAS,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACvE;AACA,YAAM,SAAS,MAAM,IAAI,cAAc,OAAO;AAC9C,UAAI;AACJ,UAAI;AACF,eAAQ,MAAM,QAAQ,KAAK;AAAA,MAC7B,QAAQ;AACN,eAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACtE;AACA,UAAI,CAAC,KAAK,cAAc,CAAC,KAAK,eAAe,CAAC,KAAK,WAAW;AAC5D,eAAO,SAAS,KAAK,EAAE,OAAO,sDAAsD,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACxG;AACA,UAAI;AACF,cAAM,SAAS,MAAM,IAAI,UAAU,MAAM;AACzC,cAAM,SAAS,MAAM,IAAI,gBAAgB,MAAM,EAAE,UAAU;AAAA,UACzD,YAAY,KAAK;AAAA,UACjB,aAAa,KAAK;AAAA,UAClB,WAAW,KAAK;AAAA,UAChB,iBAAiB,KAAK;AAAA,QACxB,CAAC;AACD,eAAO,SAAS,KAAK,EAAE,kBAAkB,OAAO,kBAAkB,OAAO,OAAO,MAAM,CAAC;AAAA,MACzF,SAAS,KAAK;AACZ,YAAI,2BAA2B,GAAG,GAAG;AACnC,iBAAO,SAAS,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,QACzE;AACA,YAAI,uBAAuB,GAAG,GAAG;AAC/B,iBAAO,SAAS,KAAK,EAAE,OAAO,IAAI,SAAS,MAAM,IAAI,KAAK,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,QACrF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACvMO,SAAS,wBAAwB,MAAiD;AACvF,SAAO,SAAS,SAAS,SAAS,eAAe,OAAO;AAC1D;AAEO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YACW,QACT,QACA;AACA,UAAM,4BAA4B,MAAM,MAAM,MAAM,EAAE;AAH7C;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAMb;AAGO,SAAS,2BAA2B,OAAmD;AAC5F,SACE,iBAAiB,SACjB,MAAM,SAAS,8BACf,OAAQ,MAA+B,WAAW;AAEtD;AAiFO,SAAS,0BAA0B,MAAuD;AAC/F,QAAM,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC/C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,gDAAgD;AAC9E,MAAI,CAAC,KAAK,YAAa,OAAM,IAAI,MAAM,oDAAoD;AAC3F,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,YAAY,KAAK,aAAa;AAEpC,WAAS,sBAA8B;AACrC,UAAM,QAAQ,OAAO,KAAK,iBAAiB,aAAa,KAAK,aAAa,IAAI,KAAK;AACnF,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iDAAiD;AAC7E,WAAO;AAAA,EACT;AAEA,iBAAe,QAAW,MAAc,MAAmB,SAA8B;AACvF,UAAM,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,MAC/C,GAAG;AAAA,MACH;AAAA,MACA,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC/C,YAAM,IAAI,yBAAyB,IAAI,QAAQ,MAAM,OAAO,WAAW,IAAI,UAAU;AAAA,IACvF;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAEA,WAAS,SAAY,YAAoB,MAA0B;AACjE,UAAM,UAAU,IAAI,QAAQ;AAC5B,YAAQ,IAAI,iBAAiB,UAAU,UAAU,EAAE;AACnD,WAAO,QAAW,MAAM,CAAC,GAAG,OAAO;AAAA,EACrC;AAEA,SAAO;AAAA,IACL,MAAM,gBAAgB,YAAY;AAChC,YAAM,OAAO,MAAM,SAGhB,YAAY,mBAAmB;AAClC,YAAM,MAAM,KAAK,MAAM,gBAAgB;AACvC,aAAO,EAAE,MAAM,wBAAwB,KAAK,IAAI,GAAG,QAAQ,KAAK,UAAU,KAAK;AAAA,IACjF;AAAA,IAEA,MAAM,WAAW,YAAY;AAC3B,YAAM,OAAO,MAAM,SAGhB,YAAY,qBAAqB;AACpC,aAAO;AAAA,QACL,SAAS,KAAK,MAAM,WAAW;AAAA,QAC/B,eAAe,KAAK,MAAM,iBAAiB;AAAA,QAC3C,WAAW,KAAK,MAAM;AAAA,MACxB;AAAA,IACF;AAAA,IAEA,MAAM,kBAAkB,YAAY;AAClC,YAAM,OAAO,MAAM,SAGhB,YAAY,mBAAmB;AAClC,cAAQ,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS;AAAA,QACrC,SAAS,IAAI,WAAW;AAAA,QACxB,YAAY,IAAI,cAAc;AAAA,QAC9B,OAAO,IAAI,SAAS;AAAA,MACtB,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,sBAAsB,YAAY,WAAW;AACjD,YAAM,OAAO,mBAAmB,SAAS;AACzC,YAAM,OAAO,MAAM,SAShB,YAAY,2CAA2C,IAAI,EAAE;AAChE,YAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,YAAM,UAAU,KAAK,YAAY;AACjC,aAAO;AAAA,QACL,YAAY,KAAK,cAAc;AAAA,QAC/B,kBAAkB,KAAK,oBAAoB;AAAA,QAC3C,kBAAkB,KAAK,oBAAoB;AAAA,QAC3C;AAAA;AAAA,QAEA,YAAY,CAAC,WAAW,KAAK,eAAe;AAAA,MAC9C;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,OAAO;AAClB,YAAM,UAAU,IAAI,QAAQ;AAC5B,cAAQ,IAAI,iBAAiB,UAAU,oBAAoB,CAAC,EAAE;AAC9D,cAAQ,IAAI,kBAAkB,KAAK,WAAW;AAC9C,cAAQ,IAAI,gBAAgB,kBAAkB;AAC9C,YAAM,QAAQ,sBAAsB;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU;AAAA,UACnB,QAAQ,MAAM;AAAA,UACd,QAAQ,MAAM;AAAA,UACd,MAAM,MAAM;AAAA,UACZ,SAAS,KAAK;AAAA,UACd,aAAa,MAAM;AAAA,UACnB,aAAa,MAAM;AAAA,QACrB,CAAC;AAAA,MACH,GAAG,OAAO;AAAA,IACZ;AAAA,IAEA,aAAa;AACX,aAAO,GAAG,OAAO;AAAA,IACnB;AAAA,IAEA,gBAAgB,WAAW;AACzB,aAAO,gBAAgB,SAAS,SAAS;AAAA,IAC3C;AAAA,EACF;AACF;AAQO,SAAS,gBAAgB,SAAiB,WAA2B;AAC1E,QAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACvC,SAAO,GAAG,IAAI,sCAAsC,mBAAmB,SAAS,CAAC;AACnF;AASO,IAAM,6BAAuE;AAAA,EAClF,MAAM,EAAE,aAAa,GAAG,gBAAgB,MAAM;AAAA,EAC9C,KAAK,EAAE,aAAa,OAAO,mBAAmB,gBAAgB,KAAK;AAAA,EACnE,YAAY,EAAE,aAAa,OAAO,mBAAmB,gBAAgB,KAAK;AAC5E;AAiBA,eAAsB,oBACpB,MACA,YACA,SAAmD,4BACzB;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,MACL,MAAM;AAAA,MACN,oBAAoB;AAAA,MACpB,qBAAqB;AAAA,MACrB,kBAAkB;AAAA,MAClB,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AACA,QAAM,CAAC,cAAc,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,KAAK,gBAAgB,UAAU;AAAA,IAC/B,KAAK,WAAW,UAAU;AAAA,EAC5B,CAAC;AACD,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,oBAAoB,aAAa;AAAA,IACjC,qBAAqB,QAAQ;AAAA,IAC7B,kBAAkB,QAAQ;AAAA,IAC1B,GAAG,OAAO,aAAa,IAAI;AAAA,EAC7B;AACF;AAMO,IAAM,0BAA0B;AAOhC,IAAM,uCAAuC;AAc7C,SAAS,qBAAqB,OAA+B,CAAC,GAAY;AAC/E,QAAM,MACJ,KAAK,QACJ,OAAO,YAAY,cAAe,QAAQ,MAA6C;AAC1F,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,OAAO,IAAI,KAAK,cAAc,oCAAoC,GAAG,KAAK,EAAE,YAAY;AAC9F,SAAO,SAAS,UAAU,SAAS,OAAO,SAAS,QAAQ,SAAS;AACtE;AAYA,eAAsB,sBACpB,MACA,YACA,WACA,OAAO,MACsB;AAC7B,MAAI,CAAC,QAAQ,CAAC,WAAY,QAAO,oBAAoB;AACrD,MAAI;AACF,WAAO,MAAM,KAAK,sBAAsB,YAAY,SAAS;AAAA,EAC/D,QAAQ;AAGN,WAAO,oBAAoB;AAAA,EAC7B;AACF;AAEA,SAAS,sBAA0C;AACjD,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AACF;AAIO,SAAS,kBAAkB,KAAkC;AAClE,SAAO,IAAI,WAAW,IAAI;AAC5B;AAUO,SAAS,kCACd,MACA,UACuC;AACvC,SAAO;AAAA,IACL,iBAAiB,CAAC,WAAW,SAAS,gBAAgB,MAAM;AAAA,IAC5D,SAAS,OAAO,YAAY,MAAM,KAAK,gBAAgB,MAAM,GAAG;AAAA,IAChE,YAAY,OAAO,WAAW;AAC5B,YAAM,WAAW,MAAM,KAAK,WAAW,MAAM;AAC7C,aAAO,EAAE,SAAS,SAAS,SAAS,eAAe,SAAS,cAAc;AAAA,IAC5E;AAAA,IACA,mBAAmB,CAAC,WAAW,KAAK,kBAAkB,MAAM;AAAA,IAC5D,QAAQ,CAAC,UAAU,KAAK,OAAO,KAAK;AAAA,EACtC;AACF;","names":[]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/preflight` — deploy-time secret-liveness probes.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS: on 2026-07-15 four secrets were simultaneously dead in one
|
|
5
|
+
* production day — a dead `SANDBOX_API_KEY`, a stale `SANDBOX_API_URL`, and a
|
|
6
|
+
* dead LiteLLM router key + URL. Each one was present in `wrangler secret list`
|
|
7
|
+
* (so nothing looked wrong) yet invalid against its live endpoint, and nothing
|
|
8
|
+
* anywhere checked liveness. CI cannot hold production secrets, so this binds
|
|
9
|
+
* at DEPLOY time instead: a product declares a handful of probes built from its
|
|
10
|
+
* real env, the deploy workflow runs `agent-app-preflight` as a step, and a
|
|
11
|
+
* dead secret fails the deploy with a message that names exactly which secret
|
|
12
|
+
* to rotate.
|
|
13
|
+
*
|
|
14
|
+
* A probe is `{ name, run, critical? }`; `run()` returns `{ ok, detail? }`.
|
|
15
|
+
* The standard builders (`routerChatProbe`, `sandboxAuthProbe`, `httpHeadProbe`)
|
|
16
|
+
* each take explicit config — they read nothing global — so the same probe runs
|
|
17
|
+
* identically in a deploy step, a test, or a local check. `runPreflight` fans
|
|
18
|
+
* the probes out, times each, and folds them into a pass/fail report: any
|
|
19
|
+
* failed CRITICAL probe fails the whole run (probes are critical by default).
|
|
20
|
+
*
|
|
21
|
+
* Server-only: probes carry live API keys and hit live endpoints. This subpath
|
|
22
|
+
* must never reach a browser bundle.
|
|
23
|
+
*/
|
|
24
|
+
/** One probe's outcome. `detail` should name the secret to rotate on failure. */
|
|
25
|
+
interface PreflightProbeResult {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
detail?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A liveness probe. `run` performs one cheap live call and maps the result to
|
|
31
|
+
* `{ ok, detail }`. `critical` defaults to `true` — a failed critical probe
|
|
32
|
+
* fails the whole preflight (and the deploy).
|
|
33
|
+
*/
|
|
34
|
+
interface PreflightProbe {
|
|
35
|
+
name: string;
|
|
36
|
+
run: () => Promise<PreflightProbeResult>;
|
|
37
|
+
critical?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Per-probe verdict enriched with the resolved criticality and measured latency. */
|
|
40
|
+
interface PreflightProbeVerdict {
|
|
41
|
+
name: string;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
critical: boolean;
|
|
44
|
+
latencyMs: number;
|
|
45
|
+
detail?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Aggregate of every probe verdict plus the overall pass/fail decision. */
|
|
48
|
+
interface PreflightReport {
|
|
49
|
+
/** `false` if any critical probe failed. */
|
|
50
|
+
ok: boolean;
|
|
51
|
+
probes: PreflightProbeVerdict[];
|
|
52
|
+
passed: number;
|
|
53
|
+
failed: number;
|
|
54
|
+
criticalFailures: number;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
}
|
|
57
|
+
interface RouterChatProbeConfig {
|
|
58
|
+
/** LLM router base URL (LiteLLM / OpenAI-compatible), e.g. `https://router…`. */
|
|
59
|
+
baseUrl: string;
|
|
60
|
+
apiKey: string;
|
|
61
|
+
/** A cheap model id available on the router. */
|
|
62
|
+
model: string;
|
|
63
|
+
/** Probe name in the report. Default `'router-chat'`. */
|
|
64
|
+
name?: string;
|
|
65
|
+
/** Default `true`. */
|
|
66
|
+
critical?: boolean;
|
|
67
|
+
/** Env-var name of the API key, named verbatim in a dead-key failure. */
|
|
68
|
+
keySecret?: string;
|
|
69
|
+
/** Env-var name of the base URL, named verbatim in an unreachable failure. */
|
|
70
|
+
urlSecret?: string;
|
|
71
|
+
/** Per-probe deadline. Default 10s. */
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
/** Injection seam for tests; defaults to global `fetch`. */
|
|
74
|
+
fetchImpl?: typeof fetch;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Probe an OpenAI-compatible LLM router with one cheap `POST /chat/completions`
|
|
78
|
+
* (`max_tokens: 1`). 200 → live; 401/403 → dead router key; 503 → upstream
|
|
79
|
+
* provider down (key still valid); timeout / unreachable → check the router URL.
|
|
80
|
+
*/
|
|
81
|
+
declare function routerChatProbe(config: RouterChatProbeConfig): PreflightProbe;
|
|
82
|
+
interface SandboxAuthProbeConfig {
|
|
83
|
+
/** Sandbox API base URL. */
|
|
84
|
+
baseUrl: string;
|
|
85
|
+
apiKey: string;
|
|
86
|
+
/** Probe name in the report. Default `'sandbox-auth'`. */
|
|
87
|
+
name?: string;
|
|
88
|
+
/** Default `true`. */
|
|
89
|
+
critical?: boolean;
|
|
90
|
+
/** Env-var name of the API key, named verbatim in a dead-key failure. */
|
|
91
|
+
keySecret?: string;
|
|
92
|
+
/** Env-var name of the base URL, named verbatim in an unreachable failure. */
|
|
93
|
+
urlSecret?: string;
|
|
94
|
+
/** Per-probe deadline. Default 10s. */
|
|
95
|
+
timeoutMs?: number;
|
|
96
|
+
/** Injection seam for tests; defaults to global `fetch`. */
|
|
97
|
+
fetchImpl?: typeof fetch;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Probe the sandbox API with a cheap authed `GET /v1/sandboxes?limit=1`.
|
|
101
|
+
* 200 → live; 401/403 → dead sandbox key; 503 → sandbox platform down (key
|
|
102
|
+
* still valid); timeout / unreachable → check the sandbox URL.
|
|
103
|
+
*/
|
|
104
|
+
declare function sandboxAuthProbe(config: SandboxAuthProbeConfig): PreflightProbe;
|
|
105
|
+
interface HttpHeadProbeConfig {
|
|
106
|
+
/** Probe name in the report. */
|
|
107
|
+
name: string;
|
|
108
|
+
/** URL to `HEAD`. */
|
|
109
|
+
url: string;
|
|
110
|
+
/**
|
|
111
|
+
* Accepted status(es). A single number requires an exact match; an array
|
|
112
|
+
* requires membership. Omitted → any 2xx/3xx (the host is up and the path
|
|
113
|
+
* resolves) counts as live.
|
|
114
|
+
*/
|
|
115
|
+
expectStatus?: number | number[];
|
|
116
|
+
/** Default `true`. */
|
|
117
|
+
critical?: boolean;
|
|
118
|
+
/** Env-var name of the URL, named verbatim in a failure. */
|
|
119
|
+
urlSecret?: string;
|
|
120
|
+
/** Per-probe deadline. Default 10s. */
|
|
121
|
+
timeoutMs?: number;
|
|
122
|
+
/** Injection seam for tests; defaults to global `fetch`. */
|
|
123
|
+
fetchImpl?: typeof fetch;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Probe a plain reachability endpoint (e.g. a platform base URL) with a `HEAD`.
|
|
127
|
+
* Confirms the URL is live and resolving — the class of failure behind a stale
|
|
128
|
+
* platform URL that still sits in the secret store.
|
|
129
|
+
*/
|
|
130
|
+
declare function httpHeadProbe(config: HttpHeadProbeConfig): PreflightProbe;
|
|
131
|
+
/**
|
|
132
|
+
* Run every probe (concurrently), time each, and fold into a report. The run
|
|
133
|
+
* fails (`ok: false`) iff a critical probe fails; a failed non-critical probe
|
|
134
|
+
* is a warning that does not block the deploy.
|
|
135
|
+
*/
|
|
136
|
+
declare function runPreflight(probes: PreflightProbe[]): Promise<PreflightReport>;
|
|
137
|
+
/** Render a report as an aligned, operator-readable table + verdict line. Pure
|
|
138
|
+
* (no I/O) so it is trivially testable and reusable by the bin. */
|
|
139
|
+
declare function formatPreflightReport(report: PreflightReport): string;
|
|
140
|
+
|
|
141
|
+
export { type HttpHeadProbeConfig, type PreflightProbe, type PreflightProbeResult, type PreflightProbeVerdict, type PreflightReport, type RouterChatProbeConfig, type SandboxAuthProbeConfig, formatPreflightReport, httpHeadProbe, routerChatProbe, runPreflight, sandboxAuthProbe };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatPreflightReport,
|
|
3
|
+
httpHeadProbe,
|
|
4
|
+
routerChatProbe,
|
|
5
|
+
runPreflight,
|
|
6
|
+
sandboxAuthProbe
|
|
7
|
+
} from "../chunk-Q4TKVF3L.js";
|
|
8
|
+
export {
|
|
9
|
+
formatPreflightReport,
|
|
10
|
+
httpHeadProbe,
|
|
11
|
+
routerChatProbe,
|
|
12
|
+
runPreflight,
|
|
13
|
+
sandboxAuthProbe
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -23,6 +23,21 @@ interface AuthGuard<Session> {
|
|
|
23
23
|
getOptionalSession(request: Request): Promise<Session | null>;
|
|
24
24
|
}
|
|
25
25
|
declare function createAuthGuard<Session>(opts: AuthGuardOptions<Session>): AuthGuard<Session>;
|
|
26
|
+
type GuardResolution<T> = {
|
|
27
|
+
ok: true;
|
|
28
|
+
value: T;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
response: Response;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Adapt a guard that THROWS a Response (the quartet above — the router
|
|
35
|
+
* convention) to the `{ok: true, value} | {ok: false, response}` resolution
|
|
36
|
+
* shape the route factories take (`/chat-routes` `authorize`,
|
|
37
|
+
* `/interactions` `resolveConnection`, `/chat-routes` upload `authorize`).
|
|
38
|
+
* Every product wrote this try/catch by hand; it lives here once.
|
|
39
|
+
*/
|
|
40
|
+
declare function guardResolution<T>(run: () => Promise<T>): Promise<GuardResolution<T>>;
|
|
26
41
|
/** Comma/whitespace separated → trimmed, lowercased, empties dropped. */
|
|
27
42
|
declare function parseAdminEmails(raw: string | null | undefined): string[];
|
|
28
43
|
interface AdminGuardOptions<Session> {
|
|
@@ -267,4 +282,4 @@ interface TangleSsoHandlers {
|
|
|
267
282
|
}
|
|
268
283
|
declare function createTangleSsoHandlers(opts: TangleSsoHandlerOptions): TangleSsoHandlers;
|
|
269
284
|
|
|
270
|
-
export { type AuthGuard as A, type BetterAuthSessionCookieMinterOptions as B, type SsoStateConfig as S, type TangleSsoHandlers as T, type TangleSsoAuthClient as a, type TangleSsoAccountStore as b, type AdminGuardOptions as c, type AssertBillableBalanceOptions as d, type AuthGuardOptions as e, type BetterAuthSessionCookieSource as f, type BillableBalanceState as g, type TangleSsoExchangeResult as h, type TangleSsoHandlerOptions as i, type TangleSsoSessionCookieArgs as j, TangleSsoUserCreateError as k, assertBillableBalance as l, createAdminGuard as m, createAuthGuard as n, createBetterAuthSessionCookieMinter as o, createSignedSsoState as p, createTangleSsoHandlers as q,
|
|
285
|
+
export { type AuthGuard as A, type BetterAuthSessionCookieMinterOptions as B, type GuardResolution as G, type SsoStateConfig as S, type TangleSsoHandlers as T, type TangleSsoAuthClient as a, type TangleSsoAccountStore as b, type AdminGuardOptions as c, type AssertBillableBalanceOptions as d, type AuthGuardOptions as e, type BetterAuthSessionCookieSource as f, type BillableBalanceState as g, type TangleSsoExchangeResult as h, type TangleSsoHandlerOptions as i, type TangleSsoSessionCookieArgs as j, TangleSsoUserCreateError as k, assertBillableBalance as l, createAdminGuard as m, createAuthGuard as n, createBetterAuthSessionCookieMinter as o, createSignedSsoState as p, createTangleSsoHandlers as q, guardResolution as r, parseAdminEmails as s, signSessionCookieValue as t, verifySignedSsoState as v };
|
package/dist/stream/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MembersPanel
|
|
3
|
-
} from "../chunk-GNL3MG5J.js";
|
|
4
1
|
import {
|
|
5
2
|
InvitationsPanel
|
|
6
3
|
} from "../chunk-7C64WKAH.js";
|
|
7
4
|
import {
|
|
8
5
|
InviteAcceptPage
|
|
9
6
|
} from "../chunk-VCPZ3HTN.js";
|
|
7
|
+
import {
|
|
8
|
+
MembersPanel
|
|
9
|
+
} from "../chunk-GNL3MG5J.js";
|
|
10
10
|
import "../chunk-63CE7FEZ.js";
|
|
11
11
|
export {
|
|
12
12
|
InvitationsPanel,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkThemeContract
|
|
4
|
+
} from "../chunk-NYATNLRK.js";
|
|
5
|
+
|
|
6
|
+
// src/theme-contract/cli.ts
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const out = { srcDirs: [], extraCss: [], allow: [] };
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const flag = argv[i];
|
|
11
|
+
const take = () => {
|
|
12
|
+
const v = argv[++i];
|
|
13
|
+
if (v === void 0) fail(`${flag} needs a value`);
|
|
14
|
+
return v;
|
|
15
|
+
};
|
|
16
|
+
switch (flag) {
|
|
17
|
+
case "--src":
|
|
18
|
+
out.srcDirs.push(take());
|
|
19
|
+
break;
|
|
20
|
+
case "--extra-css":
|
|
21
|
+
out.extraCss.push(take());
|
|
22
|
+
break;
|
|
23
|
+
case "--allow":
|
|
24
|
+
out.allow.push(take());
|
|
25
|
+
break;
|
|
26
|
+
case "--tokens":
|
|
27
|
+
out.tokens = take();
|
|
28
|
+
break;
|
|
29
|
+
case "-h":
|
|
30
|
+
case "--help":
|
|
31
|
+
printUsage();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
fail(`unknown argument: ${flag}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function printUsage() {
|
|
41
|
+
process.stdout.write(
|
|
42
|
+
"Usage: agent-app-theme-check --src <dir> [--src <dir>\u2026] [--extra-css <file>\u2026] [--tokens <file>] [--allow <--var>\u2026]\n"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function fail(msg) {
|
|
46
|
+
process.stderr.write(`agent-app-theme-check: ${msg}
|
|
47
|
+
`);
|
|
48
|
+
printUsage();
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
function main() {
|
|
52
|
+
const args = parseArgs(process.argv.slice(2));
|
|
53
|
+
if (args.srcDirs.length === 0) fail("at least one --src <dir> is required");
|
|
54
|
+
const { ok, missing } = checkThemeContract({
|
|
55
|
+
srcDirs: args.srcDirs,
|
|
56
|
+
tokensCss: args.tokens,
|
|
57
|
+
extraTokensCss: args.extraCss,
|
|
58
|
+
allowlist: args.allow
|
|
59
|
+
});
|
|
60
|
+
if (ok) {
|
|
61
|
+
process.stdout.write(`theme contract OK \u2014 every referenced token is defined (${args.srcDirs.join(", ")})
|
|
62
|
+
`);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
`theme contract FAILED \u2014 ${missing.length} token reference(s) resolve to nothing (surface ships transparent):
|
|
67
|
+
|
|
68
|
+
`
|
|
69
|
+
);
|
|
70
|
+
for (const m of missing) process.stderr.write(` ${m.varName}
|
|
71
|
+
referenced in ${m.referencedIn}
|
|
72
|
+
`);
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
"\nDefine these in your tokens.css (or `import '@tangle-network/agent-app/styles'`),\npass the defining CSS via --extra-css, or suppress a deliberately-external one with --allow.\n"
|
|
75
|
+
);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
main();
|
|
79
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/theme-contract/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * agent-app-theme-check — CI guard against the invisible-surface incident class.\n *\n * A consumer app runs this over its own source; it fails (exit 1) when a\n * component references a theme token — `var(--popover)` or a preset-mapped\n * utility like `bg-surface-container-high` — that the app's shipped CSS never\n * defines, which would paint that surface transparent with no error at runtime.\n *\n * agent-app-theme-check --src src --src packages/ui/src \\\n * --extra-css src/app-tokens.css\n *\n * Flags (all repeatable except --tokens):\n * --src <dir> source dir to scan for token references (required, 1+)\n * --extra-css <file> extra CSS whose --name: definitions also count as defined\n * --tokens <file> override the base tokens.css (defaults to the one\n * agent-app ships as `@tangle-network/agent-app/styles`)\n * --allow <--var> suppress a token name from the missing report\n *\n * Wire it as a CI step: `\"theme-check\": \"agent-app-theme-check --src src\"`.\n */\n\nimport { checkThemeContract } from './index'\n\ninterface ParsedArgs {\n srcDirs: string[]\n extraCss: string[]\n allow: string[]\n tokens?: string\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = { srcDirs: [], extraCss: [], allow: [] }\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const take = () => {\n const v = argv[++i]\n if (v === undefined) fail(`${flag} needs a value`)\n return v!\n }\n switch (flag) {\n case '--src':\n out.srcDirs.push(take())\n break\n case '--extra-css':\n out.extraCss.push(take())\n break\n case '--allow':\n out.allow.push(take())\n break\n case '--tokens':\n out.tokens = take()\n break\n case '-h':\n case '--help':\n printUsage()\n process.exit(0)\n break\n default:\n fail(`unknown argument: ${flag}`)\n }\n }\n return out\n}\n\nfunction printUsage(): void {\n process.stdout.write(\n 'Usage: agent-app-theme-check --src <dir> [--src <dir>…] ' +\n '[--extra-css <file>…] [--tokens <file>] [--allow <--var>…]\\n',\n )\n}\n\nfunction fail(msg: string): never {\n process.stderr.write(`agent-app-theme-check: ${msg}\\n`)\n printUsage()\n process.exit(2)\n}\n\nfunction main(): void {\n const args = parseArgs(process.argv.slice(2))\n if (args.srcDirs.length === 0) fail('at least one --src <dir> is required')\n\n const { ok, missing } = checkThemeContract({\n srcDirs: args.srcDirs,\n tokensCss: args.tokens,\n extraTokensCss: args.extraCss,\n allowlist: args.allow,\n })\n\n if (ok) {\n process.stdout.write(`theme contract OK — every referenced token is defined (${args.srcDirs.join(', ')})\\n`)\n process.exit(0)\n }\n\n process.stderr.write(\n `theme contract FAILED — ${missing.length} token reference(s) resolve to nothing (surface ships transparent):\\n\\n`,\n )\n for (const m of missing) process.stderr.write(` ${m.varName}\\n referenced in ${m.referencedIn}\\n`)\n process.stderr.write(\n '\\nDefine these in your tokens.css (or `import \\'@tangle-network/agent-app/styles\\'`),\\n' +\n 'pass the defining CSS via --extra-css, or suppress a deliberately-external one with --allow.\\n',\n )\n process.exit(1)\n}\n\nmain()\n"],"mappings":";;;;;;AA+BA,SAAS,UAAU,MAA4B;AAC7C,QAAM,MAAkB,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,EAAE;AAC/D,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,OAAO,KAAK,CAAC;AACnB,UAAM,OAAO,MAAM;AACjB,YAAM,IAAI,KAAK,EAAE,CAAC;AAClB,UAAI,MAAM,OAAW,MAAK,GAAG,IAAI,gBAAgB;AACjD,aAAO;AAAA,IACT;AACA,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,YAAI,QAAQ,KAAK,KAAK,CAAC;AACvB;AAAA,MACF,KAAK;AACH,YAAI,SAAS,KAAK,KAAK,CAAC;AACxB;AAAA,MACF,KAAK;AACH,YAAI,MAAM,KAAK,KAAK,CAAC;AACrB;AAAA,MACF,KAAK;AACH,YAAI,SAAS,KAAK;AAClB;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,mBAAW;AACX,gBAAQ,KAAK,CAAC;AACd;AAAA,MACF;AACE,aAAK,qBAAqB,IAAI,EAAE;AAAA,IACpC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAmB;AAC1B,UAAQ,OAAO;AAAA,IACb;AAAA,EAEF;AACF;AAEA,SAAS,KAAK,KAAoB;AAChC,UAAQ,OAAO,MAAM,0BAA0B,GAAG;AAAA,CAAI;AACtD,aAAW;AACX,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,OAAa;AACpB,QAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC5C,MAAI,KAAK,QAAQ,WAAW,EAAG,MAAK,sCAAsC;AAE1E,QAAM,EAAE,IAAI,QAAQ,IAAI,mBAAmB;AAAA,IACzC,SAAS,KAAK;AAAA,IACd,WAAW,KAAK;AAAA,IAChB,gBAAgB,KAAK;AAAA,IACrB,WAAW,KAAK;AAAA,EAClB,CAAC;AAED,MAAI,IAAI;AACN,YAAQ,OAAO,MAAM,+DAA0D,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,CAAK;AAC3G,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,OAAO;AAAA,IACb,gCAA2B,QAAQ,MAAM;AAAA;AAAA;AAAA,EAC3C;AACA,aAAW,KAAK,QAAS,SAAQ,OAAO,MAAM,KAAK,EAAE,OAAO;AAAA,oBAAuB,EAAE,YAAY;AAAA,CAAI;AACrG,UAAQ,OAAO;AAAA,IACb;AAAA,EAEF;AACA,UAAQ,KAAK,CAAC;AAChB;AAEA,KAAK;","names":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exportable theme-token contract checker — the incident guard for the
|
|
3
|
+
* invisible-popover class of bugs.
|
|
4
|
+
*
|
|
5
|
+
* The failure mode (tax-agent's transparent model dropdown; the whole
|
|
6
|
+
* `bg-surface-container-*` family): a consumer app ships a component that
|
|
7
|
+
* references a theme token — either as `var(--popover)` or as a Tailwind class
|
|
8
|
+
* like `bg-surface-container-high` that the agent-app preset maps to
|
|
9
|
+
* `hsl(var(--popover))` — but the app's OWN build never emits that custom
|
|
10
|
+
* property (it forgot `import '@tangle-network/agent-app/styles'`, or dropped a
|
|
11
|
+
* token in its local tokens.css). CSS resolves the missing var to nothing, the
|
|
12
|
+
* surface paints transparent, and NOTHING errors. It ships invisible.
|
|
13
|
+
*
|
|
14
|
+
* `tests/theme/tokens-contract.test.ts` guards agent-app's OWN components. This
|
|
15
|
+
* module lifts that walking logic into a function every CONSUMER app can run
|
|
16
|
+
* against ITS OWN source in CI, comparing references to the tokens.css agent-app
|
|
17
|
+
* ships plus any extra CSS the app defines.
|
|
18
|
+
*
|
|
19
|
+
* ── What each check covers (scope is deliberately honest) ────────────────────
|
|
20
|
+
*
|
|
21
|
+
* 1. var(--…) check — COMPLETE. Every `var(--name)` literal in the scanned
|
|
22
|
+
* source (inline styles, `bg-[var(--name)]` arbitrary Tailwind values, CSS
|
|
23
|
+
* template strings) is matched and compared against the defined token set.
|
|
24
|
+
* This is exact: a `var(--x)` reference is unambiguous. It is a raw-text
|
|
25
|
+
* scan (no AST), so a `var(--x)` written inside a comment or string literal
|
|
26
|
+
* counts too — deliberate: it keeps the single-source logic identical to the
|
|
27
|
+
* agent-app self-test, and a dangling `var(--x)` in a comment is a smell
|
|
28
|
+
* worth surfacing. Suppress a deliberate one with `allowlist`.
|
|
29
|
+
*
|
|
30
|
+
* 2. Tailwind-utility check — INTENTIONALLY PARTIAL. Bare classes like
|
|
31
|
+
* `bg-card` carry no `var(--)` and so are invisible to check 1; Tailwind
|
|
32
|
+
* resolves them to `hsl(var(--card))` at build via the preset. Fully
|
|
33
|
+
* resolving arbitrary Tailwind config is out of scope (it would mean
|
|
34
|
+
* re-implementing Tailwind). Instead we check the SPECIFIC known-dangerous
|
|
35
|
+
* families that have actually shipped invisible: the MD3 surface ladder
|
|
36
|
+
* (`surface-container` / `-high` / `-highest`) and the `card` / `popover`
|
|
37
|
+
* elevation pairs — exactly the utilities the agent-app tailwind-preset
|
|
38
|
+
* registers onto elevation tokens (see src/theme/tailwind-preset.ts, the
|
|
39
|
+
* source of truth for this mapping). The canvas/sequence aliases
|
|
40
|
+
* (`--bg-input`, `--text-primary`, …) are consumed as `bg-[var(--…)]`
|
|
41
|
+
* arbitrary values and so are already covered fully by check 1 — they need
|
|
42
|
+
* no entry here.
|
|
43
|
+
*
|
|
44
|
+
* Node-only (reads the filesystem) → this lives in the `./theme-contract`
|
|
45
|
+
* subpath, NOT `./theme`, which must stay browser-clean (it's in the
|
|
46
|
+
* browser-safe manifest test).
|
|
47
|
+
*/
|
|
48
|
+
interface ThemeContractOptions {
|
|
49
|
+
/** Consumer source directories to scan for token references (recursively). */
|
|
50
|
+
srcDirs: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Path to the base tokens.css whose `--name:` definitions are the ground
|
|
53
|
+
* truth. Defaults to the tokens.css agent-app ships (`./styles`) — the set a
|
|
54
|
+
* consumer gets from `import '@tangle-network/agent-app/styles'`.
|
|
55
|
+
*/
|
|
56
|
+
tokensCss?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Additional CSS files whose `--name:` definitions also count as defined —
|
|
59
|
+
* the app's own overrides/extensions layered on top of the base tokens.
|
|
60
|
+
*/
|
|
61
|
+
extraTokensCss?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Token names (e.g. `--my-app-accent`) to treat as always-defined, suppressing
|
|
64
|
+
* them from the missing list. For app-specific vars defined outside any CSS
|
|
65
|
+
* the checker can see (injected at runtime, from a third-party stylesheet, …).
|
|
66
|
+
*/
|
|
67
|
+
allowlist?: string[];
|
|
68
|
+
}
|
|
69
|
+
interface ThemeContractMiss {
|
|
70
|
+
/** The undefined custom property, e.g. `--popover`. */
|
|
71
|
+
varName: string;
|
|
72
|
+
/**
|
|
73
|
+
* Where it was referenced: `path/to/file.tsx`, or
|
|
74
|
+
* `path/to/file.tsx (via bg-surface-container-high)` when the reference is a
|
|
75
|
+
* Tailwind utility that resolves to the token rather than a literal var().
|
|
76
|
+
*/
|
|
77
|
+
referencedIn: string;
|
|
78
|
+
}
|
|
79
|
+
interface ThemeContractResult {
|
|
80
|
+
ok: boolean;
|
|
81
|
+
missing: ThemeContractMiss[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check that every theme token a consumer's source references is actually
|
|
85
|
+
* defined in the CSS that consumer ships. Returns the full missing set; the
|
|
86
|
+
* caller decides how to fail (the bin exits non-zero on any miss).
|
|
87
|
+
*/
|
|
88
|
+
declare function checkThemeContract(opts: ThemeContractOptions): ThemeContractResult;
|
|
89
|
+
|
|
90
|
+
export { type ThemeContractMiss, type ThemeContractOptions, type ThemeContractResult, checkThemeContract };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -9,6 +9,7 @@ import { a as FlowTrace } from '../flow-types-Cb_AblZs.js';
|
|
|
9
9
|
export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-BIIC__CP.js';
|
|
10
10
|
import { CatalogModel } from '../catalog/index.js';
|
|
11
11
|
import { Harness } from '../harness/index.js';
|
|
12
|
+
export { b as ChatTurnFilePartInput, a as ChatTurnPartInput, C as ChatTurnRequestPayload, f as chatTurnRequestInit } from '../wire-BaUF66AS.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Client-side chat-stream consumption — the NDJSON parse loop every agent
|
|
@@ -93,6 +94,17 @@ interface StreamChatOptions {
|
|
|
93
94
|
*/
|
|
94
95
|
declare function streamChatTurn(opts: StreamChatOptions): Promise<ConsumeChatStreamResult>;
|
|
95
96
|
|
|
97
|
+
/** Prompt-part descriptor an uploaded file carries (the upload route's
|
|
98
|
+
* `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors
|
|
99
|
+
* `/chat-routes`' wire shape structurally — no server import here. */
|
|
100
|
+
interface ComposerFilePart {
|
|
101
|
+
type: 'image' | 'file';
|
|
102
|
+
filename?: string;
|
|
103
|
+
mediaType?: string;
|
|
104
|
+
url?: string;
|
|
105
|
+
path?: string;
|
|
106
|
+
content?: string;
|
|
107
|
+
}
|
|
96
108
|
interface ComposerFile {
|
|
97
109
|
id: string;
|
|
98
110
|
name: string;
|
|
@@ -101,11 +113,19 @@ interface ComposerFile {
|
|
|
101
113
|
/** Number of files inside, for a folder chip. */
|
|
102
114
|
fileCount?: number;
|
|
103
115
|
status: 'pending' | 'uploading' | 'ready' | 'error';
|
|
116
|
+
/** Uploaded part descriptor; set once the upload route returns. Only
|
|
117
|
+
* `status: 'ready'` files with a part travel on a parts-aware send. */
|
|
118
|
+
part?: ComposerFilePart;
|
|
104
119
|
}
|
|
105
120
|
interface ChatComposerProps {
|
|
106
121
|
/** Send the trimmed, non-empty message. Attached files travel separately via
|
|
107
|
-
* `onAttach` + `pendingFiles` (the host consumes and clears them on send).
|
|
108
|
-
|
|
122
|
+
* `onAttach` + `pendingFiles` (the host consumes and clears them on send).
|
|
123
|
+
* Optional when `onSendParts` is wired. */
|
|
124
|
+
onSend?: (message: string) => void;
|
|
125
|
+
/** Parts-aware send: receives the trimmed message plus the `part`
|
|
126
|
+
* descriptors of every `ready` pending file. Takes precedence over
|
|
127
|
+
* `onSend`; enables file-only sends (empty text, ≥1 ready part). */
|
|
128
|
+
onSendParts?: (message: string, parts: ComposerFilePart[]) => void;
|
|
109
129
|
/** Stop the in-flight turn; shown in place of Send while `isStreaming`. */
|
|
110
130
|
onCancel?: () => void;
|
|
111
131
|
isStreaming?: boolean;
|
|
@@ -137,7 +157,7 @@ interface ChatComposerProps {
|
|
|
137
157
|
sendLabel?: string;
|
|
138
158
|
className?: string;
|
|
139
159
|
}
|
|
140
|
-
declare function ChatComposer({ onSend, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, accept, dropTitle, dropDescription, focusShortcut, sendLabel, className, }: ChatComposerProps): react.JSX.Element;
|
|
160
|
+
declare function ChatComposer({ onSend, onSendParts, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, accept, dropTitle, dropDescription, focusShortcut, sendLabel, className, }: ChatComposerProps): react.JSX.Element;
|
|
141
161
|
|
|
142
162
|
/**
|
|
143
163
|
* Shared answer-building + submit plumbing for the interaction cards
|
|
@@ -766,4 +786,4 @@ declare function useThinkingSeconds(active: boolean): number;
|
|
|
766
786
|
*/
|
|
767
787
|
declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, }: ChatMessagesProps): react.JSX.Element;
|
|
768
788
|
|
|
769
|
-
export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, CatalogModel, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, ChatInteractionStatus, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FlowWaterfall, type FlowWaterfallProps, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsResult, type WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createInteractionAnswerSubmitter, dispatchChatStreamLine, fieldAnswer, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, interactionStatusLabels, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
|
|
789
|
+
export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, CatalogModel, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, ChatInteractionStatus, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ComposerFilePart, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FlowWaterfall, type FlowWaterfallProps, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsResult, type WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createInteractionAnswerSubmitter, dispatchChatStreamLine, fieldAnswer, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, interactionStatusLabels, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
|