Skip to content

feat(models): add aimlapi.com as a preset model provider - #1

Open
Lookoff-AIMLAPI wants to merge 4 commits into
mainfrom
feat/aimlapi-provider
Open

feat(models): add aimlapi.com as a preset model provider#1
Lookoff-AIMLAPI wants to merge 4 commits into
mainfrom
feat/aimlapi-provider

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

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:

  1. feat(models): add aimlapi.com as a preset model provider — one entry in MODEL_PLATFORMS plus its logo.
  2. feat(api): scope partner attribution headers to the provider originClientFactory previously hardcoded one header pair for every vendor with nowhere to put a vendor's own. Headers are now keyed by request origin.
  3. 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 no i18nKey. The stored value is AIMLAPI, matching the OpenRouter / SiliconFlow convention.

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/completions endpoint (it 404s), so the chat-completions path is the only one to declare.

Attribution headers

HTTP-Referer and X-Title were 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 for https://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, localhost and an unparsable URL.

Two smaller fixes fall out of the same change:

  • Headers are now merged into baseConfig.defaultHeaders instead of being assigned over. Previously a caller supplying its own defaultHeaders silently dropped HTTP-Referer and X-Title entirely.
  • 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.svg 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 to the cloud placeholder. Vite inlines it (< 4 KB) as a data: URI, so ThemedLogo renders 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)

  • This PR contains exactly one feature or bug fix that cannot be further decomposed
  • The PR title follows Conventional Commit format: <type>(<scope>): <subject> (English)

Honest answer on Rule 1: this is one feature — the provider — in three commits, but the header hook in commit 2 is independently mergeable and the placement in commit 3 is deliberately separable. Upstream this should be split; here they travel together on purpose so the fork-only placement can be dropped as one commit.

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 baseline
  • bunx tsc --noEmit — no type errors
  • bunx vitest run — see numbers below
  • i18n validated (bun run i18n:types + node scripts/check-i18n.js) — both pass; no locale files changed
  • New/changed user-facing text uses i18n keys — N/A: the only new string is a brand name, consistent with every other vendor row

just push's full gate (lint-strictfmt-checktypechecki18n-checktest) was run recipe by recipe; just itself is not installed on the build machine.

Verification

Tests

Test files Tests
Baseline (pristine main) 520 passed, 1 skipped 4980 passed, 5 skipped
After this change 520 passed, 1 skipped 4990 passed, 5 skipped

Ten new tests, zero regressions, no pre-existing failures on either side.

Build

bun run package succeeds on both baseline and after (exit 0, checked bare — not through a pipe). node scripts/build-mcp-servers.js also succeeds; the attribution constants are present in the shipped out/main/builtin-mcp-image-gen.js, which is the bundle that actually carries ClientFactory.

Live inference

One real call and one tool-calling round trip through the code path this PR adds — getPlatformByValue('AIMLAPI')ClientFactory.createRotatingClientOpenAIRotatingClient → real POST /v1/chat/completions. Not a mock. Key redacted.

registry entry -> {"name":"aimlapi.com","value":"AIMLAPI","base_url":"https://api.aimlapi.com/v1","platform":"custom"}

--> POST https://api.aimlapi.com/v1/chat/completions
    headers: {"accept":"application/json","authorization":"Bearer <redacted>",
              "content-type":"application/json","http-referer":"https://aionui.com",
              "user-agent":"OpenAI/JS 5.23.2","x-aimlapi-partner-id":"part_aionui",
              "x-aimlapi-source":"agent/aionui","x-title":"AionUi", ...}
<-- 200 OK
CHAT model=gpt-4o-mini-2024-07-18 finish=stop
CHAT content="AionUi live check OK"
CHAT usage={"prompt_tokens":17,"completion_tokens":6,"total_tokens":23}

--> POST https://api.aimlapi.com/v1/chat/completions
<-- 200 OK
TOOLS finish=tool_calls
TOOLS calls=[{"id":"call_ZAyHRX7IqJNKfSppsWFED9IZ","type":"function",
             "function":{"name":"get_weather","arguments":"{\"city\":\"Prague\"}"}}]

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 of type == "openai/chat-completions", all distinct).

Runtime Verification

  • Verified on macOS
  • Verified on Windows
  • Verified on Linux
  • I have performed a self-review of my own code

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

  • Image generation. imageModelAllowlist.ts gates 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.
  • Chat traffic attribution. In this repo the only code path that issues LLM requests through the OpenAI SDK is the built-in image-generation MCP server. Conversations, /api/providers/fetch-models and protocol detection are all served by the AionCore backend binary, which lives outside this repository — the headers added here cannot reach those requests.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant