Revive the aimlapi aggregator build on current upstream - #1
Merged
Conversation
…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
This reverts commit 5947232.
…atalog"" This reverts commit 12dca57.
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.
… providers" This reverts commit 19cd696.
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>
|
This PR targets the Automatically setting the base branch to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyintodata/llm_registry/, emptying the exact file our dynamic AIMLAPI catalog lived in — so this is a port, not a merge resolution:blocks/aiml_models.py→data/llm_registry/aiml_catalog.py. It is catalog code and now sits next to the catalog; leaving it underblocks/would makellm_modelsimportbackend.blocks, whose package init pulls_base— an import cycle.catalog.pykeeps 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.pyregisters 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.LLMModelmixes instr, so members are built withstr.__new__._missing_strips the provider prefix, soanthropic/claude-sonnet-5lands on the native member. 34 of 302 loaded models are shadowed this way — deliberate, so an aggregator entry never reroutes an existing graph.MODEL_COSTnow comes from upstream's catalog-derived path (injected entries carrycost.run_credits);TOKEN_COSTstill bills fromAIML_TOKEN_PRICING.DEFAULT_LLM_MODELstill 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_apidispatched athttps://api.aimlapi.com/v2, which 404s — that host only serves/v2for billing/usage/keys, never chat. Verified:/v1/chat/completions→ 401,/v2/chat/completions→ 404.Close the attribution gaps.
X-AIMLAPI-Source+X-AIMLAPI-Partner-IDnow 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 —OPTIONSreturnsaccess-control-allow-headers: authorization,x-aimlapi-source,x-aimlapi-partner-id.NEXT_PUBLIC_*is inlined at build, so the partner id also needed a DockerfileARG.Dropped: our SSR auth-token loopback fix — upstream fixed the same bug independently and better (
BETTER_AUTH_INTERNAL_URL+ a bounded hop).Verification
llm_registrytests + 84providerstests pass.openai/gpt-5.6-terra-proviaaiml_api.tscerror count is identical to clean upstream/dev (1704) — zero introduced. All are pre-existing, from the ungenerated Orval client.