Skip to content

feat(cloudflare/ai): add AI.ProviderKey BYOK composition helper - #586

Merged
sam-goodwin merged 4 commits into
alchemy-run:mainfrom
agcty:feat/cloudflare-ai-gateway-provider-keys
Jul 8, 2026
Merged

feat(cloudflare/ai): add AI.ProviderKey BYOK composition helper#586
sam-goodwin merged 4 commits into
alchemy-run:mainfrom
agcty:feat/cloudflare-ai-gateway-provider-keys

Conversation

@agcty

@agcty agcty commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Cloudflare.AI.ProviderKey — a composition helper that declares a Cloudflare AI Gateway bring-your-own-key (BYOK) provider key as a single resource.

A BYOK key needs two coordinated resources:

  • a Secrets Store Secret holding the provider API key — scoped to ai_gateway and named exactly {gatewayId}_{providerSlug}_{alias}, and
  • a GatewayProvider config that references that secret on the gateway.

This helper owns that naming contract and wires both, so app stacks declare one resource instead of hand-coupling the two:

const store = yield* Cloudflare.SecretsStore.Store("Store");
const gateway = yield* Cloudflare.AI.Gateway("Gateway", {
  id: "my-gateway",
  storeId: store.storeId,
});

const { secret, gatewayProvider } = yield* Cloudflare.AI.ProviderKey("OpenAiKey", {
  store,
  gatewayId: gateway.gatewayId,
  providerSlug: "openai",
  value: yield* Config.redacted("OPENAI_API_KEY"),
});

It returns { secret, gatewayProvider } so either underlying resource stays addressable.

Notes

  • Rebased onto current main and re-ported onto the Cloudflare/AiGateway/Cloudflare/AI/ restructure: the helper composes GatewayProvider (formerly AiGatewayProviderConfig), lives at AI/ProviderKey.ts, and surfaces as Cloudflare.AI.ProviderKey.
  • Per review: the ProviderKey effect is piped through Namespace.push(id), so the children are plain Secret("Secret") and GatewayProvider("Provider") namespaced under ${id} (drops the secretResourceId escape hatch the old ${id}-secret default needed).
  • Updates the GatewayProvider manual-wiring example to use Config.redacted (per the earlier review note) and cross-links this helper.
  • ProviderKey is tagged @resource with @section/@example blocks, so docs:gen emits an API reference page for it (untagged exports are skipped).
  • Tests cover the {gatewayId}_{providerSlug}_{alias} naming contract, explicit alias, redeploy idempotency, in-place value rotation (stable secretId + providerConfigId), the alias-change replacement cascade (secret renamed, provider config replaced and re-pointed, old secret reclaimed), and a post-destroy check that the BYOK secret is reclaimed from the surviving (adopted) store.
  • tsc -b is green. The check job's red is docs:check failing on pre-existing broken links to unrelated Cloudflare provider pages (/providers/cloudflare/email/*, /providers/cloudflare/queue) — not introduced here (no website/ changes).

🤖 Generated with Claude Code

Comment thread packages/alchemy/src/Cloudflare/AiGateway/AiGatewayProviderKey.ts Outdated
@agcty
agcty force-pushed the feat/cloudflare-ai-gateway-provider-keys branch from 07189a4 to 5909b1e Compare June 16, 2026 17:27
@agcty
agcty marked this pull request as ready for review June 16, 2026 17:27
@agcty
agcty force-pushed the feat/cloudflare-ai-gateway-provider-keys branch 2 times, most recently from 4354af0 to 8b62a28 Compare June 16, 2026 17:31
@agcty

agcty commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@sam-goodwin rebased and tested with cf resources now

agcty added a commit to agcty/alchemy-effect that referenced this pull request Jun 26, 2026
…oviderConfig composition

Replace the monolithic AiGatewayProviderKey Resource (PR alchemy-run#586) with a
lightweight composition helper: an Effect.gen that provisions a Secrets
Store Secret named {gatewayId}_{providerSlug}_{alias} scoped to
ai_gateway, then declares an AiGatewayProviderConfig referencing that
secret's id.

No Provider registration needed — the helper composes two existing
resources rather than managing its own lifecycle.

Also fix process.env usage in ProviderConfig.ts JSDoc example to use
Config.redacted() per the maintainer's review comment (sam-goodwin,
line 154 on the original PR).
@agcty
agcty force-pushed the feat/cloudflare-ai-gateway-provider-keys branch from 8b62a28 to f2a8482 Compare June 26, 2026 16:05
@agcty agcty changed the title feat(cloudflare/ai-gateway): AiGatewayProviderKey — declarative BYOK provider keys feat(cloudflare/ai-gateway): AiGatewayProviderKey — Secret + ProviderConfig composition helper Jun 26, 2026
@agcty

