fix(azure): carry declared options through withOptions and fix deployment paths - #2514
fix(azure): carry declared options through withOptions and fix deployment paths#2514morgan-coded wants to merge 2 commits into
Conversation
…ment paths `AzureOpenAI` contradicts its own declared option surface in two places: - `withOptions` re-types the option bag but re-injects nothing. `apiVersion` and `deployment` live only as instance fields and never reach `_options`, so `OpenAI.withOptions` rebuilds the clone without them: the clone throws `The OPENAI_API_VERSION environment variable is missing or empty` when the variable is unset, and otherwise silently misroutes, rebuilding the deployment segment from `body.model`. An `endpoint` override — a declared member of the `Partial<AzureClientOptions>` the override advertises — always collided with the inherited base URL. Re-inject the Azure-only options the way `BedrockOpenAI.withOptions` re-injects its own subclass-only field. - Deployment suppression tested the whole base URL for the `/deployments` substring, so a gateway path such as `/azure/deployments-proxy/openai` (or a host like `deployments.example.com`) silently dropped the deployment segment and Azure answered 404. Test the base URL's path for a `/deployments` segment instead.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8679512bb7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (options.endpoint !== undefined && options.baseURL === undefined) { | ||
| azureOptions.baseURL = undefined; |
There was a problem hiding this comment.
Require an own endpoint before clearing the trusted base URL
If Object.prototype.endpoint is polluted after an Azure client is constructed and OPENAI_BASE_URL is unset, even client.withOptions({ maxRetries: 0 }) enters this branch despite having no own endpoint. The inherited value remains visible when the clone's constructor reads endpoint, so the clone replaces the trusted base URL with the polluted origin and sends its API key there on the next request. Check for an own endpoint property before clearing the inherited base URL.
AGENTS.md reference: AGENTS.md:L98-L105
Useful? React with 👍 / 👎.
…e URL
Follow-up to the `withOptions` override and the deployment segment test.
- The override advertises that it clears the inherited `baseURL` only when the
caller passed an `endpoint` and no explicit `baseURL`, but both halves of the
guard were plain member reads that answer from the prototype chain. An option
bag that never mentioned either field — `withOptions({ maxRetries: 0 })` —
could enter the branch and lose the base URL it was supposed to inherit, and
the mirror case, an inherited `baseURL` alongside a real `endpoint` override,
skipped the branch and failed with `baseURL and endpoint are mutually
exclusive`. Test what the caller passed with the `hasOwn` helper the client
already uses for `baseURL`. Passing `endpoint: undefined` is still not passing
an endpoint, so the value check stays.
- `hasDeploymentPathSegment` parsed the base URL unguarded on the request path,
so a base URL that is not an absolute URL now threw `TypeError: Invalid URL`
where the previous substring test simply reported no match and let the joined
request URL decide. Report no segment instead, which is what the substring
test answered for every base URL a request could be built from.
|
Tightened the The repro in that comment is accurate for Two sentences in the description are stale now. Corrected:
|
AzureOpenAIoverrideswithOptionsonly to re-type it:apiVersionanddeploymentare instance fields thatOpenAI.withOptionscannot see when it rebuilds the clone, and the inheritedbaseURLis always carried across. So a clone throwsThe OPENAI_API_VERSION environment variable is missing or emptywhen that variable is unset, silently rebuilds the deployment segment frombody.modelwhen it is set — a client configured fordeployment-astarts sending/openai/deployments/request-model/chat/completions, which 404s — and rejectswithOptions({ endpoint })withbaseURL and endpoint are mutually exclusive, thoughendpointis declared on thePartial<AzureClientOptions>the override accepts. Separately, the deployment-path suppression was a substring test on the base URL, so a host likedeployments.example.comor a gateway path like/azure/deployments-proxy/openaidropped the deployment segment and Azure answered 404 with nothing to show the SDK had dropped it.Following
src/bedrock.ts, which overrideswithOptionsto re-inject a subclass-only field the base clone cannot see, the Azure override now re-injectsapiVersionanddeploymentahead of the caller's options and clears the inheritedbaseURLonly when the caller passed anendpointand no explicitbaseURL, so caller-supplied values still win and{ deployment: undefined }still clears. The suppression now testsnew URL(baseURL).pathnamefor adeploymentspath segment, so/openai/deployments/existingand its trailing-slash form still suppress and only the false matches change. One residual stays: withOPENAI_BASE_URLset,withOptions({ endpoint })still throwsbaseURL and endpoint are mutually exclusive, exactly asnew AzureOpenAI({ endpoint })does in that environment.I added 10 regression tests across
tests/lib/azure.test.tsandtests/lib/azure-deployment-path-safety.test.ts— nine red before the fix, the tenth a parity pin on the boundary case — then ran lint,tsc --noEmit, and the full suite, with 7019 handwritten and 556 generated tests passing.AzureClientOptionsdeclares aworkloadIdentitymember that the Azure credential guard never consults, and I've deliberately left it untouched here: wiring it up would federate an Azure-only client's calls through OpenAI's own token exchange at the hardcodedhttps://auth.openai.com/oauth/token, so whether Azure should accept it or declare it?: neveralongsideprovider/dataResidency/credential/x509Transportis your call, not mine — flagging it rather than changing it.