feat(models): add aimlapi.com as a preset model provider - #1
Open
Lookoff-AIMLAPI wants to merge 4 commits into
Open
feat(models): add aimlapi.com as a preset model provider#1Lookoff-AIMLAPI wants to merge 4 commits into
Lookoff-AIMLAPI wants to merge 4 commits into
Conversation
AI/ML API is an OpenAI-compatible aggregator that fronts several hundred models from many vendors behind a single key, which is the same reason OpenRouter, Novita and PPIO are already preset here: users otherwise have to know and retype the base URL under "Custom". The brand is written lowercase with its TLD, so the display name is the domain itself. It is a brand, not translatable copy, so it carries no i18nKey — the same choice as every other vendor entry in this list. Base URL is the OpenAI-compatible chat surface at /v1. The provider has no /v1/completions endpoint, so `platform: 'custom'` (which routes through the OpenAI chat-completions client) is the only correct wiring. The logo is bundled locally rather than requested from the backend logo service, because that service has no ai-cloud/aimlapi.svg to serve and a missing asset renders as a broken image rather than falling back.
The OpenAI-compatible client sent one hardcoded header pair to every vendor. Vendors that run a partner programme also want their own headers, and there was no place to put them. Headers are now keyed by the request origin rather than by the configured platform name. Origin is the right key: a user can point any platform entry at any base URL, and a proxy that fronts a vendor's API under a different hostname must not receive that vendor's partner headers — nor should one vendor's headers ride a request to another. The headers are merged into, not assigned over, whatever the caller passed in baseConfig; previously a caller supplying its own defaultHeaders silently dropped HTTP-Referer and X-Title entirely. Each call builds a fresh object so the shared constants cannot be mutated through the returned map. HTTP-Referer and X-Title keep identifying AionUi, which is what they are for — they name the calling application, not the vendor being called. The partner id shape is asserted in a test because a malformed one is not rejected by the gateway: the request succeeds and the attribution is silently discarded, so nothing at runtime would ever surface a typo.
Moves the aimlapi.com entry from the end of MODEL_PLATFORMS to index 1, directly behind the Custom placeholder, so it leads the provider picker. This is placement, not function: nothing here changes how the provider works, and it displaces an entry the file's own comment marks as a strategic partner pin. It is therefore isolated in a single commit that can be dropped before this work is offered upstream, leaving the provider and its attribution intact. Index 0 is deliberately untouched: DEFAULT_PLATFORM_VALUE reads MODEL_PLATFORMS[0], so taking that slot would change which platform the add-model dialog opens on. The ordering test is updated in the same commit — dropping this commit drops the assertion with it.
The placeholder part_aionui was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_UJK4IAHBjvT9g4cPDrb7B7KT. 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 as a preset model provider, and gives the OpenAI-compatible client a place to put per-vendor attribution headers.
AI/ML API is an OpenAI-compatible aggregator: one key, one base URL, several hundred models from many vendors. That is the same shape as OpenRouter, Novita and PPIO, which are already presets here — without an entry, users have to know and retype the base URL under Custom.
Three commits:
feat(models): add aimlapi.com as a preset model provider— one entry inMODEL_PLATFORMSplus its logo.feat(api): scope partner attribution headers to the provider origin—ClientFactorypreviously hardcoded one header pair for every vendor with nowhere to put a vendor's own. Headers are now keyed by request origin.chore(aimlapi): fork-only placement — do not send upstream— moves the entry to the top of the picker. Placement only; drop this commit before offering the rest upstream.Display name
The entry renders as
aimlapi.com— the brand is written lowercase with its TLD. It is a brand, not translatable copy, so like every other vendor row it carries noi18nKey. The storedvalueisAIMLAPI, matching theOpenRouter/SiliconFlowconvention.Base URL
https://api.aimlapi.com/v1, the OpenAI-compatible chat surface.platform: 'custom'routes it through the OpenAI chat-completions client, which is correct: this API has no/v1/completionsendpoint (it 404s), so the chat-completions path is the only one to declare.Attribution headers
HTTP-RefererandX-Titlewere already sent app-wide and keep identifying AionUi — that is what they are for; they name the calling application, not the vendor. The two vendor-specific headers (X-AIMLAPI-Partner-ID,X-AIMLAPI-Source) are added only forhttps://api.aimlapi.com.Keying on origin rather than on the configured platform name is deliberate: a user can point any platform entry at any base URL, so a name-keyed map would let one vendor's partner headers ride a request to another vendor, or to a proxy that fronts the same API under a different hostname. A unit test asserts exactly that for
api.openai.com,openrouter.ai, a look-alike proxy path,localhostand an unparsable URL.Two smaller fixes fall out of the same change:
baseConfig.defaultHeadersinstead of being assigned over. Previously a caller supplying its owndefaultHeaderssilently droppedHTTP-RefererandX-Titleentirely.buildDefaultHeaders()returns a fresh object each call, so the module-level constants cannot be mutated through the returned map. Covered by a test.The partner-id shape is asserted against
/^part_[A-Za-z0-9]{1,64}$/in a test because a malformed id is not rejected by the gateway: the request succeeds and the attribution is silently discarded, so nothing at runtime would ever surface a typo.Logo
Bundled locally at
packages/desktop/src/renderer/assets/logos/aimlapi.svgrather than requested from the backend logo service, because that service has noai-cloud/aimlapi.svgto serve and a missing asset renders as a broken image rather than falling back to the cloud placeholder. Vite inlines it (< 4 KB) as adata:URI, soThemedLogorenders it through the plain<img>path with no network fetch — verified in the production bundle.Type of Change
feat— New feature (non-breaking change which adds functionality)Atomic PR Checklist (Rule 1)
<type>(<scope>): <subject>(English)Local Checks (Rule 3)
bun run format— formatting passes (format:check: "All matched files use the correct format")bun run lint— 913 warnings, 0 errors — byte-identical to the pre-change baselinebunx tsc --noEmit— no type errorsbunx vitest run— see numbers belowbun run i18n:types+node scripts/check-i18n.js) — both pass; no locale files changedjust push's full gate (lint-strict→fmt-check→typecheck→i18n-check→test) was run recipe by recipe;justitself is not installed on the build machine.Verification
Tests
main)Ten new tests, zero regressions, no pre-existing failures on either side.
Build
bun run packagesucceeds on both baseline and after (exit 0, checked bare — not through a pipe).node scripts/build-mcp-servers.jsalso succeeds; the attribution constants are present in the shippedout/main/builtin-mcp-image-gen.js, which is the bundle that actually carriesClientFactory.Live inference
One real call and one tool-calling round trip through the code path this PR adds —
getPlatformByValue('AIMLAPI')→ClientFactory.createRotatingClient→OpenAIRotatingClient→ realPOST /v1/chat/completions. Not a mock. Key redacted.All four attribution headers reach the wire; tool calling works.
Model ids
This PR ships no model list — AionUi fetches the catalog live from the provider, so there are no hardcoded ids to go stale. The one id used in the live probe,
openai/gpt-4o-mini, was checked against the live catalog as an id and as an alias (936 rows / 785 distinct ids; 353 rows oftype == "openai/chat-completions", all distinct).Runtime Verification
Not run as a packaged desktop app on any platform. What was verified on macOS: the production build, the full test suite, and a real inference call through the added code path. The unverified part is purely visual — how the new row draws in the picker.
What could not be verified
imageModelAllowlist.tsgates the built-in image tool to providers known to support "form B" (images returned inline from chat completions). Whether this provider's image models answer in that shape was not tested, so no allowlist rule was added — a guess there would surface dropdown entries that fail at runtime, which is the exact failure that file exists to prevent./api/providers/fetch-modelsand protocol detection are all served by the AionCore backend binary, which lives outside this repository — the headers added here cannot reach those requests.