Skip to content

feat(providers): claude on azure (#1009 final cell)#1027

Merged
chaholl merged 1 commit intomainfrom
feat/claude-azure-1009
Apr 20, 2026
Merged

feat(providers): claude on azure (#1009 final cell)#1027
chaholl merged 1 commit intomainfrom
feat/claude-azure-1009

Conversation

@chaholl
Copy link
Copy Markdown
Contributor

@chaholl chaholl commented Apr 20, 2026

Summary

Closes the final cell of the provider × platform matrix. Best-endeavors:
the URL pattern follows Microsoft's standard AIServices deployment shape
(mirrors openai+azure exactly except /messages instead of
/chat/completions). No live verification has been done — the
Foundry Anthropic quota for our subscription is currently 0 in eastus2
and swedencentral and quota requests are portal-only.

Wire format (planned, to verify on first live run)

URL {endpoint}/openai/deployments/{deployment}/messages?api-version={v}
Auth Entra Bearer via credentials.AzureCredential (same as openai+azure)
Body Standard Anthropic Messages API verbatim — model in body, stream:true for streaming. Distinct from Bedrock/Vertex which use the partner shape.
API version PlatformConfig.AdditionalConfig.api_version → defaults to 2024-12-01-preview (same as openai+azure)
Headers No anthropic-version (api-version query parameter pins the contract)

If the Foundry deployment turns out to use a vendor-prefixed pattern
({endpoint}/anthropic/v1/messages?api-version=...) instead, the fix
is a one-line change to messagesURL/messagesStreamURL.

Code structure

  • azurePlatform constant + defaultAzureAPIVersion in claude.go,
    plus azureAnthropicDeploymentEndpoint(endpoint, deployment).
  • isAzure() predicate. New usesCredentialAuth() predicate
    covering Bedrock + Vertex + Azure (the auth-side superset of
    isPartnerHosted, which stays Bedrock+Vertex only because Azure
    does not use the partner body shape).
  • azureAPIVersion() reads PlatformConfig.AdditionalConfig with
    defaultAzureAPIVersion fallback.
  • messagesURL() / messagesStreamURL() branch on Azure to produce
    {baseURL}/messages?api-version={v}. Both return the same path —
    Azure uses one endpoint signalled by stream:true in body.
  • NewProviderWithCredential derives the deployment URL from
    PlatformConfig.Endpoint when BaseURL == "" and Platform="azure".
  • makeClaudeHTTPRequest skips the anthropic-version header for
    Azure (api-version pins it). Errors route through
    providers.ParsePlatformHTTPError.
  • applyToolRequestHeaders switched to usesCredentialAuth so Azure
    goes through credential.Apply (Bearer) rather than x-api-key.
  • PredictStream direct branch now calls messagesStreamURL()
    (Azure-aware) and skips the anthropic-version header for Azure.
  • Registry skips https://api.anthropic.com default for
    Platform == "azure".

Tests

  • azure_unit_test.go — predicates, URL builder, factory URL
    derivation, api-version selection, header omission, full
    PredictStream wire-format assertion against an httptest server.
  • registry_extended_test.go — claude+azure regression test
    matching the openai+azure / gemini+vertex / claude+vertex pattern.

Bedrock / Vertex / direct paths unchanged — confirmed via existing
unit tests (all pass) and not regressed by the predicate refactor.

Pre-commit

  • lint: 0 issues
  • coverage on changed files: claude.go 83.9%, claude_streaming.go
    88.1%, claude_tools.go 84.1%, registry.go 93.5% (all ≥80%)

Test plan

  • go test -race -count=1 ./runtime/providers/claude/... ./runtime/providers/... — green
  • New Azure unit tests pass (URL/auth/body wire format verified via httptest)
  • Registry regression test pass
  • Existing claude+vertex, claude+bedrock, direct-API tests pass — refactor didn't regress
  • Live verification — deferred: requires Anthropic quota allocation in your Foundry subscription (portal-only flow). First live PredictStream call will validate the URL shape; if the model returns 404 the fix is a one-line URL pattern swap to /anthropic/v1/messages per the inline comment.

Matrix status after this PR

bedrock vertex azure
claude (this PR, unverified)
openai ⛔ rejected
gemini ⛔ rejected ⛔ rejected

9 of 9 cells delivered. #1009 is closeable on the code side; one cell flagged as "URL-shape unverified pending live deployment".

Closes #1009

Closes the final cell of the provider × platform matrix.

This is best-endeavors: the URL pattern follows Microsoft's standard
AIServices deployment shape (mirrors openai+azure exactly except
`/messages` instead of `/chat/completions`). The choice is documented
inline (azureAnthropicDeploymentEndpoint) with rationale and a
follow-up note for switching to a vendor-prefixed pattern if the live
deployment uses one. No live verification has been done — the Foundry
Anthropic quota for our subscription is currently 0 in eastus2 and
swedencentral and quota requests are portal-only.

Wire format (planned, to verify on first live run):

- URL: {endpoint}/openai/deployments/{deployment}/messages?api-version={v}
       (mirrors openai+azure with /messages instead of /chat/completions;
       deployment name comes from spec.Model)
- Auth: Entra Bearer via credentials.AzureCredential — same path as
        openai+azure
- Body: standard Anthropic Messages API verbatim (model in body,
        stream:true for streaming) — distinct from Bedrock/Vertex
        which use the partner shape (no model, anthropic_version body)
- API version: configurable via PlatformConfig.AdditionalConfig
        ("api_version"); defaults to defaultAzureAPIVersion
        ("2024-12-01-preview", same as openai+azure)
- No anthropic-version header (api-version query parameter pins the
  contract)

Code structure:

- Added `azurePlatform` constant + `defaultAzureAPIVersion` in
  claude.go, plus `azureAnthropicDeploymentEndpoint(endpoint,
  deployment)` URL builder.
- New `isAzure()` predicate. New `usesCredentialAuth()` predicate
  covering Bedrock + Vertex + Azure (the auth-side superset of
  isPartnerHosted, which stays Bedrock+Vertex only because Azure
  does not use the partner body shape).
- New `azureAPIVersion()` reads PlatformConfig.AdditionalConfig
  with defaultAzureAPIVersion fallback.
- `messagesURL()` / `messagesStreamURL()` now branch on Azure to
  produce `{baseURL}/messages?api-version={v}`. Both return the
  same path because Azure's streaming and non-streaming share an
  endpoint (signalled by `stream:true` in body).
- `NewProviderWithCredential` derives the deployment URL from
  `PlatformConfig.Endpoint` when `BaseURL == ""` and
  `Platform == "azure"`.
- `makeClaudeHTTPRequest` (claude.go): partner-vs-direct dispatch
  unchanged; Azure now also skips the anthropic-version header
  (api-version pins it instead). Error parsing routes Azure
  through `providers.ParsePlatformHTTPError`.
- `applyToolRequestHeaders` (claude_tools.go): switched from
  `isPartnerHosted` to `usesCredentialAuth` so Azure also goes
  through credential.Apply (Bearer) rather than x-api-key.
- `PredictStream` direct branch (claude_streaming.go): now calls
  `messagesStreamURL()` (Azure-aware) and skips the
  anthropic-version header for Azure. Body is unchanged since
  Azure shares the direct API body shape.
- Registry skips `https://api.anthropic.com` default when
  `spec.Platform == "azure"` (regression test added).

Tests:

- azure_unit_test.go covers predicates, URL builder, factory URL
  derivation, api-version selection, header omission, and a full
  PredictStream wire-format assertion against an httptest server.
- registry_extended_test.go adds the claude+azure regression test
  matching the openai+azure / gemini+vertex / claude+vertex
  patterns.

Bedrock/Vertex/direct paths unchanged — confirmed via existing
unit tests (all pass) and not regressed by the predicate refactor.

Closes #1009 (final cell)
@sonarqubecloud
Copy link
Copy Markdown

@chaholl chaholl merged commit 6986a18 into main Apr 20, 2026
40 of 44 checks passed
@chaholl chaholl deleted the feat/claude-azure-1009 branch April 20, 2026 16:04
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.

[FEATURE] Support full providerType × platform matrix in runtime providers (claude/openai/gemini × bedrock/vertex/azure)

1 participant