The two paths derive the endpoint and the credential header independently, so a
resource could authenticate in one and 401 in the other. A parity test pins them
together across the provider/platform matrix and fails on each divergence below.
- Anthropic base URLs were read differently: the proxy trimmed and re-appended
`/v1` while the agent step appended `/messages` to the stored value, so a
`.../anthropic` base worked in workspace settings and 404'd in an agent step.
`build_anthropic_api_url` accepts both forms for both paths, and the URL no
longer depends on the client-supplied `X-Anthropic-SDK` header, which is gone.
- A base URL stored with a trailing slash doubled it in an agent step.
- An OpenAI resource pointed at Azure got Azure's URL layout and `api-key`
header from the proxy but bearer auth and the plain path from the agent step,
where `OpenAIQueryBuilder` ignored `is_azure`.
- The agent step sent an empty credential when the resource had no api key,
where the proxy sends none at all. `retain_effective_credentials` gives both
the same rule, so an endpoint that authenticates another way still works.
- An OAuth resource cannot resolve to a token in a worker: there is no client
credentials exchange there, so it now fails with that reason unless it carries
the credential header its provider reads.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): stop breaking OpenAI-compatible gateways in workspace AI settings
The workspace/instance AI proxy sent credentials in a shape that
OpenAI-compatible and Anthropic-compatible gateways reject, while the same
resource worked in an AI agent step:
- `is_azure` treated *any* OpenAI base URL other than api.openai.com as Azure,
so a gateway got the Azure `api-key` header instead of `Authorization: Bearer`
and an `/openai/v1/`-rewritten path. Match on the host instead.
- The Anthropic proxy sent both `authorization: Bearer` and `X-API-Key`.
Gateways reject ambiguous credentials; send only the header the endpoint
expects, matching `get_auth_headers` and the agent-step path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): match Azure on the endpoint and let resource headers own auth
Review follow-ups:
- Azure OpenAI reached through a custom domain keeps its `/openai/deployments`
path, which `openai_azure_base_path` documents; match on it so those
instance-wide settings are not reclassified as plain OpenAI-compatible.
- Cover the sovereign-cloud API Management suffixes and FQDNs with a trailing
dot.
- A resource that supplies its own `authorization`/`x-api-key` header now
suppresses the built-in one. Outgoing headers are appended rather than
replaced, so both credentials used to travel, which is exactly what gateways
reject; this is the escape hatch for endpoints wanting bearer auth.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): give the agent step the same resource-header credential rule
Review round follow-ups:
- `ai_executor` appended the query builder's credential header alongside the
resource's, so an AI agent step still sent two credentials where the proxy now
sends one. Both paths share `resource_owns_credentials`/`CREDENTIAL_HEADERS`;
non-credential headers such as `anthropic-version` are kept.
- Cover the OpenAI-compatible proxy's suppression branch with a test.
- Match the Azure deployments path case-insensitively, like the host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): scope the credential override to the header the provider uses
A resource header now replaces the built-in credential only when it is the same
header the provider authenticates with, or an `authorization` one (which every
endpoint reads as the credential). Matching any credential-shaped header let an
OpenAI-compatible resource's `x-api-key` routing header suppress the bearer
token. Google AI's `x-goog-api-key` joins the list so the override reaches that
provider too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(ai): share credential and trailing header assembly across AI paths
The proxy and the AI agent step each assembled outbound headers themselves, so
this fix had to be applied at three sites and the rules could drift apart
silently. Two pieces move into `proxy`:
- `credential_header` picks the credential to send, applying the resource
override. `authorization` carries a bearer token and every other credential
header carries the raw key, which holds for every provider.
- `common_outbound_headers` yields Windmill's own headers then the resource's,
the tail every outbound request shares.
A resource resolves to an api key or an OAuth token, never both, so selecting
one drops the branch that could emit two `authorization` headers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): keep OAuth tokens on the bearer header
An OAuth resource resolves to an access token, which every provider reads from
`authorization` — Azure OpenAI accepts api keys in `api-key` but Entra ID tokens
only as a bearer. Sending it in the provider's key header left Azure OpenAI and
Foundry Claude OAuth resources unauthenticated.
Also covers the credential-override narrowing: a credential-shaped header the
provider does not authenticate with is an ordinary header and must not suppress
the built-in credential.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ai): route Azure Foundry Claude models via Anthropic Messages API
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): keep explicit Azure OpenAI deployment base URLs intact
build_azure_openai_url only appends /openai/v1 for a bare resource root; any base with an explicit path (e.g. .../openai/deployments/<id>) is preserved. Adds a regression test and a unit test for usesAnthropicMessagesApi.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): enable Claude extended thinking on Azure Foundry
Route azure_foundry+Claude through the Anthropic reasoning branch (adaptive thinking + output_config.effort) instead of the gpt/o gate, and recognize claude-sonnet-5. Live-verified: sonnet-5 and opus-4-8 on Foundry accept the low/medium/high/xhigh/max ladder and render summarized thinking.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): add Azure AI Foundry as a native AI provider
Adds `azure_foundry` as a new AIProvider variant wired through the AI
chat (copilot) and AI agent flow steps. Foundry's chat completions API
is OpenAI-compatible and uses Azure conventions (api-key header, Azure
URL building), so it reuses the existing OpenAI-compatible query builder
and proxy path via the shared `is_azure` helper (renamed from
`is_azure_openai`).
Backend (windmill-ai):
- New `AzureFoundry` enum variant (serde `azure_foundry`)
- `get_base_url` requires a resource base URL (like Azure OpenAI / Custom)
- `is_azure()` covers Azure OpenAI + Foundry (api-key auth, Azure URL)
- Added to OpenAI-compatible proxy support and HttpForward proxy mode
- New proxy URL unit test
Frontend (copilot):
- New provider entry, completion config, model-token handling, streamed
usage tracking, and reasoning registry (all model-id-gated, so a no-op
for Foundry's non-OpenAI catalog)
- Treated as a chat-completions provider, not the OpenAI Responses API
OpenAPI:
- `azure_foundry` added to AIProvider (openapi.yaml) and AIProviderKind
(openflow.openapi.yaml); regenerated CLI guidance
Note: the `azure_foundry` resource type (base_url + optional api_key) is
hub-managed and must be published to the Windmill Hub separately.
Fixes WIN-2122
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): add azure_foundry to copilot flow Zod provider enum
The tracked copilot flow schema (openFlowZod.gen.ts and its openFlow.json
source) still carried the old AIProvider enum, so validateFlowModules /
validateSpecialFlowModule rejected AI-generated flow edits that create or
update an aiagent module with provider kind "azure_foundry" before they
could be saved. Add the value to both (preserving the generated single-line
format) and a regression test over the flow-module validation path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): lead provider list with OpenAI, Anthropic, Google AI
Reorder AI_PROVIDERS so the three primary direct providers come first. The
AIProviderPicker renders the first three entries as quick-access buttons, so
these become the defaults (previously OpenAI, Azure OpenAI, Azure Foundry);
Azure OpenAI / Azure Foundry stay adjacent right after. No logic depends on
provider order (only per-provider defaultModels[0] is read).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove deprecated enable_1m_context from AI provider code
1M context is now standard on Anthropic models — the beta header
`anthropic-beta: context-1m-2025-08-07` is no longer needed.
Remove the field from ProviderCredentials and AnthropicQueryBuilder,
and stop injecting the beta header in both the API proxy and worker
query builder paths.
The field is kept (as `_enable_1m_context`) on the ProviderResource
deserialization structs in both windmill-api and windmill-ai so
existing resources with the field still deserialize without error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: drop vestigial _enable_1m_context field from AI resources
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: assert legacy enable_1m_context keys still deserialize
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>