feat(providers): add aimlapi.com as a gateway - #1
Open
Lookoff-AIMLAPI wants to merge 4 commits into
Open
Conversation
AI/ML API is an OpenAI-compatible router over ~350 chat models, so it needs no new backend: the ProviderSpec plus the matching ProvidersConfig field is the whole wiring, exactly as Requesty and Forge landed in HKUDS#159. Both halves are required — ProvidersConfig is read via getattr(providers, spec.name), so a spec without a field is silently skipped everywhere. The attribution block follows _DEFAULT_OPENROUTER_HEADERS / _DEFAULT_REQUESTY_HEADERS, with one deliberate difference: the two existing helpers accept any base URL containing the provider's name, which would also fire for a third-party proxy that fronts the gateway. _uses_aimlapi_attribution requires the resolved base URL's host to be ours, so DeepCode's identity does not travel to someone else's endpoint. The partner id fails silently when malformed — the gateway answers 200 and records the traffic as untagged — so its shape is asserted in a test rather than left to be noticed in production. Verified against the live gateway: chat, tool calling and streaming all return through core.config.make_llm_provider, and cache_control markers come back with cached_tokens in usage, which is why supports_prompt_caching is set. Model ids were checked against the published catalog by id AND alias.
The gateway publishes one directory for every endpoint family it hosts. An unfiltered GET /v1/models returns 936 rows for 785 distinct ids — image, video, speech-to-text and embedding endpoints included, with ids repeating across families — so the model picker offered 173 rows that a chat request rejects, and offered several of them twice. Asking for the chat surface narrows that to 353 unique, usable models. Discovery also invented context windows for all of them: the gateway nests its numbers under `info` rather than publishing OpenRouter's flat keys, so every model fell through to the built-in 128k/8k fallback. A 1M-context model was budgeted as 128k and a 32k one as 128k, which is the direction that overflows mid-turn. The nested read is placed after the flat names, so a provider that publishes both is unaffected. Numbers verified against the live directory through ModelCatalogService.
Moves the aimlapi.com spec to position 0 of PROVIDERS and its ProvidersConfig field to the front. Every provider list DeepCode renders iterates PROVIDERS in declaration order, so this is placement, not behaviour: no badge is invented, because the repo has no featured/recommended concept to reuse, and nothing else is reordered. Kept as its own commit so it can be dropped whole before the provider is offered upstream. The test that pins position 0 lives in this commit too, so dropping it leaves no dangling assertion.
The placeholder part_deepcode was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_CxcOejScJ3hI0O2cExWchiBy. A wrong or unknown partner id is accepted with a 200 and silently not attributed, so this would not have surfaced at runtime.
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.
Description
Adds aimlapi.com (AI/ML API) as an OpenAI-compatible gateway, following the
shape of PR HKUDS#159 (Requesty / MiniMax / Forge) exactly: a
ProviderSpec, thematching
ProvidersConfigfield, an attribution-header block next to theOpenRouter and Requesty ones, and a test file mirroring
tests/test_requesty_provider.py+tests/test_forge_provider.py.No new backend is needed —
backend="openai_compat",is_gateway=True.Related Issues
None.
Changes Made
74fea880—feat(providers): add aimlapi.com as a gatewaycore/providers/registry.py— oneProviderSpec(
name="aimlapi",display_name="aimlapi.com",env_key="AIMLAPI_API_KEY",default_api_base="https://api.aimlapi.com/v1",is_gateway=True,supports_prompt_caching=True).core/config.py— the matchingProvidersConfigfield.ProvidersConfigisread via
getattr(providers, spec.name), so a spec without a field issilently skipped; both halves are required.
core/providers/openai_compat.py—_DEFAULT_AIMLAPI_HEADERSand_uses_aimlapi_attribution, wired at the same call site as the OpenRouterand Requesty blocks.
tests/test_aimlapi_provider.py— registry, config and attribution.3828e51d—fix(catalog): scope aimlapi.com discovery to the chat surfacecore/providers/catalog_service.py— two discovery fixes, both measuredagainst the live directory:
Unfiltered,
GET /v1/modelsreturned 936 rows for 785 distinct ids —image, video, speech-to-text and embedding endpoints included, with ids
repeating across families — so the
/modelpicker offered 173 rows that achat request rejects, several of them twice. Scoped to the chat surface it
is 353 unique, usable models.
contextLength/outputMax/nameunderinfoinstead of publishing OpenRouter's flat keys, so every model fell through
to the built-in 128k/8k fallback. A 1M-context model was budgeted as 128k;
a 32k model was also budgeted as 128k, which is the direction that
overflows mid-turn. The nested read is placed after the flat names, so a
provider publishing both is unaffected (covered by a test).
aae6a658—chore(aimlapi): fork-only placement — do not send upstreamPlacement only: moves the spec to position 0 of
PROVIDERSand its configfield to the front. Every provider list DeepCode renders iterates
PROVIDERSin declaration order. No badge was invented — the repo has no
featured/recommended concept to reuse — and nothing else was reordered. It is
isolated as the last commit, with its own pinning test, so it can be dropped
whole before the provider is offered upstream.
Attribution headers
_uses_aimlapi_attributiondiffers deliberately from the two helpers above it._uses_openrouter_attribution/_uses_requesty_attributionaccept any baseURL containing the provider's name, which would also fire for a third-party
proxy that merely fronts the gateway. This one requires the resolved base
URL's host to be ours, so DeepCode's identity does not travel to someone
else's endpoint. Lookalike hosts (
api.aimlapi.com.example.io,notaimlapi.com) are covered by tests.Headers are merged into a per-instance dict, never assigned over: a caller's
own
extraHeaderswin on a key clash, the module-level constant is nevermutated, and both are asserted. The partner id fails silently when
malformed — the gateway answers 200 and records the traffic as untagged — so
its shape is pinned by a regex test rather than left to be noticed in
production.
Checklist
docs/; theregistry
display_nameis the only label surface. The README news entryis written by maintainers per release, as it was for feat(providers): add Requesty, MiniMax and Forge HKUDS/DeepCode#159.
tests/test_aimlapi_provider.py, 20 tests)Verification
Build —
python -m buildexit 0;twine check dist/*PASSED on both thewheel and the sdist.
Tests —
python -m pytest -q, CPython 3.13.15, macOS:main, pristine worktree)The failure set is byte-identical (
diffof theFAILEDlines is empty) andall five are environment, not code: they shell out to the system
python3(3.14, which has no
pytestinstalled) or tonpm.Separately, this tree has pre-existing flaky concurrency tests — lease,
claim-ownership and scheduler cases that fail non-deterministically under load
and pass in isolation. A second pristine-
mainrun produced 6 failures(
test_execution_coordination_integration), and branch runs produced 6 and 9.Every extra failure passed when re-run on its own. This is not caused by the
change, but it is worth knowing before reading a red CI run.
Live inference — one real call through
core.config.make_llm_provider(...), i.e. the same path the CLI, TUI and AppServer use. Not a mock, not raw curl. Key passed via env; never written to the
tree.
Prompt caching —
supports_prompt_caching=Trueis not copied fromOpenRouter, it was measured. Two identical 2,410-token requests through
OpenAICompatProvider:Model ids — every id named in this diff or in its tests was checked against
the live catalog by
idandaliases, not byidalone:openai/gpt-4o-mini,anthropic/claude-sonnet-4.5andalibaba/glm-5.2areall present as ids. No model list is hard-coded — the catalog is fetched live —
so there is no static array to go stale.
Lint —
ruff check --ignore=E402andruff format --checkclean at thepinned
.pre-commit-config.yamlversion (v0.15.21).Additional Notes
Not verified, and why:
through
PROVIDERS(settings_service._view,llm_configuration_service), which is exercised by the Python tests, but theElectron app itself was not launched.
GET /v1/modelson this gateway returns 200 for abogus key — only
POST /v1/chat/completionsreturns 401. Nothing in thisdiff validates a key by listing models, and nothing should: DeepCode's
test_connectionalready reportsokon a successful catalog fetch, so atypo'd key will read as a good connection until the first turn. That is
pre-existing behaviour for every provider here and is out of scope for this
PR.