Skip to content

feat(providers): add aimlapi.com as an OpenAI-compatible provider - #1

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

feat(providers): add aimlapi.com as an OpenAI-compatible provider#1
Lookoff-AIMLAPI wants to merge 3 commits into
mainfrom
feat/aimlapi-provider

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

Summary

Why

Comparing vendors mid-research currently means one account, one key and one .env block per vendor. AI/ML API is an OpenAI-compatible aggregator fronting ~350 chat models behind a single key, so it slots into the existing registry as data.

The header scoping is the part that needed design rather than data. capabilities.default_headers is selected by the provider label, and AIMLAPI_BASE_URL — or an ambient OPENAI_BASE_URL, which get_llm_credentials also falls back to — can point that label at a corporate proxy or a look-alike host. A partner identifier must not follow it there. The gate is therefore an exact hostname allowlist, mirroring the existing _openai_label_points_at_openai check a few lines above it. A substring or suffix test would have accepted api.aimlapi.com.evil.io; that and notaimlapi.com are both covered by tests.

Changes

Commit 1 — feat(providers): add aimlapi.com as an OpenAI-compatible provider (3164c3c8)

  • agent/src/providers/capabilities.py_AIMLAPI_CAPABILITIES, plus AIMLAPI_ATTRIBUTION_HEADERS (a read-only MappingProxyType) and AIMLAPI_ATTRIBUTION_HOSTS.
  • agent/src/providers/llm.py_aimlapi_attribution_headers(caps, base_url). The build_llm header block now merges attribution underneath caps.default_headers, so provider configuration still wins on a key clash and the shared constant is copied, never mutated. Behaviour is unchanged for every other provider: when the merged dict is empty no default_headers kwarg is passed at all, which the existing test_kimi_user_agent_header_is_moonshot_only asserts.
  • agent/src/providers/llm_providers.json, agent/cli/onboard.py, agent/cli/_legacy.py (_provider_key_env, _provider_base_env, _PROVIDER_CHOICES), agent/src/swarm/models.py, agent/.env.example, desktop/electron/src/secure-credentials.ts — registry rows.
  • README.md and its five translations — the "Supported LLM providers" line.
  • agent/tests/test_aimlapi_provider.py — 13 new tests.
  • agent/tests/test_llm_reasoning_effort.py — the top_level_reasoning_effort allowlist assertion, widened deliberately with the live evidence recorded in a comment, which is what that test asks for.

Commit 2 — chore(aimlapi): fork-only placement — do not send upstream (16f0bf57)

Ordering only. Moves the entry to the head of the four hand-ordered surfaces (the catalog that drives the Web Settings dropdown, the onboarding wizard, the vibe-trading init menu, the six README lines) and applies the same free-text "recommended" marker the repo already uses on OpenRouter — the only featured-provider concept this codebase has; no badge mechanism was invented. agent/tests/test_cli_init.py stops pinning openrouter to literal menu choice 1 and resolves it through the _provider_choice_number helper that file already defines; that change is correct with or without the reordering. Revert this commit before offering anything upstream.

Capability flags — set from watched requests, not from docs

capabilities.py documents top_level_reasoning_effort as "a positive allowlist, and it stays one … a provider gets this only once someone has watched a real request to it succeed". Probed 2026-09-03 against six models from five upstream vendors through this gateway — deepseek/deepseek-v4-pro, openai/gpt-5-5, anthropic/claude-sonnet-4.5, google/gemini-3.5-flash, alibaba/qwen3.5-plus, z-ai/glm-5.3-flash — every one returned 200 for a request carrying a top-level reasoning_effort, and the reasoning models among them returned reasoning_content. Hence capture_reasoning=True and top_level_reasoning_effort=True.

send_reasoning_content stays False — replaying reasoning on assistant turns is unverified here, exactly as it is for OpenRouter. openrouter_reasoning_body stays False: the gateway takes the top-level field, so the extra_body.reasoning relay is unnecessary.

Model ids

Every id shipped (deepseek/deepseek-v4-pro as the default; openai/gpt-5-5, anthropic/claude-sonnet-4.5, google/gemini-3.5-flash as onboarding suggestions) was checked against the live catalog's ids and aliases, filtered to type == "openai/chat-completions" (353 of 936 catalog rows), and each was additionally answered by a real request. No id was copied from another aggregator's list.

Test Plan

  • Existing tests pass. Baseline captured on a pristine checkout of main (7b43ba37) before any change: 12107 passed, 93 skipped, 0 failed. On this branch: 12120 passed, 93 skipped, 0 failed — +13, exactly the tests added here. Command in both runs: pytest --ignore=agent/tests/e2e_backtest --ignore=agent/tests/test_e2e_harness_v2.py --tb=short -q, Python 3.12.14.
  • New tests addedagent/tests/test_aimlapi_provider.py, 13 tests: registry wiring across all surfaces, the display name, base-URL fallback to the catalog default, the partner-id ^part_[A-Za-z0-9]{1,64}$ shape (a malformed id is accepted by the gateway and silently dropped, so only a test catches it), headers reaching the client on the default endpoint, the shared constant surviving mutation of the dict handed to the caller, no headers on another provider, no headers for api.aimlapi.com.evil.io / notaimlapi.com / aimlapi.com.evil.io / a path-based proxy, headers still applied for a pinned vendor host including an uppercase URL, an exact-host-allowlist guard, and reasoning_effort going top-level rather than into extra_body.
  • Tested manually — one real inference call and one tool call through build_llm(), not a mock and not raw curl:
    resolved base_url : https://api.aimlapi.com/v1
    resolved model    : deepseek/deepseek-v4-pro
    content : A stock's price-to-earnings ratio measures how much investors are
              willing to pay for each dollar of the company's earnings.
    usage   : {'input_tokens': 99, 'output_tokens': 96, 'total_tokens': 195,
               'output_token_details': {'reasoning': 70}}
    headers on the wire: http-referer=https://github.com/HKUDS/Vibe-Trading
                         x-title=Vibe-Trading
                         x-aimlapi-partner-id=part_vibetrading
                         x-aimlapi-source=agent/vibe-trading
    tool_calls: [{'name': 'get_last_close', 'args': {'ticker': 'AAPL'}, ...}]
    
  • bash tools/ci_grep_gates.sh — all gates pass (exit 0). pytest tools/test_ci_env_var_gate.py — 23 passed.
  • ruff check clean on every changed Python file. black --check clean on the new test file and on capabilities.py; the other touched files were already outside black before this branch and reformatting them would be the unrelated formatting cleanup CONTRIBUTING.md asks contributors to avoid.

Not verified: the desktop Electron lifecycle (npm run smoke:lifecycle) was not run — the change there is one string added to the ENV_CREDENTIALS allowlist so the key is covered by safeStorage rather than written in plaintext. The frontend is untouched and its provider fixtures are inline rather than read from llm_providers.json. No CHANGELOG.md entry: release notes here are maintainer-authored per release and cite PR numbers that do not exist yet.

Checklist

Risk and rollback

No live, broker, MCP, order-gate or mandate surface is touched. Network behaviour changes only for a user who explicitly selects this provider. Either commit reverts independently with git revert; reverting commit 2 alone removes the placement and leaves the provider working.

Vibe-Trading users who want to move between vendors mid-research currently
need one account, one key and one .env block per vendor. AI/ML API is an
OpenAI-compatible aggregator that fronts ~350 chat models behind a single key,
so this adds it the way ModelScope (HKUDS#1011) and Requesty (HKUDS#474) were added: data
rows in the five provider surfaces plus a capability record, no new plumbing.

The capability flags are set from watched live requests on 2026-09-03, not from
the vendor's docs, per the standard capabilities.py keeps for
top_level_reasoning_effort. deepseek/deepseek-v4-pro, openai/gpt-5-5,
anthropic/claude-sonnet-4.5, google/gemini-3.5-flash, alibaba/qwen3.5-plus and
z-ai/glm-5.3-flash each returned 200 for a request carrying a top-level
reasoning_effort, and the reasoning models among them returned
reasoning_content. send_reasoning_content stays off: replaying reasoning on
assistant turns is unverified there, as it is for OpenRouter.

Attribution headers ride the existing default_headers hook, but the hook alone
is not safe for an identifier. capabilities.default_headers is selected by
provider *label*, and AIMLAPI_BASE_URL (or an ambient OPENAI_BASE_URL) can
point that label at a proxy or a look-alike host, so the four headers are gated
on the resolved base URL's hostname matching an exact allowlist. A substring or
suffix test would have accepted api.aimlapi.com.evil.io; both that and
notaimlapi.com are covered by tests. HTTP-Referer and X-Title name this project,
not the gateway, following the convention OpenRouter established.

Every model id shipped here was checked against the live catalog's ids and
aliases, filtered to type == "openai/chat-completions", and each was also
answered by a real request.

Signed-off-by: Stan <stan@aimlapi.com>
Moves the aimlapi.com entry to the head of the four hand-ordered provider
surfaces (llm_providers.json, which drives the Web Settings dropdown; the
onboarding wizard; the `vibe-trading init` menu; the README provider line in
six languages) and gives it the same free-text "recommended" marker the repo
already uses on OpenRouter — the only featured-provider concept this codebase
has. No badge mechanism is invented.

This is promotional placement, not a functional change, and it is isolated in
its own commit so it can be dropped with a single `git revert` before any
upstream pull request. Nothing outside the ordering changes: the machine
provider id, credentials, endpoint and capability record are all untouched.
Note the fork now shows two entries marked "recommended", ours and OpenRouter's.

Signed-off-by: Stan <stan@aimlapi.com>

The interactive `vibe-trading init` menu test pinned openrouter to literal
choice 1; it now resolves the number through the `_provider_choice_number`
helper the file already defines, so the assertion survives this reordering and
reverts cleanly with it.

Signed-off-by: Stan <stan@aimlapi.com>
The placeholder part_vibetrading was a readable stand-in chosen before the
partner was registered. Registration mints the id server-side, so the
real value is part_m4LUWIsqHcT74jLfzge4zJAJ. 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