Commit Graph
4154 Commits
Author SHA1 Message Date
Susana Ferreira 1989db0e2b feat(coderd): enforce ai budget on pre-request path (#26915)
## Description

Adds pre-request AI budget enforcement to `aibridged`. Requests are rejected with HTTP 403 when the user's aggregated spend for the current period has reached their effective limit.

## Changes

- Add `IsBudgetExceeded` RPC to `aibridgedserver`. Resolves the user's effective budget, aggregates spend over the caller-supplied `[period_start, now]` window, and returns whether the limit has been reached along with the effective limit.
- Wire the check into `aibridged`'s HTTP handler. The caller computes the period start (monthly for now) and passes it in the request.
- Reject exceeded requests with HTTP 403 Forbidden and a message directing the user to contact an administrator.
- Add `dbtime.StartOfMonth` alongside `StartOfDay` for period computation.
- Add real-DB tests covering the enforcement path: month-boundary excludes prior-period spend, and a new user override unblocks a previously-exceeded user.

Closes https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:53:36 +01:00
Susana Ferreira be9c95c8f5 feat(coderd): accumulate user daily AI spend on token usage (#26741)
## Description

Adds post-response spend accumulation to `RecordTokenUsage`.

## Changes

- Wrap the token usage insert and daily spend increment in a single transaction.
- Skip the spend update when the user is unbudgeted, the model is unpriced, or the computed cost is non-positive.

Depends on #26562

Closes https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:42:37 +01:00
Susana Ferreira fcdd029d74 feat: add ai_user_daily_spend table and queries (#26562)
## Description

Adds the spend tracking table and queries needed by [AIGOV-427](https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation) (post-response accumulation) and [AIGOV-428](https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement) (pre-request enforcement).

## Changes

- Add `ai_user_daily_spend` table to aggregate per-user, per-effective-group AI spend by UTC day.
- Add `UpsertUserAIDailySpend` and `GetUserAISpendSince` queries.

Closes https://linear.app/codercom/issue/AIGOV-426/add-daily-spend-table-and-queries

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:29:25 +01:00
Paweł Banaszewski 79a74fc817 Revert "chore: hide AI Gateway key management UI/CLI/API (#26879)" (#26913)
Reverting
https://github.com/coder/coder/commit/377c1309b7a42ead9cfbd864f0af8e9b6e472851
since release 2.35 was already cut:
https://github.com/coder/coder/tree/release/2.35
2026-07-02 13:45:05 +00:00
Cian Johnston 843754a547 refactor(coderd/x/chatd): remove dead model-routing dispatch shim (#26942)
Follow-up to #26862 ("remove direct chat routing"), which collapsed the
routing discriminated union into a single `aiGatewayModelRoute` but left
a one-path dispatch shim behind in `model_routing.go`.

Removes
`resolveModelRouteForConfig`/`resolveModelRouteForProviderType`/`newModel`
wrapper functions that did nothing but call their `*AIGateway*`
counterparts, and renames the `*AIGateway*` targets to take over those
names directly. Also collapses a redundant if/else in
`title_override.go` where both branches called the same function with
the same effective argument, and has `chatutil.NormalizedStringPointer`
delegate to the existing `coderd/util/strings.EmptyToNil` instead of
reimplementing empty-string-to-nil logic.

No behavior change.

<details>
<summary>Investigation notes / decision log</summary>

Two independent read-only investigations were run over `coderd/x/chatd`
looking for cleanup opportunities following #26862: one focused on
residue from that PR specifically, one a general over-engineering pass
on the whole package. Both independently converged on the
`model_routing.go` shim as the top finding (verified zero divergent call
sites).

Other candidates considered and explicitly deferred/rejected for this
PR:

- Renaming away the vestigial `AIGateway` prefix package-wide:
cosmetic-only, touches many call sites, skipped.
- Inlining the `chatcost` subpackage into `chatd`: unrelated to #26862,
skipped.
- Deleting the deprecated `AIGatewayRoutingEnabled` deployment flag:
confirmed dead/no-op, but intentionally kept as a back-compat shim per
#26862; removal should follow the same deprecation cadence as other
deprecated deployment options, as a separate, differently-timed change.
- Folding `chatutil` entirely into `chatprovider`/`chatopenai`:
`NormalizedStringPointer` overlapped with
`coderd/util/strings.EmptyToNil` (now reused), but `NormalizedEnumValue`
has no equivalent elsewhere in the repo and still has 2 real call sites,
so the package stays.

</details>

---
Generated by Coder Agents on behalf of @johnstcn.
2026-07-02 11:22:56 +01:00
Jon Ayers cda7a9d4f4 fix: reword autostop reminder to use relative countdown (#26948) 2026-07-01 21:24:42 -05:00
Jon Ayers 40bceeaf8d fix: nats timing flakes (#26944) 2026-07-01 17:57:42 -05:00
Yevhenii Shcherbina db7f4438b4 feat: generate STS external ID for Bedrock role assumption (#26869)
Implements:
https://linear.app/codercom/issue/AIGOV-495/add-externalid-to-prevent-confused-deputy-problem

When a Bedrock provider assumes an IAM role via STS, the gateway now
generates a unique external ID for it and sends that value on every
`AssumeRole` call. The external ID guards against the [confused deputy
problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
on cross-account role assumption. Per [AWS's
recommendation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html),
the gateway generates and owns the value rather than accepting one from
the operator; that ownership is what makes it effective, since a party
who knows another's external ID can't induce the gateway to send it.

The external ID is server-owned and read-only over the API. It is
generated once, when a provider first has a `role_arn`, and is stable
thereafter. Clients cannot set it: create rejects any supplied
`external_id`, and update rejects a value that differs from the stored
one. An update may echo the stored value back unchanged, so the normal
read-modify-write flow (GET the provider, change a field, PATCH the full
settings object) keeps working. The value is not a secret and is
returned on GET so operators can copy it into the target role's trust
policy as an `sts:ExternalId` condition.

It is persisted in the existing JSON settings blob, so there is no
migration or audit-table change.
2026-07-01 20:44:15 +00:00
Cian Johnston 4936ff9808 refactor: deprecate AIGatewayRoutingEnabled, remove direct chat routing (#26862)
This PR removes the now-dead direct-routing code:

- Deletes the direct routing implementation.
- Collapses the resolvedModelRoute discriminated union into aiGatewayModelRoute.
- Removes the dead providerKeys cascade.
- Deletes the preferredShortTextCandidates quickgen function.
- Simplifies the advisor override error handling.
- Deprecates the AIGatewayRoutingEnabled deployment option. It is now a no-op so as to not break existing deployments on upgrade.

Once direct routing was gone, the AI Gateway became mandatory for chat, which surfaced gaps in how the product behaves with the gateway disabled:

- Exposes ai-gateway-enabled to the frontend via embedded page metadata.
- Disables the chat composer via the existing AgentSetupNotice when the gateway is disabled, for both new and existing chats.
- Fixes nil/typed-nil chatDaemon panics on startup and shutdown when gateway is disabled.
- Fixes chat WebSocket from retrying the still-gated stream endpoint forever when the gateway is disabled.
2026-07-01 20:15:03 +01:00
Kyle Carberry 58f70b4488 fix(coderd/x/chatd): sanitize workspace MCP tool names (#26928)
## Summary

Workspace MCP tools (servers a workspace declares in `.mcp.json`) take
their model-facing name from the server key joined with the tool name as
`serverName__toolName`. That name reached the model **unsanitized**, so
a server or tool name containing a character outside
`^[a-zA-Z0-9_-]{1,128}$` (for example `@`) produced an invalid tool
name. Anthropic and Bedrock reject the whole request with `HTTP 400`:

```
tools.N.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$'
```

which fails the entire turn, not just the one tool. The remote MCP path
(`mcpclient`) and the AI Gateway path (`aibridge/mcp`) already sanitize;
the workspace path did not.

Alternative to #26853 (thanks @ibdafna for the report and repro).

## Fix

Sanitize and length-cap the **model-facing** name, and keep the original
`serverName__toolName` as a `routingName` the workspace agent uses to
reach the original server and tool. `NewWorkspaceMCPTools` builds a
whole set and disambiguates names that collide after sanitization (for
example server keys `foo.bar` and `foo_bar` both exposing `echo`) so
every tool stays addressable in the model's name-keyed dispatch map.
Names already within the allowed set are unchanged, so there is no
behavior change for valid names.

The sanitizer is local to `coderd/x/chatd/chattool`; the fix does
**not** touch the `aibridge` package or the remote MCP client.

### Changes
- `coderd/x/chatd/chattool/mcpworkspace.go`: local provider-safe
sanitizer + length cap, `routingName` for the agent proxy, and
`NewWorkspaceMCPTools` for set-level collision disambiguation.
- `coderd/x/chatd/chatd.go`: build the pinned workspace tool set via
`NewWorkspaceMCPTools`.

## Why sanitize here (not at `.mcp.json` / agent parse)?

The agent uses `serverName__toolName` to route to the real downstream
server (it splits on `__` and calls the original tool name), so
sanitizing at parse time would break routing or merely relocate the
original->sanitized mapping. Sanitization is also a provider constraint
the agent has no knowledge of, and coderd/agent version skew means
coderd must sanitize at its own boundary regardless. The model-facing
boundary in chatd is the right place.

## Test plan
- `@` in a name is sanitized for the model while the original routes to
the agent; a valid name is unchanged; an over-length name is truncated;
colliding names in a set are disambiguated while each still routes to
its own original name.
- `go build`, `go vet`, `golangci-lint`, and `go test
./coderd/x/chatd/chattool/...` pass locally.

<details>
<summary>Design notes / decision log</summary>

**Constraint that drives the design.** The tool name is both the
identifier shown to the model (and the key the model layer dispatches
tool calls by) and, for the workspace path, the string the agent splits
on `__` to route back to the original server and tool. Those roles
conflict once sanitization changes the name, so the name is sanitized
for the model while the unsanitized form is kept as `routingName`.

**Options considered.**
1. **Chosen:** sanitize in the workspace path only, with helpers local
to `chattool`. Smallest blast radius; no new cross-package dependency.
This matches the shape of the other MCP paths (`mcpclient` keeps
`originalName` + `configID`) without sharing code.
2. Sanitize at `.mcp.json` parse time or in the agent. Rejected: breaks
routing (the agent needs the original name), pushes a provider concern
into the agent, and coderd must still defend its own boundary because
the agent and coderd version independently. Tool names also come from
the downstream server at list time, not from `.mcp.json`, so parsing
cannot fully validate them.
3. Extract a shared sanitize/truncate/dedupe helper into `aibridge/mcp`
and adopt it in `mcpclient` too (so the remote path also gains collision
disambiguation). This DRYs all paths, but it grows chatd's coupling to
the `aibridge` subsystem and expands scope/behavior/tests in the remote
path for what is a workspace-path bug. Left out deliberately to keep
this change minimal and self-contained; it can be a separate refactor.
4. Sanitize once at the provider serialization boundary (chat loop). The
only truly generic spot, but the model dispatches by name, so it needs a
reverse (sanitized -> original) mapping and set-wide collision handling
in the model layer. Larger, riskier change.

**Notes.**
- The workspace path defines its own sanitizer (`[^a-zA-Z0-9_-]` -> `_`)
and a `maxModelToolNameLen = 64` constant that mirrors the strictest
provider limit (OpenAI 64, Bedrock 128), rather than importing
`aibridge/mcp`, so it carries no new dependency.
- The set builder sorts before assigning suffixes so disambiguation is
stable across turns.

</details>

---

_Opened by Coder Agents on behalf of @kylecarbs. Alternative to #26853._
2026-07-01 20:34:25 +02:00
Mathias Fredriksson 047c47495b refactor: drop chat_model_configs provider column (#26877)
The provider type already lives authoritatively in ai_providers.type,
reachable on every active row through ai_provider_id, which the
chat_model_configs_ai_provider_required_when_active CHECK makes
mandatory. The stored provider string was a denormalized copy the system
kept in sync with a startup backfill and no longer needs.

Every surface now derives provider type from the linked ai_providers
row. Telemetry is the one exception: it keeps emitting provider, now
sourced from ai_providers.type via a JOIN, so the BigQuery column and the
Nexus dashboards that read it are unaffected. The experimental HTTP/SDK
response drops provider and makes ai_provider_id required, since those
endpoints return only active configs; consumers resolve provider type
from ai_provider_id and the AI providers listing.

This ships in a single release with no compatibility window: production
reads the table via SELECT *, so a pre-drop binary fails config reads the
moment the column is gone. Operators must scale to zero before upgrading,
and there is no rollback.

Closes CODAGT-599
2026-07-01 15:59:55 +03:00
Jon Ayers 6b3341aad3 fix!: require org membership for user ACLs (#26852) 2026-07-01 02:15:08 -05:00
Ethan ac8cda66f7 feat: make Coder Agents right sidebar app and port tabs generally available (#26906)
The workspace-app and port preview tabs in the Coder Agents right panel
were gated behind the `agent-app-tabs` deployment experiment. This
removes the experiment entirely and renders the app and port tabs
unconditionally, so the add-panel dropdown, workspace-app tabs, and port
preview tabs are always available alongside terminals.

## Changes

- Remove the `ExperimentAgentAppTabs` constant, its `DisplayName()`
case, and its `ExperimentsKnown` registration in
`codersdk/deployment.go`, then regenerate
`site/src/api/typesGenerated.ts`, `coderd/apidoc/docs.go`,
`coderd/apidoc/swagger.json`, and `docs/reference/api/schemas.md`.
- Drop the frontend experiment gate in `AgentChatPageView.tsx`
(including the now-unused `useDashboard`/`experiments` usage) so
persisted app and port tabs are no longer filtered out.
- Remove the `appExperimentEnabled` prop from `RightPanelAddTabControl`
and render the add-panel dropdown unconditionally; update the stories
accordingly.

This reverses the gating introduced in #26395.

note: the diff is tiny if you hide whitespace changes
2026-07-01 16:58:10 +10:00
Jeremy Ruppel 3d966d48b5 fix(coderd/templatebuilder): fix archive bundling for nested static files and counted agents (#26901)
Fixes two template builder bugs that caused AWS EC2 (Linux) template
imports to fail:

1. **Missing directory entries in tar archive**: `BundleTar` wrote
static files with nested paths (e.g.
`cloud-init/cloud-config.yaml.tftpl`) without emitting `TypeDir` entries
for parent directories. The provisioner's archive extractor requires
explicit directory entries and failed with "no such file or directory".

2. **Incorrect agent reference for counted resources**:
`ExtractAgentResourceName` returned `dev` for the AWS Linux base
template, but the agent uses `count =
data.coder_workspace.me.start_count`, so module templates need
`coder_agent.dev[0].id`. The function now detects `count`/`for_each` and
appends `[0]`.

> [!NOTE]
> Generated by Coder Agents (on behalf of @jeremyruppel)
2026-06-30 19:37:30 -04:00
Jeremy Ruppel a48ace5017 fix(coderd/templatebuilder): include Terraform diagnostics in import errors (#26677)
`ClassifyProvisionerError` previously returned only the raw job error
string (e.g. "terraform plan: exit status 1") for unrecognized failures,
discarding the provisioner log lines it already had. This made template
import errors in the builder unactionable.

Now the function extracts Terraform diagnostic blocks (Error:/Warning:
lines and their context) from the provisioner logs and appends them to
the error detail. This surfaces the actual failure cause (missing
credentials, invalid references, unsupported blocks) in the ErrorAlert
banner.

Changes:
- `extractDiagnostics` parses Terraform diagnostic blocks from log
output, capped at 20 lines with a truncation marker
- Auth error classification for AWS, GCP, Azure credential failures with
a targeted user-facing message
- Case-insensitive pattern matching via `strings.ToLower` on the
combined text
- Auth branch guarded against empty diagnostics (no trailing `\n\n`)

> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
2026-06-30 18:43:58 -04:00
Jeremy Ruppel c40b3b9bcb feat: add base templates for all major cloud providers (#26634)
Add 6 new base templates to the template builder, covering all major
cloud providers and platforms:

- **scratch**: Minimal starter template with only `coder_agent` and
metadata
- **aws-windows**: AWS EC2 Windows instances with PowerShell user_data
- **azure-linux**: Azure VMs with cloud-init and managed disk
persistence
- **gcp-linux**: Google Compute Engine Linux instances with persistent
disk
- **gcp-windows**: Google Compute Engine Windows instances
- **digitalocean-linux**: DigitalOcean Linux droplets with persistent
volumes

Each base includes `base.json`, `main.tf.tmpl`, `README.md` (with
prerequisite markers), and any static files (cloud-init configs). Tests
verify all 9 bases load, render without error, and produce valid
single-agent declarations.

`azure-windows` is deferred; it needs to be registered in the `examples`
package first.

Depends on #26633

> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.

---

NB: This is very much an agent-generated PR and draws completely from
base templates that exist in `examples/templates/`. The base.json files
are new, so review those, but don't spend any brain tokens on the
correctness of the terraform and supporting files: any issues there are
issues with the upstream example template
2026-06-30 18:26:31 -04:00
Jeremy Ruppel 6f0b81cfbc feat(coderd/templatebuilder): validation extraction, static file bundling, Windows OS (#26633)
Extract validation helpers to a dedicated file, add static file bundling
for base templates, and add Windows OS support.

**Commit 1: Extract validation to `validate.go`**
Move `validateVariableValue`, `validateStringValue`,
`validateNumberValue`, `validateBoolValue`, `toHCLLiteral`, `hclQuote`,
and `isSimpleJSONValue` from `compose.go` into `validate.go`.
Corresponding tests move to `validate_internal_test.go`. This keeps
`compose.go` focused on the compose pipeline.

**Commit 2: Static file bundling**
Add `StaticFiles` field to `ComposeResult` and a `collectStaticFiles`
helper that walks the base template FS to collect non-template files
(e.g. cloud-init `.tftpl` inputs). `BundleTar` now writes these files
into the output archive in sorted order for deterministic output. This
fixes `aws-linux`, whose cloud-init files were embedded but never
included in the tar.

**Commit 3: Windows OS support**
Add `BaseOSWindows` constant and register `"windows"` in `validBaseOS`
so that base templates with `os="windows"` can be loaded and used for
module compatibility filtering.

> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
2026-06-30 18:08:02 -04:00
Danielle Maywood 6b8c38b5a4 fix: gate chat advisor and virtual desktop behind experiments, delete experiments page (#26809) 2026-06-30 21:57:40 +01:00
Paweł Banaszewski 377c1309b7 chore: hide AI Gateway key management UI/CLI/API (#26879)
Hides UI, CLI and API related to AI Gateway key management +
`/api/v2/ai-gateway/serve` endpoint.
API endpoints and CLI commands are still working they are just not
visible.
2026-06-30 17:32:38 +00:00
Mathias Fredriksson 2fd5ae4323 fix: stop Agents dead-ending on unsupported providers (#26841)
Configuring only a GitHub Copilot provider left the Agents page stuck on
"set up a provider then add a model", even with a provider and models
configured. The catalog dropped any provider type that NormalizeProvider
did not recognize, so a Copilot-only deployment looked identical to an
empty one and never unlocked the page.

The Agents harness cannot use Copilot: it needs a per-request token only
an official Copilot client can mint, and the harness is not one. Instead
of dropping such providers, the catalog now reports them as unsupported
so the UI can explain the dead end and point elsewhere, rather than ask
for setup that already happened. The providers stay usable through the
AI Gateway proxy.

Support is derived from the provider type, not stored, so there is no
migration. codersdk.IsAgentsUnsupportedProviderType is the single source
of truth, consulted by the chatd catalog and, through the generated
AgentsUnsupportedProviderTypes list, the frontend.

The diff also carries unrelated modernization of nearby db2sdk and
chatprovider helpers (slices.SortFunc, strings.Cut, range-over-int).

Closes CODAGT-627
Refs CODAGT-256
Refs CODAGT-682
2026-06-30 18:49:50 +03:00
Sas Swart d179266cc7 feat: capture, persist, and strip Agent Firewall correlation headers in AI Bridge (#26529)
Wire the Agent Firewall correlation headers
(`X-Coder-Agent-Firewall-Session-Id` and
`X-Coder-Agent-Firewall-Sequence-Number`) through the AI Bridge
interception processor so that each interception is linked to its
originating firewall session.

Closes https://linear.app/codercom/issue/AIGOV-259

> Generated by Coder Agents on behalf of @SasSwart

**Data flow:**
`request header` → `bridge.go` reads + strips → `InterceptionRecord` →
`translator.go` → proto `RecordInterceptionRequest` →
`aibridgedserver.go` → DB
2026-06-30 14:01:27 +02:00
Cian Johnston e5b7e74847 test: migrate chatd tests to AI Gateway routing (#26658)
Refs CODAGT-681

Migrates all chatd tests from `AIGatewayRoutingEnabled = false` (direct
routing) to AI Gateway routing using the test helpers extracted in
#26639.

- `coderd/x/chatd/chatd_test.go` — 6 full-server tests migrated to
`NewWithAPI` + daemon, `directChatRoutingDeploymentValues` helper
deleted, 3 bare-chatd tests renamed
- `coderd/x/chatd/context_integration_test.go` — 2 tests migrated
- `coderd/exp_chats_test.go` — `chatDeploymentValues` helper deleted,
all 5 helper functions now use `NewWithAPI` + daemon internally (no call
site changes)
- `coderd/exp_chats_acl_test.go` — stale `chatDeploymentValues`
reference replaced
- `enterprise/coderd/exp_chats_test.go` — 9 sites across 5
`TestChatStreamRelay` subtests migrated
- `cli/exp_scaletest_chat_test.go` — 1 test migrated
- `coderd/x/chatd/model_routing_internal_test.go` — 1 direct-only test
removed
- `coderd/x/chatd/chatd_internal_test.go` — 1 direct-only test removed

> 🤖
2026-06-30 12:17:42 +01:00
Cian Johnston cb6a75717c fix(coderd/x/chatd): bind goInflight contexts to server lifetime (#26811)
fix(coderd/x/chatd): bind goInflight contexts to server lifetime
- Add Server.inflightContext: WithoutCancel(reqCtx) bound to p.ctx via
  context.AfterFunc, so Close cancels in-flight work instead of blocking
  on the caller's timeout while a provider is unreachable.
- Apply at GenerateChatTitleAsync, finalizeSuccessfulTurnStatusLabelWithAfterFunc,
  setLastTurnSummaryAsync, clearLastTurnSummaryAsync, and scheduleDebugCleanup.
- Honor cleanupCtx in the debug retry-delay timer so cancellation lands
  promptly between attempts.

> 🤖
2026-06-30 10:51:59 +01:00
Jon Ayers 7179be24fa feat: skip autostop reminder for active workspaces (#26772) 2026-06-30 00:24:29 -05:00
Jeremy Ruppel 7daf3123cb feat: import new modules and refactor codegen script (#26838) 2026-06-29 17:47:06 -04:00
Jeremy Ruppel 48e8f70e09 fix: remove Goose module from catalog (#26833)
Removes the Goose AI agent module from the template builder backend
catalog.

## Changes

- Deleted `coderd/templatebuilder/modules/goose/` (Terraform template
and module metadata)
- Removed the `"goose"` entry from
`scripts/templatebuildermodulegen/main.go`

Frontend assets (`goose.svg`, `icons.json`) are intentionally left in
place as other parts of the app still reference them.

> Generated by Coder Agents on behalf of @jeremyruppel
2026-06-29 16:52:41 -04:00
J. Scott Miller 1dea00dd04 fix: deflake TestWorkspaceTagsTerraform with context-aware build waits (#26315)
`TestWorkspaceTagsTerraform` runs a real terraform provisioner but
waited on builds with `coderdtest` helpers whose deadlines are sized for
the echo provisioner used by most tests, which replays canned responses
and completes in well under a second. On Windows runners, where
terraform providers are not cached and every `terraform init` downloads
from the registry, template imports exceeded the 25s budget in
`AwaitTemplateVersionJobCompleted` and workspace builds exceeded the 10s
context in `AwaitWorkspaceBuildJobCompleted`, even though the test
intends a 120s budget.

Add `AwaitTemplateVersionJobCompletedWithTimeout` and
`AwaitWorkspaceBuildJobCompletedWithTimeout`, which take a
caller-provided wait bound, and use them in the test with
`2*testutil.WaitSuperLong` (120s). Also fix
`AwaitWorkspaceBuildJobCompleted` creating a `WaitShort` (10s) context
while polling for `WaitMedium` (15s), which guaranteed `context deadline
exceeded` errors for the final five seconds of polling.

`TestWorkspaceTemplateParamsChange` has the same shape (real terraform
provisioner, 120s test context, plain await helpers) and the same latent
bug, so it gets the same fix.

Closes https://github.com/coder/internal/issues/1470 (Linear: PLAT-176)

<details>
<summary>Root cause analysis</summary>

Two CI failures, same mechanism:

- 2026-04-16 (run 24493089585, windows-2022):
`overrides_with_dynamic_option_from_var/dynamic` failed at
`coderdtest.AwaitTemplateVersionJobCompleted` with `Condition never
satisfied ... make sure you set IncludeProvisionerDaemon!`. The template
import job (real terraform init/plan, with network provider download)
did not complete within `WaitLong` (25s).
- 2026-05-27 (run 26492817796, windows-2022): `tag_param/dynamic` failed
at `coderdtest.AwaitWorkspaceBuildJobCompleted` with `failed to get
workspace build ...: context deadline exceeded`. The helper's internal
context was `WaitShort` (10s) while its polling window was `WaitMedium`
(15s), so after 10s every poll could only fail. The logged `terraform
apply: exit status 1` and the `TempDir RemoveAll ... Access is denied`
cleanup error are consequences of test teardown canceling the in-flight
job while the provider exe was still file-locked.

The test declares a 120s budget (`2*testutil.WaitSuperLong`, commented
"This can take a while"), but the await helpers ignored it and applied
their own 10-25s budgets. `testutil.CacheTFProviders` is a no-op on
Windows, so real builds are much slower there.

This change raises the ceiling for the tests rather than making
terraform faster; both observed failure signatures are eliminated. The
default helper budgets are unchanged for the ~880 existing call sites.
One small behavior change: `AwaitTemplateVersionJobCompleted` previously
marked the test failed on any transient poll error via `assert.NoError`;
it now logs and keeps polling, matching the workspace build helper, and
still fails on timeout.

`TestWorkspaceTemplateParamsChange` is the sibling real-terraform test
in the same file (also covered by the original provider-caching work in
#20603). It runs three sequential real builds with the plain await
helpers under a 120s context, so it is exposed to the same Windows
slowness even though it has not produced its own issue yet. Its context
is raised to `6*testutil.WaitSuperLong` to outlast three sequential
await budgets.

API note: a context-taking variant was considered first, but a
`time.Duration` parameter avoids an implicit "context must have a
deadline" contract and matches how the existing helpers manage their own
wait budgets.

</details>

---

🤖 This PR was generated by Coder Agents on behalf of @jscottmiller.
2026-06-29 09:56:42 -05:00
Susana Ferreira 56373a09fc chore: rename user-facing AI Bridge strings to AI Gateway (#26700)
Rename user-facing "AI Bridge" strings to "AI Gateway" in deployment
config, RBAC display names, log messages, error strings, docs style
guide, and Grafana dashboard README.

Deprecated option names and descriptions (the `--aibridge-*` block) are
intentionally kept as "AI Bridge". The `Name` field cannot be renamed
because `serpent` uses it as a unique key during JSON serialization;
duplicating names causes `UnmarshalJSON` failures (e.g. in the support
bundle). Descriptions also stay as "AI Bridge" to avoid confusion
between the deprecated and primary options.

Refs https://linear.app/codercom/issue/AIGOV-226

> Generated with the assistance of Coder Agents (@ssncferreira)
2026-06-29 14:33:22 +01:00
Danny Kopping ce94d42e19 feat: fetch providers over DRPC (#26650)
Closes [AIGOV-455](https://linear.app/codercom/issue/AIGOV-455/extend-drpc-with-buildproviders).

## Why

The AI Gateway (`aibridged`) is being split into a standalone process that must not touch the database. `coderd` stays the source of truth and seeds the `ai_providers` / `ai_provider_keys` tables from the environment. This PR adds a DRPC call so the gateway fetches provider config from `coderd` instead of reading the DB, for both the embedded and standalone daemons.

## What

- **Proto:** new `ProviderConfigurator` service with a unary `GetAIProviders` RPC, plus `AIProvider` / `AIProviderBedrock` messages. `CurrentMinor` bumped to 1 (additive).
- **Server (`coderd/aibridgedserver`):** `GetAIProviders` runs a read-only `InTx` under `LockIDAIProvidersEnvSeed` so it never returns a mid-seed snapshot, reads providers (incl. disabled) plus keys for enabled ones, and maps to proto under `dbauthz.AsAIBridged`. Unmappable rows are skipped and logged; plaintext keys and Bedrock secrets are never logged.
- **Client:** `DRPCProviderConfiguratorClient` wired into the client union, `dialer.go`, and `CreateInMemoryAIBridgeServer`.
- **cli:** `BuildProvidersFromProto` maps the response through the existing DB-neutral `buildProvider`. A shared `poolRPCReloader` does the fetch/build/replace for both daemons: the embedded daemon reloads on every `ai_providers` change and fails startup if it cannot subscribe; the standalone gateway drives the same reloader once at startup, retrying until success and staying interruptible.
- **Dead code removed:** `BuildProvidersFromConfig`, `ProvidersFromConfig`, `AIProviderFromConfig`, and the DB-read `BuildProviders` path.
2026-06-29 13:34:58 +02:00
Cian Johnston 74b8f10d4e fix(coderd/x/chatd): drop foreign provider-executed tools on model switch (#26555)
Drops provider-executed tool history (calls and results) from
assistant rows whose producing provider ID differs from the target turn's
provider ID, before the prompt is built. Same-provider history is left
untouched, so normal `web_search` replay is unaffected.

- When a model config has an `AIProviderID`, use this as identity so two
providers of the same type (e.g. two `openai-compat` providers at
different base URLs) are correctly distinguished. Falls back to
the normalized provider type name. 
- Sanitization runs at the `database.ChatMessage` row level in
`prepareGeneration`. The `chatloop`
pre-request and reload paths are untouched.
- Foreign provider-executed results are dropped and not converted
  to text.
- Unknown origin (unresolvable `ModelConfigID`) fails closed (strip).
- Adds tests for the pure `stripForeignProviderExecutedToolRows`.
- Adds unit tests for `modelConfigProviderIdentity`.

_This pull request was created by Coder Agents on behalf of @johnstcn._
2026-06-29 11:49:10 +01:00
Cian Johnston 5942cec329 fix: synchronize bridge and pool shutdown with in-flight requests (#26743)
When adding chatd tests to route through a real in-process `aibridged`
daemon (#26658), found two races:

- **Pool:** `CachedBridgePool.Shutdown` calls `cache.Close()` while an
in-flight `Acquire` runs `cache.Wait()`. ristretto closes the channel
`Wait` sends on.
- **Bridge:** `RequestBridge.ServeHTTP` does `inflightWG.Add(1)` after
the `b.closed` check, racing `Shutdown`'s `inflightWG.Wait()`.

## Fix

- `RequestBridge`: adds `admitMu` RWMutex to order `inflightWG.Add`
(ServeHTTP, read) before `close(b.closed)` (Shutdown, write).
- `CachedBridgePool`: adds `opsMu` + `opsWG` so `Shutdown` drains
in-flight `Acquire`/`ReplaceProviders` before `cache.Close()`
- Adds tests `TestRequestBridgeShutdownAdmissionRace` and
`TestPoolShutdownReplaceProviders` for above. (Note:
`TestRequestBridgeShutdownAdmissionRace` leverages a `serve_admission`
quartz trap added to `RequestBridge`).

---

> 🤖 Created by Coder Agents on behalf of @johnstcn.
2026-06-29 11:33:21 +01:00
McKayla はな efd93027ce feat: allow editing user avatars (#26652)
Adds an avatar URL field to the admin **Edit user** page, available only
for users whose login type is `password` or `none`.

For identity-provider login types (`github`, `oidc`) the avatar is
synced from the IdP on every login, so the field is hidden and the API
ignores any submitted avatar to avoid confusing overwrites.

The field reuses the same emoji picker + URL input (`IconField`) already
used for template, group, and organization icons.

A follow-up PR will add the same control to the self-service Account
settings page.

<details>
<summary>Implementation plan & decisions</summary>

**Goal:** Let an admin set/clear a user's avatar from the Edit user
page, gated to `password`/`none` login types.

**Backend**
- Add `avatar_url` to `codersdk.UpdateUserProfileRequest`.
- `putUserProfile` applies the submitted avatar only for
`password`/`none`; otherwise it preserves the existing (IdP-synced)
value.
- Regenerated TS types and API docs via `make gen`.

**Frontend**
- `EditUserForm` renders an `IconField` ("Avatar URL") when the login
type allows it.
- `EditUserPage` passes the avatar value and a `canEditAvatar` flag.
- `AccountPage` round-trips `avatar_url` so the shared request type
doesn't wipe avatars on the self-service path.

**Gating** is enforced in both the UI (field hidden) and the backend
(submitted value ignored for IdP login types).

**Tests/stories:** backend `TestUpdateUserProfile` covers apply
(password) and ignore (SSO); `EditUserForm` stories cover the
shown/hidden states with interaction tests.

</details>

---
> Generated by Coder Agents on behalf of @aslilac.
2026-06-26 16:35:03 -06:00
Kyle Carberry d6daf273aa fix: tighten single tool result byte budget (#26763)
## Problem

#26637 caps each locally-executed tool result (built-in,
global/deployment MCP, workspace MCP) at a per-result byte budget
derived from the model's context window. The budget was `ContextLimit/2
* 4 bytes` — i.e. **half the window at an optimistic 4 bytes/token**.

On large-context models that is far too generous. With a
`1,000,000`-token `ContextLimit` the per-result cap is **~2 MB**. A user
hit exactly this with a chatd (deployment-pinned) MCP tool: the result
was truncated to **1,998,709 characters** and still overflowed the
prompt. 2 MB of dense text (JSON/logs/code) is ~650k–1M tokens — most or
all of the window for a *single* result — so the cap fired but didn't
actually prevent the overflow.

## Fix

Tighten the two budget constants in `tooltruncate.go`:

| constant | before | after |
| --- | --- | --- |
| `toolResultContextDivisor` | `2` (½ window) | `3` (⅓ window) |
| `bytesPerTokenEstimate` | `4` | `3` (conservative) |

The budget becomes `ContextLimit/3 * 3 ≈ ContextLimit` bytes:

| ContextLimit | before | after |
| --- | --- | --- |
| 1,000,000 | ~2 MB | ~1 MB |
| 200,000 | ~400 KB | ~200 KB |
| unknown (≤0) | 64 KB | 64 KB (unchanged) |

The 16 KB floor and 64 KB unknown-window default are unchanged. A
conservative bytes-per-token estimate is intentional: dense payloads run
well under 4 B/tok, so a lower estimate yields a smaller byte budget
that is less likely to underestimate the true token cost.

No behavioral code paths change — only the two constants and their doc
comments. The existing `tooltruncate_internal_test.go` cases derive
their expectations from the constants (`LargeWindow`) or exercise the
floor/default (`BelowFloor`, `Unknown`), so they remain green.

<details>
<summary>Investigation notes</summary>

Global/deployment MCP tools (`mcpclient.ConnectAll`) are appended to
`prepared.Tools` and execute locally via `ExecuteLocalTools →
executeTools → executeSingleTool`, so the #26637 cap *does* apply to
them for text results (`convertCallResult` joins text content into
`resp.Content`). The cap was simply too large:
`toolResultByteBudget(ContextLimit)` = `ContextLimit/2*4` ≈ 2 MB for a
1M-token window. Reverse-engineering the reported `1,998,709` truncated
characters confirms a `ContextLimit` of ~1,000,000 tokens.

Known gaps left for follow-ups (out of scope here):
- **Per-step aggregate is unbounded.** MCP tools advertise `Parallel:
true` and `executeSingleTool` caps each result independently, so N
parallel calls in one step can sum to N × the per-result cap.
- **Binary/media `Data` bypasses the cap.** Only the text payload is
bounded; `image`/`media`/blob embedded-resource results are
base64-encoded untouched in `executeSingleTool`.
- **Compaction is reactive.** It is gated on the prior step's reported
usage (`latestPromptUsage`), so it can't pre-empt a single large result
appended on the current step.
</details>

---

Generated by Coder Agents on behalf of @kylecarbs.
2026-06-26 13:33:55 -06:00
Callum Styan 8ff2109298 feat: add nats_ca crypto_key_feature enum value (#26761) 2026-06-26 11:59:00 -07:00
Paweł Banaszewski c15d483863 chore: rename 'last_used_at' column (#26749)
Renames the `last_used_at` column  to `last_heartbeat_at` in `ai_gateway_keys` table.  
`ai_gateway_keys` table has not been released yet.  
All references updated.
2026-06-26 18:45:37 +02:00
Paweł Banaszewski 6189d6e386 feat: add /api/v2/aibridge/serve endpoint (#26506)
Adds a new enterprise-only `GET /api/v2/ai-gateway/serve` endpoint that standalone AI Gateway replicas use to connect to `coderd` over a DRPC-over-WebSocket transport, mirroring the existing in-memory path used by the embedded AI Bridge daemon.

- The endpoint upgrades the HTTP connection to a WebSocket, multiplexes it with yamux, and finally serves the three DRPC services (Recorder, MCPConfigurator, Authorizer).
- The `X-AI-Governance-Gateway-Key` header is used for authentication.
    - The key is looked up by its hashed secret
    - Missing or revoked keys return `401`.
- API version negotiation is enforced via a new `aibridged/proto` version (`v1.0`).
    - Incompatible versions return `400`.
- `FeatureAIBridge` entitlement is required.
- Key liveness (`last_used_at`) is recorded immediately on connection and refreshed every 60 seconds while the session remains open.
  - When key liveness detects the key was deleted (no rows where updated) session is closed.

#### Small refactors

* The three DRPC service registrations are extracted into `aibridgedserver.Register`, shared by both the in-memory and WebSocket paths.

* The literal `256 * 1024` used as the yamux-aligned WebSocket read limit is replaced with the named constant `drpcsdk.YamuxDefaultStreamWindowSize` in all call sites.
  * as noted in review comment https://github.com/coder/coder/pull/26506#discussion_r3461905223 order of `SetReadLimit` and `WebsocketNetConn` calls was fixed.
2026-06-26 18:27:37 +02:00
Steven Masley ad355aeaa9 feat: add INSECURE oidc email fallback flag for IdP brokers (#26751)
<!-- Authored by Coder Agents on behalf of @Emyrk. -->

Adds an opt-in `CODER_DANGEROUS_OIDC_EMAIL_FALLBACK` flag (alias
`--dangerous-oidc-email-fallback`) for IdP brokers that do not issue a
stable `sub` for the same user across connections.
2026-06-26 11:23:42 -05:00
Paweł Banaszewski 0f1e792f3f feat(coderd/database): add AI Gateway key auth lookup and last-used queries (#26505)
Adds DB methods`GetAIGatewayKeyIDByHashedSecret` and `UpdateAIGatewayKeyLastUsedAt`.
`GetAIGatewayKeyIDByHashedSecret` - returns AI Gateway key ID by hashed secret value.
`UpdateAIGatewayKeyLastUsedAt` - updates last used timestamp for given AI Gateway key. 
Used by standalone AI Gateway for authentication and keeping track of currently used keys.
2026-06-26 18:16:01 +02:00
Jeremy Ruppel 7bdeee166c fix(coderd/templatebuilder): shorten long module variable descriptions (#26747) 2026-06-26 10:57:01 -04:00
Cian Johnston 387011d725 test: extract AI Gateway test helpers for chatd (#26639)
Extracts test infrastructure for AI Gateway routing into shared helpers
under a new package `coderd/aibridgedtest` so both AGPL and enterprise
tests can use them.

- aibridgedtest.StartTestAIBridgeDaemon` spins up a real in-process 
  aibridged daemon wired to fake upstream providers.
- `chattest.MockAIBridgeTransport` is a mock `aibridge.TransportFactory`
   for the 3 bare-chatd tests that use `newActiveTestServer`.

> 🤖 Generated by Coder Agents under the eyes of a human.
2026-06-26 14:33:26 +01:00
Spike Curtis 98e1ce133c chore: modify replicasync to handle NATS explicitly (#26666)
relates to GRU-69

Modifies replicasync to handle discovering NATS enabled primary replicas explicitly, and passing that info to the NATS Pubsub.

This PR adds a new deployment value to explicitly represent the host or IP that the replica can be reached on. It isn't wired up to the CLI, but piggybacks on the DERP config for now.

We learn the NATS port directly from NATS at runtime, and propagate it thru replicasync to learn all peers for clustering.
2026-06-26 08:36:32 -04:00
Spike Curtis 612e50dab7 test: fix azureidentity Verify test by replacing with recent token (#26720)
fixes https://github.com/coder/internal/issues/1602

The `TestValidate/regular` case was failing because it was chaining to a root CA that expired in 2025. We didn't see it until last week because we fake the validation time for the test, but still get the CA certificate itself from the OS. Presumably our CI runners OS got upgraded last week to a version that doesn't ship that CA cert, so we fail to validate, even with the faked time.

I spun up a new Azure instance and grabbed its identity document to update the test, and validated that it chains to a CA that expires in 2038, so we should be good to go for a long time.

I also checked the other test cases, and they had already migrated to the new CA, so don't need to be updated yet.

However, if we want to remove any expired intermediate certificates that are used in the test, we'll have to get new tokens for govcloud at the very least.

I also removed the "TestExpiresSoon" test case because we have been skipping it and _not_ removing expiring intermediates (presumably because of the `TestValidate` test cases. Also it makes no sense to remove intermediates when they are expiring "soon" but have not expired. It poses negligible danger to keep the old intermediates around, since we trust the OS to give us the correct time in production.
2026-06-26 07:24:21 -04:00
Mathias Fredriksson 59fcc9c0ad feat: improve sub-agent orchestration tools (#26673)
Tool errors caused orchestrators to abandon spawned agents. Bare error
responses and the close_agent name framed delegation as one-shot: one
transient failure or timeout ended the work, and the orchestrator had no
way to recover or reuse agents.

Renames close_agent to interrupt_agent with a hidden backward-compatible
alias. wait_agent and message_agent return structured payloads instead
of bare errors, so the orchestrator can retry after a timeout, recover
from an error status, or redirect an idle agent. Adds list_agents so
orchestrators can rediscover spawned agents. Adds root-only
orchestration guidance for error recovery.
2026-06-26 13:41:43 +03:00
Jon Ayers 637a801a41 feat: notify users before workspace autostop (#26676) 2026-06-26 01:25:23 -05:00
dylanhuff-at-coder fde3639714 fix(coderd): enforce required external auth on workspace create (#26314)
Required external auth (`optional = false`) was only enforced by
client-side preflight checks, so creating a workspace via the REST API
succeeded even when the owner had never authenticated, producing a
broken workspace.

`createWorkspace` now validates the workspace owner's external auth
server-side and returns 403 before any row is inserted or prebuild is
claimed. The owner (not the initiator) is checked because build-time
token injection uses their links, so this also covers admin-on-behalf-of
creates and prebuild claims. Use `optional = true` to allow
pre-provisioning for unauthenticated users.

Fixes PLAT-241.

> This PR was generated by Coder Agents on behalf of
@dylanhuff-at-coder.
2026-06-25 15:47:35 -07:00
Zach 953091c7bc refactor: use sync.WaitGroup.Go in tests (#26671)
Migrate `wg.Add(1); go func() { defer wg.Done(); ... }()` to
`wg.Go(func() { ... })` in tests.

Where the prior pattern passed the loop variable explicitly via a
closure parameter (`go func(id int) { ... }(i)`), drop the parameter and
reference the loop variable directly. Per-iteration loop variables since
Go 1.22 make this safe.
2026-06-25 15:41:09 -06:00
Kyle Carberry 48fd0ef4bc feat: return workspace skill directory from read_skill (#26713)
Workspace skills live on the workspace filesystem, and the agent's read_file
and execute tools already operate there. read_skill now returns "dir", the
absolute skill directory, for workspace skills, so the agent can read or run
bundled supporting files (for example a scripts/ helper) with the workspace
tools. The field is omitted for personal skills, which are database-backed and
have no files. read_skill_file is unchanged.

Generated with Coder Agents on behalf of @kylecarbs.
2026-06-25 12:05:54 -06:00
Jon Ayers 7e7a6b4f18 fix: prevent open redirect in proxy authentication flow (#26647) 2026-06-25 11:17:23 -05:00
Susana Ferreira 5cae613af1 docs: rename AI Bridge to AI Gateway in swagger summaries (#26704)
Update `@Summary` and `@ID` annotations in
`enterprise/coderd/aibridge.go` from "AI Bridge" to "AI Gateway".
Regenerate swagger docs and API reference via `make gen`.

This was missed in the original API route aliases PR (#26475) which
renamed `@Tags` but not `@Summary` or `@ID` values. The `@ID` must also
change because a test (`assertConsistencyBetweenRouteIDAndSummary`)
enforces that the ID is the kebab-case form of the summary.

Refs https://linear.app/codercom/issue/AIGOV-230

> Generated with the assistance of Coder Agents (@ssncferreira)
2026-06-25 16:56:37 +01:00
Steven Masley 84350e4e7c feat: report SCIM configuration on Deployment (#26628)
Adds two nullable booleans to `telemetry.Deployment`:

- `SCIMEnabled`: `true` when `CODER_SCIM_AUTH_HEADER` is set.
- `SCIMUseLegacy`: `true` when `CODER_SCIM_USE_LEGACY` is set.

Both mirror `Deployment.IDPOrgSync`: nullable for backward
compatibility, and report configuration state rather than license
entitlement (#16323).

Lives on `Deployment` rather than `Snapshot` so the existing
`bqDeployment` table on `coder/coder-telemetry-server` gets two columns
instead of a new table.

`SCIMAPIKey` is annotated as a secret and is scrubbed by
`WithoutSecrets` before the config reaches telemetry, so
`DeploymentConfig.SCIMAPIKey` is always empty in production. The
booleans are pre-computed from the pre-scrub `DeploymentValues` in
`cli/server.go` and passed in via `telemetry.Options.SCIMEnabled` /
`SCIMUseLegacy`.

Pairs with
[coder/coder-telemetry-server#43](https://github.com/coder/coder-telemetry-server/pull/43),
which adds the matching `bqDeployment` columns and the manual BigQuery
`ALTER TABLE` step.

---

Generated by Coder Agents on behalf of @Emyrk.
2026-06-25 08:54:37 -05:00