feat: add aimlapi.com as an OpenAI-compatible gateway provider - #1
Open
Lookoff-AIMLAPI wants to merge 5 commits into
Open
feat: add aimlapi.com as an OpenAI-compatible gateway provider#1Lookoff-AIMLAPI wants to merge 5 commits into
Lookoff-AIMLAPI wants to merge 5 commits into
Conversation
DeepTutor could already reach AI/ML API only by typing a "custom" profile by hand: the endpoint, the env var and the capability flags all had to be rediscovered by each user, and nothing in the registry told the config loader that an https://api.aimlapi.com/v1 base belongs to a gateway that routes any model. Registering it once is what makes env resolution, api_base detection and the status display work the way they already do for the other aggregators in the same block. No detect_by_key_prefix: AI/ML API keys carry no distinguishing prefix, so the endpoint is the only signal that will not misfire on someone else's key. No strip_model_prefix: model ids keep their vendor prefix ("openai/gpt-4o-mini"), unlike aihubmix. The wizard fallback ids were each checked against GET https://api.aimlapi.com/v1/models (2026-09-03), filtered to type == "openai/chat-completions" and matched against both ids and aliases, rather than copied from another aggregator's list.
AI/ML API reads the same HTTP-Referer / X-Title pair OpenRouter does, plus X-AIMLAPI-Partner-ID and X-AIMLAPI-Source, to tell which application sent a request. Without them DeepTutor's traffic is indistinguishable from anyone else's, which is what the existing OPENROUTER_ATTRIBUTION_HEADERS block already exists to avoid for the other gateway. The scoping is deliberately narrower than the OpenRouter check next to it. That one matches a substring of the URL, which also fires for a proxy whose path merely contains the vendor name; this one compares the *host* of the resolved endpoint against an exact allow-list, so the headers cannot ride a request to api.aimlapi.com.evil.io, to notaimlapi.com, or to a self-hosted proxy configured under an "aimlapi" binding. Headers are merged into a per-call dict rather than assigned, so a profile's own extra_headers still win and the module constant is never mutated. The partner id fails silently when malformed — an unparseable value is accepted and simply earns nothing — so its shape is asserted in a test rather than left to review, alongside a walk over every other ProviderSpec proving none of them carries an X-AIMLAPI-* header.
A /models payload may list one model id once per endpoint family it serves, so the same name arrives more than once. AI/ML API returns 936 rows for 785 distinct ids; every repeat became a separate, identical row in the model picker with no way for the user to tell them apart. First occurrence wins, so payload order — which providers use to put their preferred models first — is preserved.
Partnership placement, kept as one commit so it can be dropped wholesale before anything is offered upstream. Nothing here changes behaviour that a user can observe beyond ordering: - aimlapi.com moves to the head of the gateway block in PROVIDERS. The block is hand-ordered and the file's own docstring says order controls match priority; every gateway in it is distinguished by an unambiguous api_base keyword, so the move changes which entry is offered first, not which one is detected. - aimlapi.com is added at the head of FEATURED_LLM_PROVIDERS, the wizard's existing hand-picked shortlist. That list is the repo's own "featured" mechanism; no new badge concept was invented for it. The wizard's default selection is untouched — it still falls back to openai unless the user's current binding is something else.
The placeholder part_deeptutor was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_ItAs0L5uSTvV2dFDOZZaS1BL. 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) to the provider registry as an OpenAI-compatible
gateway, sends the attribution headers it reads, and fixes a
/modelsde-duplicationbug that its catalog exposes.
Before this, reaching AI/ML API from DeepTutor meant hand-typing a
customprofile:the endpoint, the env var and the capability flags had to be rediscovered by every
user, and nothing told the config loader that an
https://api.aimlapi.com/v1basebelongs to a gateway that routes any model.
Commits
feat: add aimlapi.com as an OpenAI-compatible gateway providerProviderSpecin the gateway block, capability entry, wizard fallback models, registry testfeat: attribute DeepTutor traffic to aimlapi.com by endpoint hostfix: drop repeated ids from a provider's /models listcollect_model_nameschore(aimlapi): fork-only placement — do not send upstreamProvider registration
Shaped after the four aggregator peers already in that block (
openrouter,orcarouter,edenai,aihubmix). Two deliberate omissions:detect_by_key_prefix— AI/ML API keys carry no distinguishing prefix, soa prefix rule would either never fire or misfire on another vendor's key.
strip_model_prefix— model ids keep their vendor prefix(
openai/gpt-4o-mini), unlikeaihubmix.Only
POST /v1/chat/completionsandPOST /v1/responsesexist upstream; there is no/v1/completions, and none is declared.Attribution headers
AI/ML API reads the same
HTTP-Referer/X-Titlepair OpenRouter does, plusX-AIMLAPI-Partner-IDandX-AIMLAPI-Source. They are added inopenai_sdk_client_kwargs, right next to the existingOPENROUTER_ATTRIBUTION_HEADERSblock, which is the repo's own precedent for this.Both
HTTP-RefererandX-Titlename DeepTutor, not the gateway — they identifythe calling application.
The scoping is deliberately narrower than the OpenRouter check beside it. That one
matches a substring of the URL; this one compares the host of the resolved
endpoint (configured
api_base, else the spec'sdefault_api_base) against an exactallow-list. Tests cover the cases a substring match would get wrong:
https://api.aimlapi.com.evil.io/v1→ no headershttps://notaimlapi.com/v1→ no headershttps://aimlapi.com.attacker.example/v1→ no headershttps://gateway.internal.example/aimlapi/v1→ no headersaimlapi-typed profile pointed athttps://proxy.example.com/v1→ no headersHeaders are merged into a per-call dict, never assigned: a profile's own
extra_headersstill win on a key clash, and a test asserts the module constant isunchanged after a call. A further test walks every other
ProviderSpecandasserts none of them emits an
X-AIMLAPI-*header.The partner id fails silently when malformed — an unparseable value is accepted by
the gateway and simply earns nothing — so its shape is asserted in a test
(
^part_[A-Za-z0-9]{1,64}$), as is the source (^(web|agent|mcp)/[a-z0-9-]{1,32}$)./modelsde-duplicationA
/modelspayload may list one model id once per endpoint family it serves.GET https://api.aimlapi.com/v1/modelsreturns 936 rows for 785 distinct ids, andcollect_model_namesturned each repeat into a separate, identical row in the modelpicker — 151 duplicates, indistinguishable to the user. First occurrence now wins, so
payload order is preserved. This is provider-neutral: a payload with no repeats is
unaffected.
Model ids
Every id shipped in the wizard fallback list was checked against the live catalog on
2026-09-03 — against both
idandaliases, and filtered totype == "openai/chat-completions"(353 of the 936 rows). None was copied fromanother aggregator's list.
openai/gpt-4o-mini,openai/gpt-5-5,anthropic/claude-sonnet-4.5,deepseek/deepseek-chat— all four present as chat ids.Module(s) Affected
servicestestsdeeptutor_cliVerification
Build —
pip install -e ".[dev,partners]"on Python 3.12, clean.Tests — full suite, baseline captured on a pristine checkout of
main(
93df3d48) before any change:93df3d48)The 33 failures are the identical set in both runs (
diffof the sortedFAILEDlines is empty) — pre-existing, in
tests/agents/question/test_pipeline.py,tests/capabilities/,tests/api/test_canonical_route_surface.py,tests/runtime/test_isolated_worker.py,tests/services/sandbox/test_sandbox.py.The 18 new passes are the tests added here.
Lint / CI gates, all run locally at the versions CI pins:
ruff==0.16.0 check .clean ·ruff format --check .clean ·import-linter==2.11 lint-imports3 contracts kept ·mypy(hook flags) clean onevery changed non-test file ·
bandit -c pyproject.tomlclean ·scripts/check_repo_hygiene.pypassed ·detect-secretsadds no finding that thepristine files do not already produce.
One real inference call, through the code path added — not a mock, not curl:
LLMConfig(binding="aimlapi") → get_runtime_provider() → OpenAICompatProvider.chat().Tool calling exercised in the same run and returns a well-formed call.
Model discovery exercised through
cloud_provider.fetch_modelsagainst the liveendpoint: 785 names after the de-dup fix, 936 before it.
Not verified
web/file is touched, sonpm run check/ the web audit job werenot run.
ProviderIconhas noaimlapientry and falls back to the generic mark,which is what
edenai,novita,atlascloudandorcarouteralready do — no iconwas vendored rather than inventing one.
provider_runtime.pyare untouched; only the chat surface is claimed./v1/responses. The spec leavesapi_formatatauto, so DeepTutor uses ChatCompletions. The Responses endpoint exists at AI/ML API but is not exercised here.
Checklist
README.mdcarries no provider table to extend.Note for whoever takes this upstream
CONTRIBUTING.mdis explicit about branch targeting:Important
Please do not submit PRs directly to
main. All contributions should targetdevormulti-user.This PR targets the fork's own default branch (
main) per our fork workflow. Anupstream PR must target
dev, and must drop thechore(aimlapi): fork-only placementcommit first.