2.0.0-beta.55 - AI Gateway Spend Caps
An AI Gateway sits in front of your model calls, which means it’s also
where a runaway loop turns into a runaway bill. beta.55 puts the
guardrail in your stack: Cloudflare.AiGatewaySpendingLimit sets a
hard dollar cap on cumulative AI Gateway spend across every gateway in
the account, declared and reconciled like any other resource.
Cloudflare.AiGatewaySpendingLimit
Section titled “Cloudflare.AiGatewaySpendingLimit”Amounts are in cents (Cloudflare’s native unit — minimum 1_00 =
$1.00), accumulated over a daily, weekly, or monthly window with a
fixed (reset on the boundary) or sliding (rolling window) strategy:
import * as Cloudflare from "alchemy/Cloudflare";
// Cloudflare requires one manual credit top-up before a spending limit// can be set. `topUp` reconciles that requirement: the provider observes// the billing state and only charges the account's default payment// method if the account has never topped up.const cap = yield* Cloudflare.AiGatewaySpendingLimit("ai-spend-cap", { amount: 250_00, // cents -> $250.00 (minimum 1_00 = $1.00) duration: "monthly", topUp: { amount: 10_00 }, // cents -> $10.00 (Cloudflare minimum)});The topUp prop deals with Cloudflare’s bootstrap requirement:
an account can’t set a spending limit until it has loaded Unified
Billing credits via at least one manual top-up (NO_MANUAL_TOPUP).
The provider observes first_topup_success on the live billing API
and only makes the one-time charge when the account has never topped
up — once bootstrapped, the prop is inert and never charges again. If
the payment needs interactive confirmation (3-D Secure), the resource
fails with a typed AiGatewaySpendingLimitTopupRequired error and the
top-up must be completed in the dashboard.
Auto recharge is on by default — when the credit balance drops below
threshold, Cloudflare recharges by amount automatically:
const cap = yield* Cloudflare.AiGatewaySpendingLimit("ai-spend-cap", { amount: 250_00, duration: "monthly", topUp: { amount: 20_00, threshold: 10_00 }, // recharge $20 below $10});Set topUp: { amount: 10_00, autoRecharge: false } for a one-time
bootstrap only.
One caveat: Cloudflare stores a single limit per account, so this resource is a per-account singleton — declare exactly one. Thanks Matthew Aylward (#569).
Fetcher utils on alchemy/Cloudflare/Bridge
Section titled “Fetcher utils on alchemy/Cloudflare/Bridge”The fetcher/socket interop helpers — fromCloudflareFetcher,
toCloudflareFetcher, toHttpClient, fromCloudflareSocket — are now
exported from the alchemy/Cloudflare/Bridge entrypoint, and
fromCloudflareFetcher / fromCloudflareSocket accept the global
Fetcher / Socket types directly
(#581).
That combination matters inside a framework worker (TanStack Start,
Astro, …), where your Effect backend arrives as a plain service binding
on env. Two calls turn it into a fully typed Effect HttpClient —
no casts:
import * as Cloudflare from "alchemy/Cloudflare/Bridge";
const client = Cloudflare.toHttpClient( Cloudflare.fromCloudflareFetcher(env.BACKEND),);const res = await client .get(`https://backend/?key=${encodeURIComponent(key)}`) .pipe(Effect.runPromise);return HttpServerResponse.toWeb( HttpServerResponse.fromClientResponse(res),);The request rides the in-account service binding, not the public
network. The cloudflare-tanstack example’s /api/hello route now
serves the same object four ways side by side — the raw R2 binding,
the service binding’s fetch, typed RPC (toRpcAsync), and this
HttpClient form — each covered by an integration test.
Also in this release
Section titled “Also in this release”- Fixed a dangling process that kept
alchemy deploy/alchemy destroyfrom exiting — localRpcProviderservice layers (providerServices/providerServicesEffect) now only construct whenAlchemyContext.devis set, so one-shot CLI runs no longer spin up the local dev provider machinery (#580)