Skip to content

feat(gateway): add BEAMR as an LLM gateway#418

Closed
SahilParikh03 wants to merge 2 commits into
aaronjmars:mainfrom
SahilParikh03:feat/beamr-gateway
Closed

feat(gateway): add BEAMR as an LLM gateway#418
SahilParikh03 wants to merge 2 commits into
aaronjmars:mainfrom
SahilParikh03:feat/beamr-gateway

Conversation

@SahilParikh03

Copy link
Copy Markdown
Contributor

What

Adds BEAMR to Aeon's LLM gateway roster (direct | bankr | openrouter | usepod | surplus | venice | **beamr**).

BEAMR is an OpenAI-compatible inference router: for each request it classifies difficulty, routes to the cheapest capable provider, and settles that single call in USDC on Base over x402. It's the same SIDECAR shape as Surplus/Venice, so it rides the existing claude-code-router bridge — no new payment code in Aeon.

How it works

  • scripts/llm-gateway.sh gains a beamr case (SIDECAR tier): points claude-code-router at ${BEAMR_BASE_URL}/api/v1/chat/completions, auth optional (BEAMR_API_KEY, defaults to x402), model ${BEAMR_MODEL:-auto}.
  • Dashboard: beamr added to the gateway enum + secrets descriptors; setting BEAMR_BASE_URL flips the gateway like the other providers.
  • Workflows (aeon.yml, messages.yml): BEAMR_BASE_URL / BEAMR_API_KEY / BEAMR_MODEL wired through; secrets excluded from the extraction grep.
  • README: gateway table row + auth-modal/intro mentions.

Design note

BEAMR is self-hostable, so the trigger is BEAMR_BASE_URL (your deployment) rather than a key prefix; BEAMR_API_KEY is optional. Happy to model it as a key instead if you'd prefer consistency with the other gateways.

🤖 Generated with Claude Code

0xShak and others added 2 commits June 10, 2026 10:04
BEAMR is an OpenAI-compatible inference router: it classifies each request,
routes it to the cheapest capable provider, and settles that single call in
USDC on Base over x402. That makes it a SIDECAR-tier gateway with the same
shape as Surplus/Venice — bridged through the existing claude-code-router.

Unlike key-based gateways, BEAMR is self-hostable, so the routing trigger is
the deployment URL (BEAMR_BASE_URL) rather than a key prefix; BEAMR_API_KEY is
optional (only if the deployment is auth-gated) and BEAMR_MODEL defaults to
`auto` so BEAMR's own router picks the model.

- scripts/llm-gateway.sh: `beamr)` case (sidecar via start_ccr_sidecar)
- dashboard: GatewayProvider enum, auth detection, secret descriptor + mapping
- workflows: pass BEAMR_BASE_URL/BEAMR_API_KEY/BEAMR_MODEL into the run steps;
  exclude the BEAMR gateway secret from per-skill secret extraction
- README: gateway table row + model-override notes

Setting BEAMR_BASE_URL in the dashboard flips aeon.yml's gateway.provider to
`beamr`; removing it reverts to `direct`. No behavior change for existing
gateways.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aaronjmars

Copy link
Copy Markdown
Owner

Hey @SahilParikh03 — thanks a lot for this (and the companion beamr-route skill in #419). I'm going to put both on hold for a little while: before I land a gateway integration for BEAMR I'd like to get more context on where the project is heading. Nothing's wrong with the PRs — I'll circle back once I have more info. Appreciate the work, and sorry for the wait in the meantime. 🙏

@SahilParikh03

Copy link
Copy Markdown
Contributor Author

No worries! @aaronjmars

@aaronjmars

Copy link
Copy Markdown
Owner

Thanks @SahilParikh03 — reviewed this alongside #419. The runtime wiring is clean and faithfully mirrors the existing sidecar providers (Surplus/Venice), and it's correctly opt-in (default gateway.provider: direct stays untouched, no silent re-routing). Nice.

One thing blocks merge: the README advertises BEAMR as selectable in the Authenticate modal "via the dropdown" (README.md:220, :359), but that path is currently non-functional:

  • apps/dashboard/components/AuthModal.tsx PROVIDER_OPTIONS doesn't include beamr.
  • apps/dashboard/lib/auth-provider.mjs GATEWAY_PROVIDERS doesn't include beamr, so normalizeAuthConfig would throw Unknown gateway provider: beamr.
  • BEAMR's trigger is a base URL, but the modal / normalizeAuthConfig flow accepts only a key and explicitly rejects a custom base URL for gateways — so there's no field to enter BEAMR_BASE_URL.

The Secrets-tab path (set BEAMR_BASE_URL directly) does work today. So either fix is fine:

  1. Wire the auth modal to collect BEAMR_BASE_URL (the full fix — add beamr to PROVIDER_OPTIONS + GATEWAY_PROVIDERS and a base-URL input), or
  2. Correct the README to say "set BEAMR_BASE_URL in the Secrets tab" instead of the dropdown.

Minor, while you're in there: scripts/llm-gateway.sh doesn't enforce https:// on BEAMR_BASE_URL the way the dashboard's other custom base URLs are — worth a guard so prompts can't go out in cleartext.

Happy to merge once the docs match the actual onboarding path. Thanks again!

@aaronjmars

Copy link
Copy Markdown
Owner

Thanks for this @SahilParikh03 — the approach is right: BEAMR is OpenAI-compatible, so riding the existing start_ccr_sidecar bridge (like Surplus/Venice) is exactly the correct shape, and the workflow plumbing + dashboard enum/secrets/auth additions are all internally consistent. A couple of things need to be sorted before it can land, though — main has moved a fair bit since you branched.

Please rebase onto current main first. Three things have landed since this branched: #430 (the auto cascade resolver), #435 (cascade failover), and #461 (sidecar providers now derive the pinned model from aeon's $MODEL instead of hardcoding it). The branch currently conflicts in scripts/llm-gateway.sh, README.md, and apps/dashboard/lib/types.ts — all mechanical, but the gateway script should be re-expressed in the post-#461 idiom (see below).

Blocker 1 — BEAMR is unreachable via auto. After the rebase, beamr also needs to be wired into the auto-resolution path, or it can never actually be selected. Both dashboard routes (auth/route.ts and secrets/route.ts) call syncGatewayProvider('auto') — they never pin a provider — so the Run step resolves through aeon_present() / GATEWAY_ORDER, which only emit providers they know about. Please add:

  • a beamr) [ -n "${BEAMR_BASE_URL:-}" ] ;; arm to aeon_present() (keyed on the URL, since BEAMR has no key prefix), and
  • beamr to the GATEWAY_ORDER default list.

