Support aimlapi.com as an AI provider preset - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
Conversation
AI/ML API is an OpenAI-compatible aggregator, so it already works today by typing its base URL into a custom provider. The preset removes that manual step and makes the endpoint discoverable in the provider catalog, the same way SiliconFlow, OpenRouter and Groq are. The entry carries no icon on purpose: Groq has none either, and hand-writing an SVG is not allowed. No model list is hardcoded — the models are read from the provider's own /v1/models, so the entry cannot go stale.
Providers that run an aggregator want to know which application traffic came from, and aimlapi.com reads four headers to do it. Without them SiYuan's requests are indistinguishable from anonymous ones. The headers are keyed by API host rather than by configured provider, so they cannot ride along to another vendor or to a third-party proxy that merely fronts the same API. They carry no API key, no user content and nothing that identifies a user. Existing headers are never overwritten, and the lookup returns a copy so a caller cannot mutate the shared table. Injection sits in a HTTPDoer wrapper, the same client-level extension point the extra-body and Gemini thought-signature adapters already use, so it applies to chat, streaming and the model list without touching call sites. The partner id is covered by a regex test: an invalid value is not rejected by the server, it is silently counted as untagged, so a typo would otherwise never surface.
Moves the aimlapi.com preset to the first position of the aggregator group so it is the first card a user sees in that section of the provider catalog. This is a commercial placement preference, not a functional change, and it is deliberately isolated in one commit so it can be dropped before any upstream proposal. The category grouping and category order are left untouched: the catalog renders official, aggregator, local, custom in that fixed order, and reordering those would change unrelated UI. The repository has no "recommended" or "featured" badge concept anywhere — the generic b3-chip component is only used for user titles, bazaar deprecation and error states, keyword tags, spellcheck languages and font selection — so no badge is added.
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.
What
Adds
aimlapi.comas an AI provider preset, and sends caller attribution headers when — and only when — a request actually goes toapi.aimlapi.com.Three commits, deliberately separable:
:sparkles: Support aimlapi.com as an AI provider preset— one line inPROVIDER_PRESETS(app/src/config/tabs/ai/aiProviderUi.ts).:sparkles: Send caller attribution headers to aimlapi.com—kernel/util/openai.go+ a newkernel/util/openai_attribution_test.go.chore(aimlapi): fork-only placement — do not send upstream— moves the preset to the first position of the aggregator group. Commercial placement only. Drop this commit before any upstream proposal.Why
AI/ML API is an OpenAI-compatible aggregator. A user can already reach it today by typing the base URL into a custom provider, so the preset is a discoverability change, not a capability one. It follows exactly the shape of the three aggregator presets already there (
siliconflow,openrouter,groq).No icon is added:
groqships without one, so the field is genuinely optional, andAGENTS.md§2.3 forbids hand-writing SVG.No model ids are hardcoded. The provider's models come from its own
GET /v1/modelsthrough the existingListAvailableModelspath, so the entry cannot go stale.No new i18n keys: preset display names are literals in the preset table, not language keys, so
langs/*.jsonis untouched andscripts/check-lang-keys.pyis unaffected.Attribution headers — how they are scoped
Injection happens in an
openai.HTTPDoerwrapper (attributionTransport), the same client-level extension point the existingextraBodyTransportand the Gemini thought-signature transport already use. It is wired once innewProviderHTTPDoer(), so chat, streaming and the model list all get it without touching any call site.The table is keyed by API host, not by configured provider. Headers are attached only if
req.URL.Hostname()matches, so they cannot ride to another vendor, and cannot reach a third-party proxy that merely fronts the same API.AttributionHeadersForHostreturns a copy, so a caller cannot mutate the shared table. Existing headers of the same name are preserved, never overwritten.The headers carry no API key, no user content, and nothing identifying a user.
HTTP-RefererandX-Titlepoint at SiYuan, not at the vendor.X-AIMLAPI-Partner-IDis asserted against^part_[A-Za-z0-9]{1,64}$in a test, because an invalid value is not rejected by the server — it is silently counted as untagged, so a typo would never surface at runtime.Verification
Numbers are baseline (pristine
master,44a6c21) versus after.go test ./util/ -count=1 -v(kernel)node --import tsx --test src/config/tabs/ai/*.test.tspnpm run typecheckpnpm run lint(typecheck + eslint --fix)gofmt -l ./util/,go vet ./util/The six new tests are the attribution ones. Exit codes were read from the bare command, not through a pipe.
pnpm buildwas not run —AGENTS.md§1 forbids it. The kernel binary was not compiled —AGENTS.md§1.3 forbids that too; the touched package was type-checked and tested instead.One real call through the code path
Driven through
util.NewOpenAIClientWithModel+util.CreateOpenAICompletion— the same functionskernel/model/ai.goandkernel/api/agent.gocall — againsthttps://api.aimlapi.com/v1, modelopenai/gpt-4o-mini, with a real key supplied via the environment. Headers were captured withhttptrace.ClientTrace.WroteHeaderField, i.e. as actually written on the wire, not as configured.Tool calling works on the same path, so the agent runtime is covered too. The probe file was temporary and is not part of this branch.
A known trap for OpenAI-compatible providers is a client that serialises unset optional parameters as literal
null; several models rejecttools: nullwith a 400 on turn 2 of an agent loop while turn 1 succeeds. This was checked rather than assumed: insashabaranov/go-openai,Tools,ToolChoice,ResponseFormat,Seed,Temperature,TopP,MaxTokens,MaxCompletionTokens,StreamOptionsandParallelToolCallsare allomitempty, andStreamis a plainboolthat serialises asfalse. Unset keys are omitted, so this codebase is structurally immune.Not fixed here, but found while verifying
util.TestModelvalidates a provider by callingListModelsfirst and only falls back to a completion request if that fails. Against this provider,GET /v1/modelsreturns 200 for any key, including a garbage one. Running the same code path withsk-not-a-real-key-000returnedmatched=true, err=<nil>— so the provider "test" action reports success for an invalid key, and the real failure only appears later at the first chat request, where it reads as a broken model rather than a bad key.That is a provider-side quirk, and fixing it here would change behaviour for every provider, so it is out of scope for this PR. It is worth a separate issue.
Relatedly, that call returns 936 entries with no
typefilter, of which only 353 are chat models, and 91 ids appear twice under different endpoint types (151 duplicate rows). The model dropdown therefore shows non-chat models and repeats some ids. Also out of scope here.What could not be verified
AGENTS.md§1.3. Verification was theutilpackage's own tests plus the live call above.pnpm dev/pnpm buildare forbidden byAGENTS.md§1. The change there is one entry in a typed literal array and is covered bytsc.