fix(ai-providers): resolve an AI provider's request URL from the base the user configured - #3042
Merged
Merged
Conversation
… the user configured
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
This branch was successfully deployed
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.
Fixes #3040
The defect
AIProviderConfigcarries one URL field, and five separate call sites each hard-coded the API version segment onto it:String.openAIPath(OpenRouter, OpenCode Zen, llama.cpp, MLX, Custom)<base>/v1/chat/completions,<base>/v1/models/v1AnthropicProvider<base>/v1/messages,<base>/v1/modelsOpenAIResponsesProvider(OpenAI, xAI)<base>/v1/responses,<base>/v1/modelsGeminiProvider<base>/v1beta/models/...Z.ai serves its OpenAI-compatible API at
https://api.z.ai/api/paas/v4/chat/completions. Typing that base produced/api/paas/v4/v1/chat/completions, and typing the full completions URL appended/v1/chat/completionsagain, so no value of the field reached the server.The same class bites the other four providers from the opposite direction: every one of them is
endpointConfigurable, so pasting a vendor's own documented base (https://api.openai.com/v1,https://api.anthropic.com/v1) yielded/v1/v1/responsesand/v1/v1/messages.The fix
One resolver,
AIEndpoint, that every provider goes through. It follows the rule VS Code documents for its own custom providers: a URL whose path already names a resource is used as it stands, and the version segment is inserted only when the last path segment is not already one (v1,v4,v1beta).Every configuration that works today resolves to the byte-identical URL, so no stored setting is migrated, no default endpoint changes, and an older build syncing the same
AISettingsblob keeps working. The only values whose resolution changes are ones that 404 today.Joining is
URL.appending(path:), measured on this toolchain rather than assumed:URL(string:relativeTo:)dropsv4fromhttps://api.z.ai/api/paas/v4per RFC 3986's relative-reference rules, andappending(path:)preserves a query string already on the base, which an Azure-shaped deployment URL needs. Scheme, host and userinfo validation mirrorsMCPServerConfiguration.validate, the pattern already used for a user-typed HTTP endpoint.One accepted behaviour change
A gateway mounted at
/v2in front of an OpenAI-compatible backend used to get an implicit/v1appended. It no longer does. The escape hatch is typing the full completions URL, which is now honoured as it stands.What shipped alongside, and why the fix is incomplete without it
Each of these was found while investigating #3040 and verified against the code:
OpenAICompatibleProvider.testConnectionchecked only for a 401 and returnedtruefor any other status whose body was JSON, so the reporter's Spring Boot 404 page drew a green "Connection successful". It now accepts 200 and 400, throws on everything else, and still requires an API-shaped body so a proxy login page answering 200 with HTML is not mistaken for a working endpoint.URL(string: "api.z.ai/api/paas/v4")is not nil; it is a relative reference with no host, so theinvalidEndpointguard never fired and URLSession reported-1002instead of TablePro's own message.AIProviderError.notFoundnames the URL that was called and keeps the server's own message.AIProviderDetailSheetbuilt its throwaway transport throughAIProviderFactory.createProvider, which writes the per-id cache thatresolvereads for running sessions, so a half-typed Base URL reached a session already streaming. The sheet now builds throughmakeUncachedProvider.UI
Endpoint is now Base URL, the wording every comparable client uses, with the type's default as its placeholder, one line saying to include the version segment, and the URL a request resolves to shown underneath and selectable. Apple's HIG note that placeholder text disappears once typing starts is why the rule is stated in a label rather than only in the placeholder.
The field is hidden for xAI while it is running on a subscription sign-in, because
XAIGrokProviderreaches its own proxy and the configured base has no effect in that mode.Verification
verify.sh buildverify.sh test(24 suites)verify.sh test(9 suites, after review fixes)verify.sh lint(17 files)verify.sh docsSkill(security-review)The three
SSEEventStreamTestsfailures in the first run are the known live-network failures on this machine. I confirmed that rather than accepting the wrapper's mute: with theSSEEventStream.swiftchange reverted, the same three cases fail identically.New tests:
AIEndpointTests(the whole resolution table, per style, plus trailing slashes, a base query string, an escaped path separator, credentials in the endpoint, and every provider's default endpoint),OpenAICompatibleProviderConnectionTests(the status-code and body-shape contract),AIProviderModelFetchTests(Claude and Gemini surfacing an HTTP failure, and all three providers reaching the resolved base).StringAIEndpointTestsis replaced.No screenshots: the change is a relabelled text field and two caption lines inside the AI provider detail sheet, which no docs page pictures today.
Review
Codex reviewed the working tree and raised three, all fixed in this branch:
URLComponents.pathdecodes%2F, so writing the decoded value back would have turned one path segment into two and addressed a different route on a gateway mounted under an escaped separator. Measured, then fixed by working onpercentEncodedPath.Skill(security-review)ran because this path is a trust boundary. It changes nothing about what a user or a paired client is allowed to do: the host was always user-supplied, only the path is rewritten, and the new validation is strictly tighter than what it replaces, refusing non-http(s)schemes and an endpoint carrying credentials, neither of which was checked before.Not in this PR
Four verified defects in the same subsystem are independent of the URL fix and are reported separately rather than bundled here.