Blocker 2 — the advertised dropdown/paste flow isn't wired. apps/dashboard/lib/auth-provider.mjs isn't touched, so beamr is missing from its GATEWAY_PROVIDERS map — normalizeAuthConfig would throw Unknown gateway provider: beamr. Setting BEAMR_BASE_URL as a raw secret works, but the "select via the dropdown" UX in the README/secrets descriptor doesn't exist yet. Since the trigger here is a deployment URL rather than a pasted key, this needs a small design call on my end — happy to take that part on if you'd rather keep this PR to the gateway script + workflows.

Naming clash to resolve. main already defines BEAMR_GATEWAY_URL + BEAMR_PAYER_KEY for the separate beamr-route skill (#419, pay-per-call over x402). This PR introduces BEAMR_BASE_URL + BEAMR_API_KEY for the gateway — two near-identical "BEAMR" secret families ("GATEWAY_URL" vs "BASE_URL") will confuse operators. Could you either rename for clarity or add a note in the secrets descriptions distinguishing the two?

Everything else looks good — quoting is correct ("${BEAMR_BASE_URL%/}/api/v1/chat/completions", "${BEAMR_API_KEY:-x402}"), require_secret BEAMR_BASE_URL matches convention, the secret-extraction grep allowlist is updated, and the dashboard additions introduce no new auth/injection surface. Once it's rebased with the two fixes above (and the naming note), I'll merge. Appreciate the contribution!

@aaronjmars

Copy link
Copy Markdown
Owner

Thanks for this @SahilParikh03, and apologies for the lag. The shape is right — BEAMR is OpenAI-compatible, so riding the start_ccr_sidecar tier alongside Surplus/Venice is exactly correct, and the shell hygiene (quoting, require_secret, the secret-grep allowlist in .github/workflows/aeon.yml) is solid.

The blocker is that the gateway layer got refactored out from under this branch in #469 (merged 2026-06-15), so the dashboard-side edits now conflict against deleted code and the auto-resolver wiring is missing. Here's the full path to land it:

1. Rebase onto main and drop the hardcoded-map edits. #469 replaced the per-provider hardcoded structures with a single registry. Your edits to the GatewayProvider union in apps/dashboard/lib/types.ts, GATEWAY_SECRETS in apps/dashboard/app/api/secrets/route.ts, and hasGateway in apps/dashboard/app/api/auth/route.ts are all against code that no longer exists — remove them.

2. Register BEAMR in one place. Add an entry to GATEWAY_REGISTRY in apps/dashboard/lib/gateway-registry.ts:

beamr: { label: 'BEAMR', secretName: 'BEAMR_API_KEY', prefixes: [], domain: 'askbeamr.com' },

Per the header comment in that file, this one entry flows automatically to the GatewayProvider union, GATEWAY_SECRET_NAMES, the secrets route, auth key-detection, and the service icon. Empty prefixes is correct: BEAMR keys on an operator-supplied base URL rather than a distinctive key prefix, so it's picker-selected — exactly like Venice/UsePod.

3. Mirror Venice for the base URL. Because BEAMR's base URL is per-operator, model it on VENICE_BASE_URL: read a BEAMR_BASE_URL repo variable (not a secret) in the script. The venice) case in scripts/llm-gateway.sh is the closest precedent.

4. Wire the auto-resolver in scripts/llm-gateway.sh — this is missing today, so beamr can never actually be selected:

  • add beamr to the $GATEWAY value list (top-of-file comment),
  • add a beamr) arm to aeon_present(), e.g. beamr) [ -n "${BEAMR_BASE_URL:-}" ] ;;,
  • add beamr to the GATEWAY_ORDER default list,
  • keep your beamr) sidecar routing case, but follow fix(gateway): sidecar providers (Venice, Surplus) track aeon's $MODEL #461 and derive the pinned model from the resolved $MODEL the way Surplus/Venice now do, rather than hardcoding ${BEAMR_MODEL:-auto},
  • enforce https:// on BEAMR_BASE_URL so prompts can't egress in cleartext.

