* feat: track token cost in AI sessions and chats
* fix: address review findings on AI cost tracking
* fix: price inherited and overridden models at their real rates
* fix: stop newer model revisions inheriting an older price
* fix: stop a sub-model inheriting its family's price
* fix: keep alias suffixes resolving to their model's price
* fix: count OpenRouter cache writes and drop unverifiable rates
* refactor: move AI spend out of the chat into workspace and user settings
* fix: pin the usage workspace per turn and stop inventing cache rates
* fix: leave Sonnet 5 unpriced while its promotional rate runs
* docs: record the new table in the schema summary and tighten comments
* fix: mark estimated AI costs with ~ and drop session grouping
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: name the workspace in the self-scoped AI usage title
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: state that overrides never replace a provider-returned cost
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: let a cleared cache rate inherit again and flag partial totals
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clear a refused rate's error when the input snaps back
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop a revision variant inheriting its base family's rate
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: report AI usage before tools run and price self usage consistently
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: key pricing rows on the model id usage is reported under
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: surface Bedrock and Gemini usage the chat proxy was dropping
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: count Gemini tool-use prompt tokens as input
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: price flat-rate Gemini Flash models
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: state the tool-use token invariant once
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop sampling params on Claude models that reject them
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: scope the sampling-param claim to what was probed and split the bedrock test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: build the disable body through the resolver instead of asserting a rejected shape
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: use the Gemini 3.1 Pro id that actually resolves
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: Bedrock Sonnet 5 cannot disable thinking, unlike the native API
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refresh AI provider model defaults and capability metadata
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: send explicit thinking disable for Claude and cap Opus 4.1 output
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve mistral-medium-latest window and OpenRouter Claude 5 off
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: cover au. bedrock geo and Fable 5 caching, revert unverified mistral ladder
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: scope the Anthropic explicit disable to models that think by default
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: translate the reasoning off sentinel on the backend Anthropic path
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: translate the reasoning off sentinel on the Bedrock Converse path
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: share the reasoning off sentinel and make its translation testable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: send prompt_cache_key on the openai responses api
* fix: bound prompt_cache_key to the provider limit and scope it to retryable paths
* fix: keep a digest suffix when bounding long frontend cache keys
* docs: attach the cache-key doc block to the function it describes
* fix(ai): route Azure OpenAI agent steps through the Responses API
* docs: note why azure responses routing is per-provider
* fix(ai): fall back to chat/completions when an azure endpoint rejects responses
* refactor(ai): retry endpoint and stream_options rejections in one loop
* fix(ai): keep a rejected request shape dropped across agent iterations
* perf(ai): remember which deployments reject the responses route
* fix(ai): only remember a route rejection the fallback resolved
* fix(ai): keep web search on azure and require the deployment be named to reroute
* fix(ai): remember an unserved route only on a 404
* docs: correct the reroute-flag and fallback-hook comments
An Azure OpenAI base URL naming a deployment, such as the
`https://<res>.openai.azure.com/openai/deployments/<id>` format that
`openai_azure_base_path` documents, was appended to as-is. That names the legacy
surface, which serves only with an `api-version` query and answers 404 without
one, so both the proxy and the AI agent step reached a route that does not exist.
Such a base now resolves to the resource root and the v1 surface, like every
other Azure shape. The deployment in the URL is redundant there: the v1 surface
takes it from the request body. `azure_foundry_root` recovers the root the same
way, so a Foundry resource on such a base builds its Claude URL from the root too.
The instance-settings help text promised the URL pins the model for every
workspace, which that surface never delivered; it now says where the model comes
from.
Verified against a live Azure OpenAI resource: the previous URLs 404 and the ones
built now return 200.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
* [ee] fix: pin validated DNS address to close SSRF DNS-rebinding TOCTOU
validate_url_for_ssrf resolved the host, checked every address was
public, then discarded them. Callers re-used the hostname and let stock
reqwest re-resolve at connect time, so a TTL-0 DNS rebinder that answered
a public IP at check-time and an internal one (e.g. 169.254.169.254) at
connect-time slipped straight through the guard.
Return the resolved addresses as a ValidatedTarget and pin them onto the
client that connects, so validate-time and connect-time target the same
address. Covers the AI proxy and worker AI-agent base_url (the primary
readable-SSRF sink), AI OAuth token_url, MCP server + OAuth
registration/discovery/token endpoints, SAML metadata, and the WebSocket
trigger connect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 22abd6d4e229f1206a13ebee8a6a9b808cd82a0d
This commit updates the EE repository reference after PR #684 was merged in windmill-ee-private.
Previous ee-repo-ref: 700feb02ef1b96758ba9425358dbebc83bc02c61
New ee-repo-ref: 22abd6d4e229f1206a13ebee8a6a9b808cd82a0d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(ai): extract prompt cache token usage from OpenAI and Azure providers
Parse the nested cache token details OpenAI returns and thread them into
TokenUsage, matching the Anthropic and Bedrock providers.
- sse.rs: add OpenAIPromptTokensDetails / OpenAIInputTokensDetails and the
optional prompt_tokens_details / input_tokens_details fields.
- other.rs (Chat Completions) and openai.rs (Responses): populate
cache_read via .with_cache(cached_tokens, None).
OpenAI's prompt_tokens/input_tokens already include cached tokens (cached is
a subset), so total/prompt are unchanged; cache_read is recorded separately
for reporting. For the same reason the frontend token-usage conversions are
left as-is (adding cached would double-count); optional cache fields and a
clarifying comment are added to prevent a future incorrect Anthropic-style fix.
Fixes WIN-2207
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai): pin OpenAI/Azure cache-token deserialization paths
Add regression tests deserializing the real Chat Completions and Responses
usage payloads, guarding the prompt_tokens_details.cached_tokens /
input_tokens_details.cached_tokens paths against a silent rename.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai): extract to_token_usage() and test the cache mapping
Address review nit: move the usage->TokenUsage conversion into
OpenAIChatUsage::to_token_usage / OpenAIResponsesUsage::to_token_usage so
the providers call one method and the tests exercise the real mapping.
Tests now assert cache_read is populated while input/total are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): stop sending the AI agent system prompt twice for OpenAI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): document collect_system_prompt precedence and trim duplicate comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): hoist only the leading system prompt for OpenAI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): disable redirects on worker AI provider client (GHSA-5q4v)
The worker AI request path issued provider requests with the shared
HTTP_CLIENT, which follows up to 10 redirects without revalidating each
hop. SSRF validation on the provider base_url is single-shot, so a public
base_url could 3xx the worker into a private/internal host (e.g. cloud
metadata), bypassing the private-endpoint protection. The API proxy was
already hardened in #9370; the worker path was missed.
Add a dedicated AI_HTTP_CLIENT with redirects disabled and use it for the
user-controlled provider endpoint, mirroring the API proxy client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): add ALLOW_AI_BASE_URL_REDIRECTS debug escape hatch
Off by default (redirects stay disabled). When set, restores redirect
following on the AI HTTP client for debugging non-standard/self-hosted
gateways, with a startup warning that it weakens SSRF protection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(ai): correct redirect comment for the escape hatch override
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(ai): condense redirect invariant comments per review
Anchor the SSRF rationale to ALLOW_AI_BASE_URL_REDIRECTS (the knob that
would break it) and shorten the AI_HTTP_CLIENT and call-site comments to
avoid restating it at multiple sites (AGENTS.md).
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-agent): support reasoning effort in AI agent workflow steps
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): round-trip native Anthropic thinking blocks and fix DeepSeek/Mistral reasoning
Address review: native Anthropic now captures the signed thinking block during streaming and replays it before tool_use across iterations (prevents a 400 on multi-turn tool use). DeepSeek 'off' sends thinking:{type:disabled} instead of the rejected reasoning_effort:none, and Mistral drops temperature when reasoning is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): move reasoning effort into the provider/model selector
Store reasoning_effort on ProviderConfig (next to the model) instead of a separate flow arg, and render the selector inside AIProviderPicker under the model dropdown. Add an explicit 'off' option on models that disable reasoning by omission (e.g. Claude), so reasoning can always be turned off from the UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): use DropdownV2 for reasoning effort, matching copilot chat
Replace the Select combobox with the same DropdownV2 action-menu the copilot chat reasoning selector uses. Each option carries an action instead of a bound value, so click selection is unambiguous and there is no typeahead/sentinel-value mismatch on the off/default entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(ai-agent): regenerate system prompts for ProviderConfig.reasoning_effort
Refresh system_prompts/auto-generated and cli skills.gen after adding reasoning_effort to the OpenFlow ProviderConfig schema (check-freshness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): clear stale reasoning effort on model change; dedup bedrock reasoning folding
Address cubic review: (P1) the reasoning picker now clears the stored effort when the newly selected model doesn't accept it (e.g. carrying 'xhigh' from Opus onto a model that tops out at 'high'), not only when the model can't reason at all. (P3) the proxy's accumulate_reasoning_delta now delegates to the shared bedrock_stream_event_to_reasoning_delta so worker and proxy folding can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): stream reasoning summary and show a thinking affordance in flow chat
Add StreamingEvent::ReasoningTokenDelta, emitted from every worker reasoning path (Anthropic native thinking deltas, Bedrock, Gemini thought parts, OpenAI-compatible reasoning_content, OpenAI Responses reasoning_summary_text with summary:auto). The flow chat parses it and renders a collapsible 'Thinking' affordance on the assistant message (thinking tokens bill regardless of display, so surfacing the summary is billing-neutral).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): persist streamed reasoning onto the finished chat message
Reasoning isn't stored server-side, so the completion re-poll (which swaps temp messages for the persisted ones) was dropping the streamed thinking summary. Carry it onto the final assistant message so the 'Thought process' affordance survives the run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): match flow-chat thinking box to the copilot chat reasoning UI
Replace the plain <details> thinking affordance with the same bordered, collapsible reasoning box the copilot chat uses (chevron + Brain/spinner + 'Thinking' header, markdown body, expand-while-streaming/collapse-on-answer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): attribute streamed reasoning per turn by content; drop duplicated comment
Address review: the completion-poll carry-over now matches each temp assistant turn's thinking summary to its persisted message by content, so a multi-turn response (reasoning -> tool call -> final answer) no longer misattributes an earlier turn's thinking to the final answer or drops intermediate turns. Also removes a leftover duplicated comment block above the AIReasoningEffortPicker effect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): address review round 2 (carry-over edges, off-token validity, aria, test)
cubic round 2: (1) reasoning carry-over now consumes temp turns in order verifying content, so identical/empty-content multi-turn responses attribute thinking correctly and reasoning-only turns aren't dropped; (2) the picker's stale-value check only accepts the off token when the model can actually disable reasoning; (3) add aria-expanded to the Thinking toggle; (4) add a test for the failed tool_result path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): add bottom margin below the flow-chat thinking box
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): don't request OpenAI reasoning summary, matching the copilot chat
OpenAI gates reasoning summaries behind org verification, so requesting summary: auto would 400 for unverified orgs. The copilot chat requests effort only and never asks for a summary; align the worker with it (reasoning: { effort }) and drop the now-unreachable summary-delta parsing. OpenAI reasoning no longer streams a summary in flow chat (consistent with the copilot); Anthropic/Bedrock/Gemini/DeepSeek reasoning display is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): scope reasoning carry-over to newly persisted messages
cubic round 3: matching by content over the full history could attach a new turn's reasoning to an older message with identical text. Restrict eligible targets to the messages just fetched for this response (via afterSeq), so historical turns are never touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): carry reasoning newest-first instead of gating on the final poll
cubic round 4: gating the carry-over on the final poll's filteredResponse dropped reasoning for messages already fetched by an earlier streaming poll (their id is excluded by afterSeq). Walk persisted newest-first and consume the newest matching pending summary, stopping once summaries run out. This response's turns are always at the end, so they claim their own reasoning (P1) before older history is reached (P2), regardless of which poll persisted them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): drop flow-chat reasoning display, keep backend + effort picker
The chat-side thinking box relied on non-deterministically matching streamed (ephemeral) reasoning back onto persisted messages, which kept spawning edge cases. Remove the flow-chat display entirely (ChatMessage box, FlowChatManager carry-over/threading, parseStreamDeltas reasoning) and keep the sound backend: per-provider reasoning-effort requests, thinking-block round-trips for tool calls, and ReasoningTokenDelta streaming. A display can be built on top later, deterministically (e.g. once the stream carries the persisted message id).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): include reasoning_effort in default-config compare; document reasoning_token_delta
Codex/Pi nits: isSameAsStoredConfig now compares reasoning_effort so the 'use as personal default' toggle reflects effort-only changes; openflow streaming-events doc lists the reasoning_token_delta event (regenerated auto prompts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (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>
* feat: map chat reasoning effort to gemini thinkingConfig in ai proxy
* feat: support claude adaptive thinking through the bedrock ai proxy
* feat: per-model gemini effort levels and thought summary display
* fix: exclude claude opus 4.5 from reasoning effort (rejects adaptive)
* fix: render markdown in thinking blocks and unstick reasoning spinner
* feat: model-aware reasoning effort options across ai chat providers
* fix: scope openrouter reasoning off to the underlying model family
get_token_using_oauth resolved the AI OAuth resource's token_url and
POSTed to it without any SSRF validation, while base_url is validated in
get_base_url. A workspace member with resources:write could point
token_url at an internal/metadata address (e.g. 169.254.169.254),
turning the server into an authenticated blind SSRF probe.
Validate the resolved token_url with validate_url_for_ssrf before the
request, gated behind the same ALLOW_PRIVATE_AI_BASE_URLS opt-in as
base_url so private AI deployments keep working consistently for both
URL fields. ALLOW_PRIVATE_AI_BASE_URLS is now pub so windmill-api can
reuse it instead of re-parsing the env var.
* fix: don't show ALLOW_PRIVATE_AI_BASE_URLS hint for malformed AI base URLs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: impl std::error::Error for SsrfValidationError for anyhow callers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
json_to_document cast any i64 to u64 via `as` and wrapped in PosInt.
Negative integers in a tool's JSON schema (e.g. "default": -1,
"minimum": -1) became huge unsigned values like 18446744073709551615,
which Bedrock rejected with `tools.N.custom.input_schema: int too big
to convert`.
Try as_u64 first to preserve full u64 range, fall back to as_i64 for
negatives via NegInt, then as_f64.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: refine windmill ai refactor plan
* refactor: move ai sse plumbing to windmill-ai
* refactor: remove ai re-export shims
* fix: update ee ai memory ref
* chore: update ee-repo-ref to d3bc7fa85195b46b7a38d43c2f806520bf8b5454
This commit updates the EE repository reference after PR #560 was merged in windmill-ee-private.
Previous ee-repo-ref: ff35bf7cc198e13884b33654e1d6dbd8a8b314d3
New ee-repo-ref: d3bc7fa85195b46b7a38d43c2f806520bf8b5454
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* refactor: create windmill-ai crate and move base AI types from windmill-common
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move worker AI types to windmill-ai crate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move QueryBuilder trait and StreamEventSink abstraction to windmill-ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add base64 dependency to windmill-ai for bedrock PDF support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add windmill-ai refactor plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address PR review — remove dead bedrock feature, add boxed_sink helper, move plan to docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>