Skip to content

Revive the aimlapi aggregator build on current upstream - #1

Merged
Lookoff-AIMLAPI merged 27 commits into
masterfrom
feat/aimlapi-provider-2026-08
Aug 21, 2026
Merged

Revive the aimlapi aggregator build on current upstream#1
Lookoff-AIMLAPI merged 27 commits into
masterfrom
feat/aimlapi-provider-2026-08

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

Summary

Brings our branded aggregator build back onto current upstream and closes the remaining attribution gaps. The branch had drifted 95 commits behind and no longer applied.

Port to upstream's catalog-as-code registry. Upstream moved model facts out of blocks/llm.py into data/llm_registry/, emptying the exact file our dynamic AIMLAPI catalog lived in — so this is a port, not a merge resolution:

  • blocks/aiml_models.pydata/llm_registry/aiml_catalog.py. It is catalog code and now sits next to the catalog; leaving it under blocks/ would make llm_models import backend.blocks, whose package init pulls _base — an import cycle.
  • catalog.py keeps its hand-authored payload as _build_static_catalog(); _build_catalog() appends the loaded models. Injected entries never shadow a slug the static catalog declares.
  • llm_models.py registers a matching enum member per injected slug — _build_model_metadata() joins catalog to enum by slug, so a catalog entry without a member is invisible. LLMModel mixes in str, so members are built with str.__new__.
  • Slugs that already resolve are skipped: _missing_ strips the provider prefix, so anthropic/claude-sonnet-5 lands on the native member. 34 of 302 loaded models are shadowed this way — deliberate, so an aggregator entry never reroutes an existing graph.
  • MODEL_COST now comes from upstream's catalog-derived path (injected entries carry cost.run_credits); TOKEN_COST still bills from AIML_TOKEN_PRICING.
  • DEFAULT_LLM_MODEL still prefers an AIMLAPI model — this is the aggregator build, a fresh install must work with the one key the user has. Upstream's catalog recommendation stays the fallback.

Fix the base URL. aiml_api dispatched at https://api.aimlapi.com/v2, which 404s — that host only serves /v2 for billing/usage/keys, never chat. Verified: /v1/chat/completions → 401, /v2/chat/completions → 404.

Close the attribution gaps. X-AIMLAPI-Source + X-AIMLAPI-Partner-ID now go on every aimlapi.com request, not just inference: the boot-time catalog fetch (GET /v1/models) and the browser-side key check (GET /v1/billing/balance) were both going out bare. The browser one is only safe because the API preflights it — OPTIONS returns access-control-allow-headers: authorization,x-aimlapi-source,x-aimlapi-partner-id. NEXT_PUBLIC_* is inlined at build, so the partner id also needed a Dockerfile ARG.

Dropped: our SSR auth-token loopback fix — upstream fixed the same bug independently and better (BETTER_AUTH_INTERNAL_URL + a bounded hop).

Verification

  • 51 llm_registry tests + 84 providers tests pass.
  • Live: 275 AIMLAPI models injected (302 loaded, 27 shadowed), default resolves to openai/gpt-5.6-terra-pro via aiml_api.
  • Frontend tsc error count is identical to clean upstream/dev (1704) — zero introduced. All are pre-existing, from the ungenerated Orval client.
  • The base-URL test fails when the fix is reverted and passes with it (checked both ways).

aimlapihello and others added 27 commits August 4, 2026 12:51
…eaders

The AI/ML API provider pointed at https://api.aimlapi.com/v2, which does not
exist on the public API (all /v2 routes 404); chat completions therefore
failed. Point the provider at /v1, and send X-AIMLAPI-Source / X-AIMLAPI-Partner-ID
so provider-side attribution works. Extend the dispatch test to cover both.
…vider

Sort featured providers (AIML API) to the top of the builder integration
provider list, and show the provider as 'aimlapi.com' in the credentials UI.
…ld arg

Lets a hosted deployment bake its real public origin into the client bundle
(the browser API client calls ${NEXT_PUBLIC_FRONTEND_BASE_URL}/api/proxy).
Defaults to localhost; overridden via compose build.args on the dev server.
… origin