5. Resolve the secret-name collision with #419. The beamr-route skill already merged and defines BEAMR_GATEWAY_URL + BEAMR_PAYER_KEY (plus BEAMR_NETWORK, BEAMR_MAX_PAY_USDC). This PR introduces BEAMR_BASE_URL + BEAMR_API_KEY — two near-identical "BEAMR URL" secret families will confuse operators. Either reuse BEAMR_GATEWAY_URL for the gateway base URL, or give each a clearly distinct description. Please pick one and align.

6. Fix the dropdown claim. The README says BEAMR is selectable in the Authenticate modal, but PROVIDER_OPTIONS in apps/dashboard/components/AuthModal.tsx has no beamr entry. Either add { value: 'beamr', label: 'BEAMR' } there (Venice/UsePod are picker-selected too — follow their flow for collecting the base URL), or correct the README to "set BEAMR_BASE_URL in the Secrets tab."

Once it's rebased with the registry entry + auto-resolver + the #419 naming sorted, ping me and I'll re-review. Thanks again for the contribution — the hard part (correct tier + working endpoint path) is already done. 🙏

@aaronjmars

Copy link
Copy Markdown
Owner

Thanks for the contribution, @SahilParikh03 — the shell-level gateway case is a faithful clone of the Surplus/Venice pattern and the /api/v1/chat/completions path is correct. A few things block merging as-is, though, and I think the branch needs a rebase + rework rather than a small tweak:

1. The branch is stale (~106 commits behind main) and the dashboard architecture has since changed. Providers are now declared once in apps/dashboard/lib/gateway-registry.ts ("add a provider HERE and it flows everywhere"), and types.ts, constants.ts, secrets/route.ts, and auth-provider.ts all derive from it. This PR hand-edits the old scattered arrays that no longer exist in that form, so it conflicts in ~5 files. On current main the right change is essentially a single GATEWAY_REGISTRY entry.

2. The dropdown entry is missing, so the feature is unreachable. The README and secret descriptor say to select BEAMR "via the dropdown," but apps/dashboard/components/AuthModal.tsx PROVIDER_OPTIONS was never updated, and auth-provider.ts would reject provider: 'beamr' as unknown. There's also no auth-provider.test.mjs case (every other gateway has one).

3. This overlaps BEAMR work that's already merged. main already ships skills/beamr-route/ (with beamr-pay.mjs doing real x402 settlement via x402-fetch/viem) using secrets BEAMR_GATEWAY_URL / BEAMR_PAYER_KEY. This PR introduces a different var (BEAMR_BASE_URL) for the same service, and the gateway path here contains no payment code — so the "settles each call in USDC over x402, no new payment code" claim doesn't hold for this path; against a real x402-gated endpoint it would just receive a 402 and fail. It only works against an open/non-paid deployment.

If you'd still like to land a gateway version, could you:

  • rebase onto current main and re-implement as a single gateway-registry.ts entry,
  • add the AuthModal PROVIDER_OPTIONS option + an auth-provider.test.mjs case,
  • reconcile BEAMR_BASE_URL vs. the existing BEAMR_GATEWAY_URL, and
  • either wire real x402 payment into the gateway path or soften the README/PR wording to "works against an open/non-paid BEAMR deployment"?

Given the overlap with the already-merged beamr-route skill, it's worth a quick sanity check first on whether a second BEAMR surface is needed at all. Happy to take another look once it's rebased. Thanks again!

@aaronjmars

Copy link
Copy Markdown
Owner

Thanks @SahilParikh03 — the BEAMR integration itself reviews clean: the API key/base-URL are read from env (GitHub secrets, never hardcoded), the endpoint is https with no TLS-disable, shell vars are properly quoted (no injection), and GitGuardian passes. No security objections.

The blocker is staleness: this branch is ~123 commits behind main, and main has since refactored the gateway layer to an auto/GatewaySlug model — so the PR edits structures that no longer exist and conflicts. Could you rebase onto current main and re-wire BEAMR into the new model:

  • add beamr to GATEWAY_SLUGS in apps/dashboard/lib/types.ts (the flat 'direct' | ... union is gone)
  • add BEAMR to the auto resolver block in scripts/llm-gateway.sh (it now auto-detects providers by which secret is set, rather than a hardcoded case list)
  • update the secrets/sync map accordingly (syncGatewayProvider() now takes no args and defaults to auto)
  • and reconcile the UI/docs: PROVIDER_OPTIONS in apps/dashboard/components/AuthModal.tsx doesn't list beamr, but the README says it's selectable — add it to one or fix the other

Once it's rebased against the new gateway model, ping me and I'll re-review. Appreciate the contribution!

@aaronjmars aaronjmars closed this Jun 25, 2026
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.

3 participants