agcty commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Reworked this onto current main: instead of a standalone Resource, AiGatewayProviderKey is now a thin composition helper over the Secret + AiGatewayProviderConfig resources that landed in main since this was opened — drops ~700 lines, resolves the merge conflict, and addresses your Config.redacted comment in the docs. The test is scoped to the composition + naming contract; happy to add a post-destroy orphaned-secret check if you'd like.

`Cloudflare.AI.ProviderKey` bundles the two low-level resources a Cloudflare
AI Gateway bring-your-own-key requires: a Secrets Store `Secret` and the
`GatewayProvider` config that references it. Cloudflare mandates the secret
live in the gateway's attached store, be scoped to `ai_gateway`, and be named
exactly `{gatewayId}_{providerSlug}_{alias}` — the helper owns that naming
contract so app stacks declare one resource instead of hand-wiring both.

Returns `{ secret, gatewayProvider }` so callers can still reference either
underlying resource. Also points the `GatewayProvider` manual-wiring example
at this helper and switches its secret example to `Config.redacted`.
@agcty
agcty force-pushed the feat/cloudflare-ai-gateway-provider-keys branch from 672034a to 500bddc Compare June 27, 2026 18:41
@agcty agcty changed the title feat(cloudflare/ai-gateway): AiGatewayProviderKey — Secret + ProviderConfig composition helper feat(cloudflare/ai): add AI.ProviderKey BYOK composition helper Jun 27, 2026
@agcty

agcty commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and re-ported onto the Cloudflare/AiGateway/Cloudflare/AI/ restructure. The helper is now Cloudflare.AI.ProviderKey, composing SecretsStore.Secret + AI.GatewayProvider (the renamed provider config), returning { secret, gatewayProvider }. Added the post-destroy orphaned-secret check you mentioned. tsc -b is green; the check red is docs:check tripping on pre-existing broken links to unrelated Cloudflare provider pages (/providers/cloudflare/email/*, /providers/cloudflare/queue) — no website/ changes here.

@agcty

agcty commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@sam-goodwin any feedback on this? would love to use it, or still reworking some internals a bit?

@sam-goodwin

Copy link
Copy Markdown
Contributor

@sam-goodwin any feedback on this? would love to use it, or still reworking some internals a bit?

Sorry for delay, trying my best to keep up.

export const ProviderKey = (id: string, props: ProviderKeyProps) =>
Effect.gen(function* () {
const alias = props.alias ?? "default";
const secret = yield* Secret(props.secretResourceId ?? `${id}-secret`, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of ${id}-secret, you could instead pipe the Effect.gen (ProviderKey effect) to Namespace.push(id) and then name these Secret("Secret") and GatewayProvider("Provider")and they will be namespaced under${id}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 55e4b0f — the ProviderKey effect is piped through Namespace.push(id) and the children are now Secret("Secret") and GatewayProvider("Provider") (dropped the secretResourceId escape hatch since the ${id}-secret default is gone). Also added a reconciliation test covering in-place value rotation and the alias-change replacement cascade. All 4 live tests pass.

sam-goodwin and others added 3 commits July 7, 2026 22:25
….push

Per review: pipe the ProviderKey effect through Namespace.push(id) so the
backing resources are Secret("Secret") and GatewayProvider("Provider"),
namespaced under ${id}. Drops the secretResourceId escape hatch (the
${id}-secret default it existed for is gone).

Adds a reconciliation test covering in-place value rotation (stable
secretId + providerConfigId) and the alias-change replacement cascade
(secret renamed, provider config replaced and re-pointed, old secret
reclaimed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds @resource/@product/@category + @section/@example blocks so docs:gen
emits an API reference page (it skips untagged exports), documents the
namespaced children and update/replacement semantics, and adds field docs
on the returned { secret, gatewayProvider }.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s signature

Replace the Omit<GatewayProviderInputProps> intersection with an explicitly
declared plain-typed interface; the constructor takes
InputProps<ProviderKeyProps> so the engine's Input wrapping stays at the
signature, not in the Props declaration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sam-goodwin
sam-goodwin merged commit 8c9bfe7 into alchemy-run:main Jul 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants