feat(providers): add aimlapi.com as an OpenAI-compatible provider - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
feat(providers): add aimlapi.com as an OpenAI-compatible provider#1Lookoff-AIMLAPI wants to merge 3 commits into
Lookoff-AIMLAPI wants to merge 3 commits into
Conversation
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.
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.
Summary
ProviderCapabilitiesrecord, no new transport code.default_headershook, gated on the resolved base URL's hostname so an identifier cannot ride a request to a proxy or a look-alike domain.Why
Comparing vendors mid-research currently means one account, one key and one
.envblock 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_headersis selected by the provider label, andAIMLAPI_BASE_URL— or an ambientOPENAI_BASE_URL, whichget_llm_credentialsalso 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_openaicheck a few lines above it. A substring or suffix test would have acceptedapi.aimlapi.com.evil.io; that andnotaimlapi.comare 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, plusAIMLAPI_ATTRIBUTION_HEADERS(a read-onlyMappingProxyType) andAIMLAPI_ATTRIBUTION_HOSTS.agent/src/providers/llm.py—_aimlapi_attribution_headers(caps, base_url). Thebuild_llmheader block now merges attribution underneathcaps.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 nodefault_headerskwarg is passed at all, which the existingtest_kimi_user_agent_header_is_moonshot_onlyasserts.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.mdand its five translations — the "Supported LLM providers" line.agent/tests/test_aimlapi_provider.py— 13 new tests.agent/tests/test_llm_reasoning_effort.py— thetop_level_reasoning_effortallowlist 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 initmenu, 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.pystops pinning openrouter to literal menu choice 1 and resolves it through the_provider_choice_numberhelper 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.pydocumentstop_level_reasoning_effortas "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-levelreasoning_effort, and the reasoning models among them returnedreasoning_content. Hencecapture_reasoning=Trueandtop_level_reasoning_effort=True.send_reasoning_contentstaysFalse— replaying reasoning on assistant turns is unverified here, exactly as it is for OpenRouter.openrouter_reasoning_bodystaysFalse: the gateway takes the top-level field, so theextra_body.reasoningrelay is unnecessary.Model ids
Every id shipped (
deepseek/deepseek-v4-proas the default;openai/gpt-5-5,anthropic/claude-sonnet-4.5,google/gemini-3.5-flashas onboarding suggestions) was checked against the live catalog's ids and aliases, filtered totype == "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
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.agent/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 forapi.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, andreasoning_effortgoing top-level rather than intoextra_body.build_llm(), not a mock and not raw curl:bash tools/ci_grep_gates.sh— all gates pass (exit 0).pytest tools/test_ci_env_var_gate.py— 23 passed.ruff checkclean on every changed Python file.black --checkclean on the new test file and oncapabilities.py; the other touched files were already outsideblackbefore this branch and reformatting them would be the unrelated formatting cleanupCONTRIBUTING.mdasks contributors to avoid.Not verified: the desktop Electron lifecycle (
npm run smoke:lifecycle) was not run — the change there is one string added to theENV_CREDENTIALSallowlist so the key is covered bysafeStoragerather than written in plaintext. The frontend is untouched and its provider fixtures are inline rather than read fromllm_providers.json. NoCHANGELOG.mdentry: release notes here are maintainer-authored per release and cite PR numbers that do not exist yet.Checklist
src/agent/,src/session/,src/providers/) without prior discussion — this PR does touchagent/src/providers/(capabilities.py,llm.py,llm_providers.json), which is unavoidable for any provider addition and is what feat(providers): add ModelScope as an OpenAI-compatible provider HKUDS/Vibe-Trading#1011, feat: add SiliconFlow CN and Global providers HKUDS/Vibe-Trading#565 and feat(providers): add Requesty as an OpenAI-compatible provider HKUDS/Vibe-Trading#474 each did. Flagged rather than ticked..env.Signed-off-by:, noCo-Authored-By:or assistant attribution trailers, Google-style docstrings, public signatures annotated.README.mdplus five translations, andagent/.env.example.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.