getServerAuthToken fetched ${BETTER_AUTH_URL}/api/auth/token — the Next server
calling itself via the PUBLIC url. Behind a reverse proxy the container cannot
reach its own public origin (hairpin), so every SSR token fetch hung to the 10s
timeout and login/signup failed. Target the loopback instead (AUTH_SELF_BASE_URL
override, default http://localhost:3000).
…integration surfaces

- Display name -> 'aimlapi.com' in the builder integration list, the Connect-a-
  service modal, the credential field, and the model picker provider label.
- New provider description (text/image/video/audio/embeddings, one key).
- Green 'Recommended' badge + pin aimlapi.com first in both the builder list
  (backend featured order) and the Connect-a-service modal (sort).
Backend: self-contained api/features/aimlapi module (config + service + router)
implementing the RFC 8628 device-authorization 'agent-auth' flow — POST
/api/aimlapi/authorize/{start,poll}; device code kept server-side, X-AIMLAPI-Source
(agent/autogpt) + X-AIMLAPI-Partner-ID on every call, source on the consent URL.

Frontend: 'Get API key' button in the aimlapi.com Connect-a-service form (opens
the consent tab, polls, auto-fills the key, green success / red error), prefilled
Name 'My aimlapi.com key'.
…API_INFERENCE_URL)

Lets a deployment point inference at staging (api-staging.aimlapi.com/v1) without
a code change; defaults to production.
…design

- window.open must not use noopener when we need the handle to redirect
  the tab; with noopener it returns null, leaving a blank about:blank page
- move the 'Have a key? Paste it here.' hint to a full-width label row
  (right-aligned) and drop the divider bars around 'or'
- provider descriptions in the connect list row and detail header wrap to
  two lines (line-clamp-2) instead of a single truncated line that ran off
  to the right; the list row grows (min-h) to fit the second line
- drop the extra 'Have a key? Paste it here.' hint on the AIMLAPI key field
- put the key input, 'or' and the button on one horizontal line and give
  the button the same rounding (rounded-xl) and height as the input
Fetch the AIMLAPI chat catalog from GET /v1/models at import (with a
committed snapshot fallback) and inject every non-duplicate model as an
aiml_api LlmModel member, so a single AIMLAPI key serves ~260 models
(OpenAI, Anthropic, DeepSeek, Qwen, GLM, Kimi, …) across all LLM blocks.

- new backend/blocks/aiml_models.py loader + data/aiml_models_snapshot.json
- llm.py: post-hoc member injection, skipping ids that already resolve to a
  native member so existing graphs are never rerouted; metadata + pricing +
  hottest set built for injected models
- block_cost_config.py: MODEL_COST/TOKEN_COST derived from catalog pricing
- frontend: aimlapi provider icon, is_hottest flag, and a "Hottest" section
  pinned to the top of the model picker
Surface only AIMLAPI-served models in the LLM Model picker and default new
blocks to an AIMLAPI model, so every model in a block runs on the single
aimlapi.com key instead of each provider's own key.

- llm.py: DEFAULT_LLM_MODEL now resolves to a flagship AIMLAPI model (with a
  native fallback so a missing catalog can't break import)
- LlmModelField: filter the picker to provider == "aiml_api"
- catalog loader dedupes API alias twins by (developer, display name), so a
  model exposed under several id spellings (e.g. gemini-3.6-flash vs
  gemini-3-6-flash, the 3 GLM 5.2 ids) appears once
- model picker: replace the flat "Hottest" list + single "Recommended" item
  with a single "Recommended" group (star) pinned first, holding all hottest
  models and reading like the creator groups below it
Extract the AIMLAPI device-grant flow into a shared useAimlapiGetApiKey hook
and reuse it in the builder's "Add new API key" modal (APIKeyCredentialsModal),
so the aimlapi.com provider gets the same "Get API key" button next to the key
field as the settings Connect-service form. Widen the button 1.5x in both forms.
Stagehand blocks only run OpenAI/Anthropic models (StagehandRecommendedLlmModel)
but their model credential used the shared AICredentials, which advertises every
LLM provider — surfacing Stagehand under each provider's integration (incl.
aimlapi.com) despite it not being usable there. Narrow the field to
Anthropic/OpenAI so Stagehand no longer appears under those integrations.
Add POST /api/aimlapi/validate-key: sends an empty /chat/completions request
(auth is checked before the body, so a bad key returns 401 while a valid key
returns 400 — no completion generated, zero cost). Both credential forms (the
settings Connect form and the in-builder API-key modal) verify a manually
entered aimlapi.com key on submit and show an error instead of saving an
invalid key. Fail open on network/backend errors so a real key is never blocked.
Drop the backend validate-key endpoint. The balance endpoint is CORS-enabled,
so the browser verifies an entered key directly (401 = invalid). Base URL comes
from NEXT_PUBLIC_AIMLAPI_API_URL (prod default) so a staging deploy checks
against staging.
Port the AIMLAPI catalog onto upstream's catalog-as-code registry.

Upstream moved model facts out of `blocks/llm.py` into
`data/llm_registry/` (catalog.py holds the models, registry.py the
in-process view, llm_models.py the enum + projections). Our dynamic
catalog lived in the file that got emptied, so this is a port, not a
merge resolution:

* `blocks/aiml_models.py` -> `data/llm_registry/aiml_catalog.py` (with
  its snapshot). It is catalog code and now sits next to the catalog;
  keeping it under `blocks/` would have made `llm_models` import
  `backend.blocks`, whose package init pulls `_base` — an import cycle.
* `catalog.py` keeps its hand-authored payload as `_build_static_catalog()`
  and `_build_catalog()` appends the loaded AIMLAPI models. Injected
  entries never shadow a slug the static catalog declares.
* `llm_models.py` registers a matching enum member per injected slug —
  `_build_model_metadata()` joins catalog to enum BY SLUG, so a catalog
  entry without a member is invisible. Slugs that already resolve
  (directly or via `_missing_`'s provider-prefix strip) are skipped so an
  aggregator entry never reroutes an existing graph through AIMLAPI.
* `MODEL_COST` now comes from upstream's catalog-derived path; the
  injected entries carry `cost.run_credits`, so our old manual loop is
  gone. `TOKEN_COST` still bills from `AIML_TOKEN_PRICING`.
* `DEFAULT_LLM_MODEL` still prefers an AIMLAPI model — this is the
  aggregator build, a fresh install must work with the one key the user
  has. Upstream's catalog recommendation stays the fallback, and
  `llm_models_test` now asserts that contract instead of the native
  default.

Dropped: our SSR auth-token loopback fix. Upstream fixed the same bug
independently and better (`BETTER_AUTH_INTERNAL_URL` + a bounded hop).

Verified: 51 llm_registry tests + 84 providers tests pass; the live
catalog injects 275 AIMLAPI models (302 loaded, 27 shadowed by native
slugs) and the default resolves to openai/gpt-5.6-terra-pro via aiml_api.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HEADERS.md requires X-AIMLAPI-Source + X-AIMLAPI-Partner-ID on EVERY
aimlapi.com request — "inference/API calls, catalog, checkout, and auth
alike, not just login/sign-up". The catalog loader called GET /v1/models
bare, so the one request every boot makes was the one request that went
unattributed.

Reuses `attribution_headers()` from the feature config rather than
restating the pair, so the id cannot drift between the two call sites.
The import is function-local: that module is stdlib-only, and keeping it
out of the module header avoids `data/` taking a load-time dependency on
`api/`.

Verified live: the fetch now carries both headers and still returns the
full 302-model catalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last aimlapi.com call that went out unattributed. HEADERS.md wants
the pair on every request; this one is issued from the browser, so it
cannot inherit the backend client's default headers and has to carry
them itself.

Custom headers turn this from a simple request into a preflighted one,
so this is only safe because the API allows them: OPTIONS on
/v1/billing/balance returns access-control-allow-headers
"authorization,x-aimlapi-source,x-aimlapi-partner-id" and reflects the
caller's origin. Verified against production, and the GET still answers
401 for a bad key — the status this function keys off is unchanged.

The partner id also needs a Dockerfile ARG: Next inlines NEXT_PUBLIC_*
at build time, so without it the documented env override could never
reach the bundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This PR targets the master branch but does not come from dev or a hotfix/* branch.

Automatically setting the base branch to dev.

@github-actions github-actions Bot added platform/blocks cla: pending CLA not yet signed by all contributors size/xl labels Aug 21, 2026
@Lookoff-AIMLAPI
Lookoff-AIMLAPI merged commit d00338e into master Aug 21, 2026
16 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants