Commit Graph

4152 Commits

Author SHA1 Message Date
Kayla はな 2943bf5f21 fix(site): use ExternalImage for icon URLs (#25315) 2026-05-13 17:52:05 -06:00
Danielle Maywood ef1093d0dd fix(site): hide sticky metadata user messages (#25316) 2026-05-13 22:27:38 +01:00
Zach e0be9bf213 feat: surface missing coder_secret requirements on resolve-autostart (#25081)
Adds `dynamicparameters.EvaluateSecretMismatch` as a shared helper on
top of the existing renderer, then wires it into the resolve-autostart
handler so the UI can surface unsatisfied `coder_secret` requirements in
a template alongside parameter mismatch for autostart.

The lifecycle executor changes will land in a follow-up that depend
on this helper. The UI changes that consume the new `secret_mismatch`
field is also a follow-up.

Generated with assistance from Coder Agents.
2026-05-13 14:20:02 -06:00
Kayla はな 660fa9478f style(site): use shorthand for boolean JSX props (#25096) 2026-05-13 10:56:50 -06:00
Danielle Maywood 7fe4d97fd0 fix(site): align streaming thinking spacing (#25291) 2026-05-13 17:05:34 +01:00
Thomas Kosiewski b9b8d763e3 refactor(site/src/pages/AgentsPage): break AgentChatPage circular dep (#25287)
`AgentChatPageView.tsx` imported `getPersistedSidebarTabId` /
`savePersistedSidebarTabId` from `AgentChatPage.tsx`, which already
imports `AgentChatPageView`, closing a cycle that `pnpm run
lint:circular-deps`
reports but doesn't fail on (dpdm defaults to exit code 0; the script
is missing `--exit-code circular:1`).

Move the three sidebar-tab localStorage helpers and the key prefix into
`utils/sidebarTabStorage.ts` alongside `draftStorage.ts` and the other
per-chat storage modules. Pure code move, no behavior change.

After this change, `pnpm run lint:circular-deps` reports zero cycles.

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:13:32 +02:00
Jaayden Halko 36200b625e fix: fix flaky storybook test (#25272) 2026-05-13 16:08:18 +01:00
Danielle Maywood b52c0bdb56 fix(site/src/pages/AgentsPage/components): unify live thinking spacing and sizing (#25192) 2026-05-13 12:51:49 +01:00
Kayla はな 9810d299d2 chore: make dynamic parameters TemplateEmbedPage the default (#25069) 2026-05-12 18:28:36 +00:00
Thomas Kosiewski 969da320ec feat: export Coder Agents debug logs (#25039)
Adds JSON export actions to the Coder Agents Debug panel so users can download either the current chat's recent debug runs or one expanded run for support sharing.

The export reuses the existing chat debug endpoints and react-query cache, adds Storybook and unit coverage for the JSON envelope, and updates the chat debug logging docs with UI and cURL instructions.

Refs CODAGT-280.

Generated by Coder Agents.

<details>
<summary>Implementation notes</summary>

- Chat-level export fetches full detail for each listed debug run with `queryClient.fetchQuery(chatDebugRun(chatId, run.id))` and writes a single JSON file.
- Run-level export uses the already-loaded detail query data from an expanded run card.
- The JSON envelope includes `version`, `scope`, `exported_at`, `chat_id`, and either `runs` or `run`.
- The chat-level export reflects the current backend list endpoint behavior, up to the 100 newest debug runs.
- Agent-browser dogfooding verified files were downloaded and that `jq` validated the chat-level and run-level JSON contents.

</details>
2026-05-12 17:39:57 +02:00
Yevhenii Shcherbina b5e1ea33d8 feat: add AI budget policy and period deployment config (#25122)
Closes
https://linear.app/codercom/issue/AIGOV-283/add-deployment-config-for-ai-budget-policy-and-period

Adds `CODER_AI_BUDGET_POLICY` and `CODER_AI_BUDGET_PERIOD` deployment
options for AI Governance cost controls.
2026-05-12 10:48:36 -04:00
Ethan fabf7d31fc test: use default provider in TestPatchChatMessage/ChangesModel (#25189)
`TestPatchChatMessage/ChangesModel` hardcoded `"openai"` as the provider
for the override model config. After #25171, the shared chat test
harness registers a single `"openai-compat"` provider by default, so
calling `createAdditionalChatModelConfig(..., "openai", ...)` fails with
HTTP 400 `Chat provider is not configured` before the test can exercise
the model-change path. The subtest was added in #25084 after #25171 was
reviewed, so the harness change and the new hardcoded provider only met
on `main`.

Use `defaultModel.Provider` so the override always matches whatever
provider the harness registered. This mirrors every other call site of
`createAdditionalChatModelConfig` in the file.

Closes https://github.com/coder/internal/issues/1530
2026-05-12 14:05:08 +00:00
Thomas Kosiewski 38091f1d82 fix(site/src/pages/AgentsPage/components/ChatConversation): remove attachment copy actions (#25119)
Messages with chat file attachments showed a `Copy message` action even
though the action only copied `parsed.markdown` and omitted the
attachment content.

Hide the message copy action whenever a parsed message contains file
attachments, and add regression coverage for both user and assistant
attachment messages.

Refs
https://linear.app/codercom/issue/CODAGT-344/remove-or-fix-copy-buttons-on-file-attachments

Generated by Coder Agents.
2026-05-12 15:46:39 +02:00
Atif Ali 97ee54a8c1 fix(site): clarify deleted workspace actions (#25186)
## Summary
Updates the deleted workspace banner so its CTA recreates from the
original template instead of sending users to the generic templates
list.

## Changes
- Change the CTA to `Create another from <template>`.
- Link the CTA to the original template workspace create flow.
- Shorten the banner copy to `This workspace has been deleted.`

## Validation
- `pnpm exec biome check --error-on-warnings
src/pages/WorkspacePage/Workspace.tsx
src/pages/WorkspacePage/WorkspaceDeletedBanner.tsx
src/pages/WorkspacePage/WorkspaceDeletedBanner.stories.tsx`
- `pnpm run lint:types`
- `git diff --check`
- `git commit` pre-commit hooks

> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻
2026-05-12 18:45:20 +05:00
Kyle Carberry b0b07536fc feat: add opt-in Coder identity headers for MCP servers (#25153) 2026-05-12 08:54:53 -04:00
Michael Suchacz f1d160c7f4 fix: allow changing model when editing earlier chat message (#25084)
Editing a previous user message and selecting a different model in the
picker silently kept using the original model: the selection was dropped
on the frontend, in the SDK, and in the backend, so both the replacement
user message and the assistant turn that followed ran against the old
model.

Plumb the selected model through all three layers (`AgentChatPage`,
`codersdk.EditChatMessageRequest`, `chatd.EditMessageOptions` /
`Server.EditMessage`), defaulting to the original message's model when
the client does not specify one. The existing `InsertChatMessages` CTE
already advances `chats.last_model_config_id` when the inserted
message's model differs, so the assistant turn picks up the new
selection without further changes. The new model is validated inside the
transaction, so an unknown ID rolls the edit back and returns a 400
`Invalid model config ID.`, mirroring the `SendMessage` path.

Refs: CODAGT-345

This change was generated by a Coder agent.

<details>
<summary>Implementation plan</summary>

# CODAGT-345: Editing an earlier message cannot change model

## Problem

When editing a previous user message in a chat, the user can change the
model in the model picker, but the backend keeps using the original
message's model. The model selection is dropped at three layers:

1. **Frontend:** `AgentChatPage.tsx`'s edit branch builds an
`EditChatMessageRequest` that omits `model_config_id`. The new-message
branch (a few lines below) does include it.
2. **SDK:** `codersdk.EditChatMessageRequest` has no `ModelConfigID`
field at all.
3. **Backend:** `chatd.EditMessageOptions` has no model field, and
`Server.EditMessage` always copies the original message's
`ModelConfigID` into the replacement message.

Once the replacement user message is inserted with the original model,
the `InsertChatMessages` CTE leaves `chats.last_model_config_id`
unchanged, so the assistant turn that follows runs against the old
model.

## Fix

Plumb the selected model through all three layers, defaulting to the
original message's model when the client doesn't override it. This
mirrors the `SendMessage` path, which already accepts a
`model_config_id` and validates it via
`resolveSendMessageModelConfigID`.

### Backend

- `codersdk/chats.go`: add `ModelConfigID *uuid.UUID` to
`EditChatMessageRequest`.
- `coderd/x/chatd/chatd.go`:
  - Add `ModelConfigID uuid.UUID` to `EditMessageOptions`.
- In `EditMessage`, after fetching the edited message, resolve the
model: if `opts.ModelConfigID != uuid.Nil`, validate it exists with
`tx.GetChatModelConfigByID` (using `chatdModelConfigLookupContext`),
otherwise keep `editedMsg.ModelConfigID.UUID`. Pass the resolved ID into
`newChatMessage(...)`.
  - Reuse the existing `ErrInvalidModelConfigID` sentinel.
- `coderd/exp_chats.go` (`patchChatMessage`):
- Read `req.ModelConfigID` (nil-safe), pass into
`chatd.EditMessageOptions`.
- Add a `case xerrors.Is(editErr, chatd.ErrInvalidModelConfigID)` arm
returning 400 `Invalid model config ID.`, matching the
`postChatMessages` handler.

### Frontend

- `site/src/pages/AgentsPage/AgentChatPage.tsx`:
- In the edit branch, set `model_config_id: effectiveSelectedModel ||
undefined` on the `EditChatMessageRequest`.
- On success, persist the chosen model to `lastModelConfigIDStorageKey`
so the next chat from this browser keeps the same default. Mirrors the
new-message branch.

### Generated

- `make site/src/api/typesGenerated.ts` and `make
coderd/apidoc/swagger.json` produce the updated `EditChatMessageRequest`
schema in `typesGenerated.ts`, `coderd/apidoc/{docs.go,swagger.json}`,
and `docs/reference/api/{chats.md,schemas.md}`.

## Tests

- `coderd/x/chatd/chatd_test.go`:
- `TestEditMessageWithModelConfigOverride`: edit with a different model
-> replacement message and `chats.LastModelConfigID` use the new model.
- `TestEditMessagePreservesModelConfigByDefault`: edit without
`ModelConfigID` -> original model preserved.
- `TestEditMessageRejectsUnknownModelConfig`: passes a random UUID ->
`ErrInvalidModelConfigID`, original message still present,
`LastModelConfigID` unchanged (rollback).
- `coderd/exp_chats_test.go` (under `TestPatchChatMessage`):
- `ChangesModel`: end-to-end via SDK; `edited.Message.ModelConfigID` and
`chat.LastModelConfigID` both match the new model.
- `InvalidModelConfigID`: random UUID -> 400 `Invalid model config ID.`.

</details>
2026-05-12 14:51:55 +02:00
Jaayden Halko 8ba24e0e54 feat(site): add collapsible agent sections (#25173)
closes CODAGT-395

<img width="426" height="480" alt="Screenshot 2026-05-12 at 19 04 03"
src="https://github.com/user-attachments/assets/ba336ecf-3e90-48b0-b5d3-b10499909953"
/>

Adds collapsible section headers to the Agents sidebar, including
visible counts and chevrons for pinned and time-grouped chats.

The section header toggle keeps nested chat expansion state independent,
preserves the existing filter dropdown behavior, and adds Storybook
coverage for counts, collapse or expand interactions, and filter menu
behavior.
2026-05-12 13:07:57 +01:00
Danielle Maywood a55430b8fd fix(site/src/pages/AgentsPage): suppress last-message spacer during active stream (#25120) 2026-05-12 12:31:40 +01:00
Cian Johnston caabb3c4ab fix(site): show Organizations in admin dropdown for single-org OSS deployments (#25175)
Fixes https://linear.app/codercom/issue/CODAGT-350

On OSS or no-license single-org deployments, the Organizations admin
link was hidden because `canViewOrganizationSettings` was gated on
`showOrganizations`, which requires either a multi-org entitlement or >1
org. The page was still reachable via direct URL, but the members view
displayed a raw "Template RBAC is a Premium feature. Contact sales!"
error from the groups API.

Two fixes:

1. Always render the Organizations link inside the `DeploymentDropdown`.
The dropdown itself is only shown to users with admin-level permissions,
so Organizations is effectively gated on having admin access.

2. Remove `groupsByUserIdQuery.error` from the error chain on the
members page. The groups endpoint is gated behind
`templateRBACEnabledMW` on enterprise, returning a 403 on OSS. The
groups data is already optional, so the page renders fine without it.

> Generated by Coder Agents
2026-05-12 12:28:43 +01:00
Thomas Kosiewski 5c3b59151e feat: add Cmd/Ctrl+Enter send setting (#25062)
Adds an Agents General setting to require Cmd/Ctrl+Enter before sending
chat messages. When enabled, plain Enter inserts a newline in agent chat
inputs while the send button remains available.

The preference is now persisted server-side through
`/api/v2/users/{user}/preferences`, alongside the existing user
preference settings, and is applied to both the create-agent input and
existing chat composer. Storybook and API coverage verify the setting,
keyboard behavior, validation, and persistence.

<details>
<summary>Coder Agents notes</summary>

Generated by Coder Agents from a Slack request. Dogfooded with
agent-browser against the Storybook settings and chat input stories.

</details>
2026-05-12 10:09:34 +02:00
Yevhenii Shcherbina 592e45dcfb chore: bump coder-guts dependency (#25154)
Bump coder/guts to v1.7.0.
Related PR: https://github.com/coder/guts/pull/81
2026-05-11 19:18:44 -04:00
Michael Suchacz 7a7b196b21 fix(site/src/pages/AgentsPage/components/Sidebar): keep subtitle in sync during streaming lifecycle (#25144)
The Agents sidebar shows the per-chat turn-end summary as a subtitle,
but the cached `last_turn_summary` is not always in sync with the live
status:

- **Resuming a turn**: when a chat goes back to `running` or `pending`,
the sidebar would keep displaying the previous turn-end label (e.g.,
"Chat is idle") while the agent is already streaming again.
- **Stream end → next summary**: status flips to `waiting`
synchronously, but the new `last_turn_summary` is generated by an async
finalizer that calls the model. For a beat, the row still carries the
previous turn's summary text, which would briefly flash before the new
text arrives.

This PR addresses both:

1. Override the subtitle with `{model} streaming…` while `chat.status`
is `running` or `pending`, matching the same `isStreaming` definition
`ChatPageContent` uses. Errors still take precedence so failure context
is not hidden.
2. Capture the cached summary observed during streaming and suppress an
exact match on the post-stream renders until the new summary lands.
State is adjusted during render so the first post-stream paint already
hides the stale string instead of flashing it for a frame. A 10s timeout
safety net releases the suppression in the corner case where the
regenerated summary equals the previous one byte-for-byte.

<details>
<summary>Decision log</summary>

- **Frontend-only fix**: the server intentionally does not bump
`updated_at` when it writes the new `last_turn_summary` (see
`UpdateChatLastTurnSummary` in `coderd/database/queries/chats.sql`), and
SSE delivers the status flip ahead of the new label. The sidebar knows
the live status, so the rendering layer is the right place to mask the
inconsistency.
- **`isStreaming = running || pending`** mirrors `ChatPageContent.tsx`
so subagents and queued continuations also flip to the live label.
- **State, not refs**, for the suppression bookkeeping: the React
Compiler rejects ref writes during render, and React's "adjust state
during render" pattern is purpose-built for storing information from
previous renders (state setters in render bail out and re-render
synchronously before paint).
- **10s timeout** is a safety net for the rare byte-for-byte equality
case; the equality-based release handles every other shape of update.
- Added Storybook stories covering both fixes
(`ChatStreamingOverridesTurnSummary` and
`StaleTurnSummaryAfterStreamingIsSuppressed`). All existing legacy
subtitle stories still pass because they use terminal statuses.

</details>

> This PR was created by Coder Agents on behalf of @ibetitsmike.
2026-05-12 00:51:22 +02:00
Sushant P 81561454d6 revert: "fix(site): enlarge checkbox click target in workspace and task tables" (#25159)
Reverts coder/coder#24739
2026-05-11 13:59:04 -07:00
Thomas Kosiewski e56381eb61 feat: stream advisor tool output (#25032)
Stream advisor output into the advisor tool card while the nested
advisor call is still running.

This keeps the advisor implementation intentionally advisor-specific:
the parent model still receives the same final structured tool result,
while the frontend receives transient `tool-result.result_delta` parts
to render partial advisor text in the expanded card. The final persisted
chat history remains unchanged.

Refs CODAGT-322.

Generated by Coder Agents.

<details>
<summary>Implementation plan</summary>

- Publish advisor text deltas from the nested `chatloop.Run` via
`RunAdvisorOptions.OnAdviceDelta`.
- Forward those deltas through `chatadvisor.Tool` with the parent
advisor tool call ID.
- Emit transient `ChatMessagePartTypeToolResult` websocket parts with
`ResultDelta` from `chatd`.
- Add `result_delta` to the generated tool-result TypeScript variant.
- Accumulate tool result deltas in frontend stream state and keep the
tool running until the final result arrives.
- Render streamed advisor advice in the existing advisor card using
streaming markdown mode, while retaining the updated advisor UI.

</details>
2026-05-11 20:18:49 +02:00
Steven Masley 19573e8aee feat!: patchTemplateMeta to use optional fields (#24984)
Closes https://github.com/coder/coder/issues/13112

**Breaking Change**: Removed status code `StatusNotModified` when no
diffs occur in a patch. Now the patch is always applied and a template
is always returned.
2026-05-11 12:43:52 -05:00
TJ c2dfaa406a fix(site): enlarge checkbox click target in workspace and task tables (#24739)
Fixes a UX issue where clicking near (but not exactly on) the
bulk-action
checkbox in the Workspaces or Tasks table would navigate to the
workspace/task page instead of toggling the checkbox. Clicking back then
clears all previous selections.

## Changes

Wraps each row checkbox in a `div` that:
- Calls `e.stopPropagation()` on `click` and `keydown` so near-miss
  clicks toggle the checkbox instead of navigating.
- Uses `h-[72px]` to fill the full row height for vertical coverage.
- Uses `pr-4 -mr-4` to extend the safe zone to the right without
  shifting layout.
- Sets `cursor-default` so the pointer hand does not appear in the
  safe zone.

Applied to both:
- `WorkspacesTable.tsx` (workspaces page)
- `TasksTable.tsx` (tasks page)

> This PR was authored by Coder Agents.
2026-05-11 16:37:00 +00:00
Jeremy Ruppel a1dbd758bc feat: add template builder deployment config and telemetry types (#25082) 2026-05-11 09:48:55 -04:00
Marcin Tojek febabfb8b2 feat: add request/response dump support to aibridgeproxyd (#24837)
Closes https://github.com/coder/coder/issues/24335
2026-05-11 10:59:26 +02:00
Kyle Carberry aaa0dacdb3 fix: infer workspace claim time from build history for /agents delete dialog (#25057)
Closes
[CODAGT-317](https://linear.app/codercom/issue/CODAGT-317/pr-workspaces-sometimes-require-name-confirmation-to-delete).

## Problem

The `/agents` archive-and-delete molly-guard (typing the workspace name)
was firing for chats that had clearly created their own workspace. The
heuristic in `resolveArchiveAndDeleteAction` decides whether
confirmation is needed by comparing the workspace's `created_at` against
the chat's `created_at`:

```ts
return new Date(workspaceCreatedAt) >= new Date(chatCreatedAt);
```

That assumption breaks for **prebuilt workspaces**.
`ClaimPrebuiltWorkspace` rewrites `owner_id`, `name`, `updated_at`,
`last_used_at`, etc., but **never touches `created_at`**, which still
reflects when the prebuild was provisioned by the reconciler, often
hours before the chat exists. Result: every prebuild-claimed workspace
looks pre-existing, so the molly-guard fires.

Concrete example from a real chat:

| Field | Value |
|---|---|
| `chat.created_at` | `2026-05-07T15:12:23Z` |
| `workspace.created_at` (provision) | `2026-05-07T14:22:24Z` |
| `latest_build.created_at` (claim) | `2026-05-07T15:19:09Z` |

`14:22:24 < 15:12:23` so `isWorkspaceAutoCreated` returned false even
though the chat issued the claim.

## Fix (frontend-only)

Derive the moment a workspace was acquired from existing build history
rather than relying on `workspace.created_at`:

- Build #1 initiator = prebuilds system user → workspace was a prebuild
→ use `build_2.created_at` (the claim build) as the acquisition time.
- Build #1 initiator = real user → workspace was created from scratch →
use `workspace.created_at` (unchanged behavior).
- Unclaimed prebuild or no build history → return `null` (force
confirmation; safe degradation for a destructive flow).

The resolver fetches the build list via the existing
`getWorkspaceBuilds` endpoint when the dialog might fire. No new column,
no migration, no schema change. Works retroactively for all existing
claimed prebuilds; no backfill needed.

The prebuilds system user UUID is exposed via
`codersdk.PrebuildsSystemUserID` and typegen'd to `typesGenerated.ts`.
`coderd/database.PrebuildsSystemUserID` parses that constant via
`uuid.MustParse` so the two cannot drift; if the codersdk literal ever
changes, package init fails fast.

## History

The first draft of this PR added a `workspaces.claimed_at` column
populated by `ClaimPrebuiltWorkspace`. After review feedback from
@johnstcn pointing out that the same fact is already implicit in build
history, I pivoted to the frontend-only approach. Subsequent review
notes consolidated the prebuilds system user UUID into a single
typegen'd constant.

## Why not the other open PRs

- **#25055** (`chatKey` cache fallback) only fixes a different
cache-miss path; it explicitly notes it does not address `created_at <
chat.created_at`.
- **#25053** (`chats.workspace_auto_created` boolean) puts the truth on
the wrong side of the schema: "this workspace was claimed at time T" is
a property of the workspace, not the chat. The MCP plumbing it adds is
also unnecessary now that the same answer is available from build
history.

## Test plan

- `pnpm vitest run --project=unit
src/pages/AgentsPage/utils/agentWorkspaceUtils.test.ts` — 40/40 pass;
new cases cover prebuild claim before/after chat, unclaimed prebuild,
missing-build-history fallback, and the fetch-skip when the chat is not
in cache.
- `pnpm lint:types`, `pnpm check`, `make pre-commit`.

<details>
<summary>Disclosure</summary>

Opened on behalf of @kylecarbs by [Coder
Agents](https://coder.com/coder-agents).
</details>
2026-05-10 11:04:55 -04:00
Yevhenii Shcherbina 4124d1137d feat: add ai_model_prices table (#24932)
# Summary

Implements
https://linear.app/codercom/issue/AIGOV-282/add-ai-model-price-table-and-seed-generator

This PR lays the groundwork for AI Bridge cost controls (per the AI
Governance RFC). It adds the foundation needed for future cost tracking:
a place to store per-model token prices, a way to keep those prices in
sync with upstream pricing data, and a startup mechanism that ensures
every deployment has prices loaded before AI Bridge starts processing
requests.

The price data comes from [models.dev](https://models.dev/), a
community-maintained catalogue of AI provider pricing. A generator
script fetches the latest prices, filters to Anthropic and OpenAI for
now, and produces a seed file checked into the repository.

On every server startup the seed is applied to the database, so new
releases automatically pick up any price corrections that landed since
the previous one. Existing rows are overwritten with the latest prices;
rows for models no longer in the seed are left untouched.

# Batching the AI model price seed: three approaches

Context: at server startup we seed the `ai_model_prices` table from an
embedded JSON price book (~70 rows today, will grow as we add providers,
potentially 4000+).

Each row is:

```text
(provider, model, input_price, output_price, cache_read_price, cache_write_price)
```

Any of the four price columns can be:

- `NULL` → “price unknown for this dimension”
- explicit `0` → “free”

The batch must be an UPSERT so re-running is idempotent and existing
rows pick up new prices.

We considered three implementations.

---

## Approach 1 — Per-row UPSERT in a Go loop

```go
for _, row := range rows {
    if err := db.UpsertAIModelPrice(ctx, database.UpsertAIModelPriceParams{
        Provider:   row.Provider,
        Model:      row.Model,
        InputPrice: nullInt64(row.InputPrice),
        // ...
    }); err != nil {
        return err
    }
}
```

### Pros

- Trivial.
- NULL handling falls out naturally from `sql.NullInt64`.

### Cons

- `N` round-trips per seed.
- With ~70 rows that means ~70 statement executions on every startup,
even inside a transaction.
- Doesn't scale gracefully as the price book grows, potentially 4000+.

---

## Approach 2 — `UNNEST` with parallel arrays

Pass each column as a separate Go slice. Postgres unnests them in
parallel into a virtual table, then `INSERT ... SELECT`.

```sql
INSERT INTO ai_model_prices (
    provider,
    model,
    input_price,
    output_price,
    cache_read_price,
    cache_write_price
)
SELECT
    UNNEST(@providers::text[]),
    UNNEST(@models::text[]),
    NULLIF(UNNEST(@input_prices::bigint[]), -1),
    NULLIF(UNNEST(@output_prices::bigint[]), -1),
    NULLIF(UNNEST(@cache_read_prices::bigint[]), -1),
    NULLIF(UNNEST(@cache_write_prices::bigint[]), -1)
ON CONFLICT (provider, model) DO UPDATE SET
    input_price       = EXCLUDED.input_price,
    output_price      = EXCLUDED.output_price,
    cache_read_price  = EXCLUDED.cache_read_price,
    cache_write_price = EXCLUDED.cache_write_price,
    updated_at        = NOW();
```

Go side: flatten rows into six parallel slices.

Use a sentinel (`-1`) for “missing”, since `lib/pq` can't encode `NULL`
into a `bigint[]` element.

```go
providers := make([]string, len(rows))
models    := make([]string, len(rows))
inputs    := make([]int64,  len(rows))
outputs   := make([]int64,  len(rows))
cacheR    := make([]int64,  len(rows))
cacheW    := make([]int64,  len(rows))

for i, r := range rows {
    providers[i] = r.Provider
    models[i]    = r.Model

    inputs[i] = -1
    if r.InputPrice != nil {
        inputs[i] = *r.InputPrice
    }

    outputs[i] = -1
    if r.OutputPrice != nil {
        outputs[i] = *r.OutputPrice
    }

    cacheR[i] = -1
    if r.CacheReadPrice != nil {
        cacheR[i] = *r.CacheReadPrice
    }

    cacheW[i] = -1
    if r.CacheWritePrice != nil {
        cacheW[i] = *r.CacheWritePrice
    }
}

return db.UpsertAIModelPrices(ctx, database.UpsertAIModelPricesParams{
    Providers:        providers,
    Models:           models,
    InputPrices:      inputs,
    OutputPrices:     outputs,
    CacheReadPrices:  cacheR,
    CacheWritePrices: cacheW,
})
```

### Pros

- Single round-trip.

### Cons

- The generated `sqlc` params become plain `[]int64`, which can't
represent `NULL`.

---

## Approach 3 — `jsonb_array_elements` over a single `@seed::jsonb`
(chosen)

Pass the raw seed JSON as one parameter; let Postgres expand and parse
it.

```sql
INSERT INTO ai_model_prices (
    provider,
    model,
    input_price,
    output_price,
    cache_read_price,
    cache_write_price
)
SELECT
    elem->>'provider',
    elem->>'model',
    (elem->>'input_price')::bigint,
    (elem->>'output_price')::bigint,
    (elem->>'cache_read_price')::bigint,
    (elem->>'cache_write_price')::bigint
FROM jsonb_array_elements(@seed::jsonb) AS elem
ON CONFLICT (provider, model) DO UPDATE SET
    input_price       = EXCLUDED.input_price,
    output_price      = EXCLUDED.output_price,
    cache_read_price  = EXCLUDED.cache_read_price,
    cache_write_price = EXCLUDED.cache_write_price,
    updated_at        = NOW();
```

Go side reduces to:

```go
return db.UpsertAIModelPrices(ctx, seedJSON)
```

### Pros

- Single round-trip.
- NULLs fall out naturally:
  - `(elem->>'cache_write_price')::bigint` becomes `NULL`
  - no sentinels
- The seed is already JSON:
- Existing precedent:
  - `jsonb_array_elements` is already used elsewhere in the codebase

### Cons

- Less type-safe at the SQL boundary than `UNNEST`
- Slightly less standard than `UNNEST`
- Readers need familiarity with:
  - `jsonb_array_elements`
  - `->>` extraction syntax
- Postgres pays JSON parse cost
  - negligible at our scale

---

---

# Decision

We picked Approach 3.

It collapses the round-trips like `UNNEST` does, but without:

- nullable-array workarounds
- sentinel values
2026-05-08 16:45:14 -04:00
Kayla はな 638e2220e9 chore: refactor BuildIcon and remove useClassName (#25017) 2026-05-08 14:11:49 -06:00
Jeremy Ruppel a638f099c8 fix(site): show running script count instead of log source count in agent log badge (#25079)
The badge next to the loading spinner in the agent logs section was
showing `agent.log_sources.length` (total log sources registered on the
agent). This is a static count unrelated to what's actively running.

Now it shows the count of startup scripts still in progress: scripts
where `run_on_start` is true and `status` is not yet set. Scripts
without a `status` haven't completed; completed scripts receive `"ok"`,
`"exit_failure"`, `"timed_out"`, or `"pipes_left_open"`. The badge also
hides when zero scripts are running.

> [!NOTE]
> Generated by Coder Agents
2026-05-08 09:37:03 -04:00
Ethan 987d415be3 feat(site): show workspace quota failures in chats (#25020)
Create and start workspace tool cards now recognize `INSUFFICIENT_QUOTA`
results and use the server-provided quota failure title in the build-log
dropdown. The existing warning icon and tooltip remain, while the
assistant response remains the place for the detailed recovery guidance.

Adds Storybook coverage for quota-reached create and start workspace
results.

https://github.com/coder/coder/pull/24956 added the necessary backend
changes.
<img width="897" height="505" alt="image"
src="https://github.com/user-attachments/assets/6cab8798-393d-429f-a3c3-a8ed50402d42"
/>



Closes CODAGT-20
2026-05-08 13:25:53 +10:00
Danielle Maywood e7958713a9 feat: add code diff display mode preference (#25027) 2026-05-07 20:15:28 +01:00
Michael Suchacz d32842f084 feat(site): cycle prompt history with up/down arrows (#25004)
Fixes
[CODAGT-319](https://linear.app/codercom/issue/CODAGT-319/support-prompt-history-cycling-with-up-arrow).

Pressing the up-arrow key in the agent chat composer now cycles through
prior user prompts in the chat (terminal/Discord/iTerm2 style).
Down-arrow steps forward, Escape exits cycling and restores the
in-progress draft. Cycling is non-destructive: the per-message hover
**Edit** button is still the destructive truncate-and-edit path.

Replaces the previous up-arrow shortcut that immediately entered
destructive history-edit mode (and which had a regression where the
composer rendered as "editing" with an empty input box).

## Behaviour

- **Up** when composer is empty: snapshot the (empty) draft and load the
most recent user prompt; subsequent **Up** presses load older prompts.
Clamp at oldest, no wrap.
- **Up** while non-empty and not yet cycling: pass through (caret
movement preserved).
- Once cycling, **Up / Down** are intercepted unconditionally because
the cycle text fully replaces editor contents. Exit explicitly via
Escape, by sending, or by typing.
- **Down** while cycling: load the next-newer prompt, or restore the
saved draft when past newest.
- **Escape** while cycling: exit cycle and restore the saved draft. This
also applies during streaming; the same keypress is stopped before it
reaches the interrupt handler, and a second Escape interrupts as before.
- **Typing / paste / drop / attach / send / `remountKey` change**: exit
cycle mode and clear the snapshot.
- Cycling is suppressed while `isEditingHistoryMessage`,
`editingQueuedMessageID !== null`, or the input is `disabled` /
`isLoading`.
- Empty `userPromptHistory` makes Up a no-op (no destructive fallback).

## Out of scope (filed as follow-ups if needed)

- Restoring file-reference chips / attachments on cycled messages — v1
cycles plain text only, matching the existing per-message destructive
Edit's `text` payload.
- `^N` / `^P` keybindings (per Cian's note in the Linear thread).
- Per-user "enable/disable history cycling" preference (per Rowan's
note).
- Cross-chat history; cycling is per-chat.

## Tests

New Storybook play functions in `AgentChatInput.stories.tsx`:

- `PromptHistoryCycling` — Up cycles older, clamps at oldest; Down
returns to newer / draft; Escape restores draft.
- `PromptHistoryCyclingExitsOnTyping` — typing exits cycle mode;
subsequent Up snapshots the fresh empty draft and Down restores it.
- `NoPromptHistoryUpArrowIsNoOp` — empty history → Up is a no-op.
- `PromptHistorySuppressedWhileEditingHistoryMessage` — cycling does not
engage while history-editing.
- `PromptHistorySuppressedWhileDisabled` — cycling does not engage while
disabled.
- `PromptHistorySuppressedWhileLoading` — cycling does not engage while
loading.

## Implementation notes

Also rewrites `useImperativeHandle` to delegate to `internalRef.current`
lazily on every call instead of capturing it eagerly at factory time.
The old code crashed when methods were called after a remount because
the captured ref was stale; the new wrapper sees the current Lexical
instance. Behavior changes from throw-on-null to silent no-op, which
matches every other consumer of `ChatMessageInputRef`.

Verified locally:

```
pnpm format
pnpm check
pnpm test:storybook src/pages/AgentsPage/components/AgentChatInput.stories.tsx         # 41 passed
pnpm lint
```

## Manual UAT

A 13-case manual UAT covering cycle entry/exit, clamping, draft
restoration, no-history no-op, suppression while editing a history
message, and the send-button enable state — all PASS. Spec lives at the
deleted artifact branch; happy to re-attach if reviewers want it.

<details>
<summary>Implementation plan and decision log</summary>

The complete plan that drove this PR, including design alternatives
considered and edge cases:

```md
# CODAGT-319 — Up-arrow prompt history cycling

## Goal
Pressing the up-arrow key in the agent chat composer should cycle through the
user's previously-sent prompts in the current chat, terminal/Discord/iTerm2
style. Down-arrow steps forward; Escape exits cycle mode and restores the
in-progress draft. Cycling is non-destructive — it only populates the
composer with text the user can choose to resend, edit, or discard.

## Today's behaviour (and the regression)
- `ChatMessageInput` is a Lexical-based plain-text editor used inside `AgentChatInput.tsx`.
- `AgentChatInput.tsx` already wires an `ArrowUp` handler. When the composer is empty and not already editing, it calls `onEditLastUserMessage`.
- `onEditLastUserMessage` puts the user into a destructive "edit history" mode that warns "Editing will delete all subsequent messages and restart the conversation here.".
- Danielle's regression report ("shows me as editing but the input box is empty") indicates the destructive flow has a bug in addition to being the wrong UX for the request. We're replacing that path on the up-arrow, not patching it. The destructive edit remains accessible via the per-message hover Edit button.

## Design

### Behaviour
- Up when composer is empty: snapshot the (empty) draft and load the most recent user prompt. Subsequent Up loads older prompts, clamping at oldest. No wrap.
- Up while non-empty and not yet cycling: pass through. Matches existing gating.
- Once cycling, Up/Down are intercepted unconditionally. Exit via Escape, send, or typing.
- Down while cycling: next-newer or restore draft past newest.
- Escape while cycling: restore draft. During streaming, stop propagation so the same keypress does not interrupt; a second Escape interrupts as before.
- Typing/paste/drop/attach/send: exit cycle.
- Suppressed while isEditingHistoryMessage, editingQueuedMessageID !== null, or disabled/isLoading.
- No history => Up is a no-op.

### State
Local to `AgentChatInput.tsx`:
- `cycleIndex: number | null` — null means not cycling. 0 = newest user prompt.
- `cycleSavedDraft: string | null` — text restored on dismiss.

No localStorage persistence — refresh is a clean exit signal and the chat already has history server-side.

### Wiring
- New prop `userPromptHistory: readonly string[]` on `AgentChatInput`, newest-first.
- Removed `onEditLastUserMessage` prop entirely (its single call-site is being replaced). Removed dead `onEditUserMessage` prop on `ChatPageInput` (no longer needed since the destructive last-message shortcut is gone; the destructive Edit button uses a separate prop chain through `ChatPageTimeline`).
- `ChatPageContent.tsx` derives `userPromptHistory` from existing message store, filtered to `role === "user"` with non-empty `getEditableUserMessagePayload(message).text.trim()`.

### Reset triggers
`cycleIndex` and `cycleSavedDraft` reset on:
1. New `remountKey` (chat change, edit start/cancel).
2. Successful send.
3. Paste, drop, file attach.
4. User typing (detected via `handleContentChange` by comparing the incoming content to `currentCycleValueRef`, the last value applied programmatically).

### Out of scope
- Chip/attachment cycling.
- ^N/^P (Cian's note).
- Per-user toggle (Rowan's note).
- Cross-chat history.
```

</details>

---
> [!NOTE]
> This PR was created on behalf of @ibetitsmike by Coder Agents.

---------

Co-authored-by: Coder Agents <noreply@coder.com>
2026-05-07 20:31:41 +02:00
Kayla はな 9fd2cc78fe refactor(site): migrate more styles from emotion to tailwind (#24914) 2026-05-07 11:09:25 -06:00
Dean Sheather e1b1c7ec5b feat: resize chat image attachments client-side for provider budgets (#24533)
Anthropic rejects inline images over 5,242,880 bytes, but our upload
endpoint accepts images up to 10 MiB — so 5–10 MiB images were
reaching the provider and failing. This adds two layers of
protection: the browser resizes oversized images before upload, and
the server rejects any that still slip through before an upstream
request is issued.

Client-side resizing uses `createImageBitmap` with
`resizeWidth`/`resizeHeight` to clamp the decoded bitmap at decode
time, then iteratively shrinks on an `OffscreenCanvas` (falling back
to `HTMLCanvasElement`) until the output fits the applicable budget.
Anthropic (and Bedrock-hosted Claude — fantasy's bedrock provider is
a thin wrapper around the Anthropic client) uses a ~5 MiB budget;
other providers use a ~10 MiB budget to stay under the server cap.
Doing the resize in the browser avoids decoding attacker-controlled
image bytes in `coderd` (image-bomb DoS surface).

Server-side, `chatFileResolver` now takes a provider string and
looks up the inline-image cap via a new
`chatprovider.InlineImageByteCap`
helper; oversized `image/*` files for capped providers are rejected
with a pre-classified `chaterror` before the SDK call. The backstop
fires for older clients, direct API callers, or any image that was
committed to the composer before the user switched to a stricter
provider.

Attachments commit to composer state synchronously with a new
`"processing"` `UploadState` so paste+Enter can't dispatch before
the resize finishes; the `"uploading"` send gate now covers both
states. Dismissed-while-resizing attachments are tracked in a
`WeakSet` so a late swap can't resurrect a removed file.

Closes CODAGT-215
2026-05-08 02:07:33 +10:00
Thomas Kosiewski 273e828442 fix: remove advisor reasoning configuration (#25030) 2026-05-07 15:19:19 +02:00
Bartek Gatz 20453e123e fix(site): clamp lander position on landing to keep feet on pad (#25026) 2026-05-07 11:36:15 +01:00
Jaayden Halko 9ec2df9574 feat(site/src/pages/AgentsPage): resize agents sidebar (#24963)
Adds a persisted, draggable left sidebar width for the agents page. The
resize handle uses the same pointer-capture resize technique as the
existing right panel and clamps the expanded sidebar between 240px and
`min(520px, 50vw)`.

Updates the agents page skeleton to read the same stored sidebar width
and adds Storybook interaction coverage for resize clamping and
persistence.
2026-05-07 10:21:53 +01:00
Thomas Kosiewski 10a22a4753 fix(site/src/pages/AgentsPage): polish advisor UI (#25002)
Polishes the advisor tool card so the header uses a clearer lightbulb icon, inline pill metadata, wrapped/clamped question text, and a separate advice pill before the rendered response.

Adds Storybook coverage for a long advice plus 1.8k-character question peak state, including collapse/expand behavior.

Refs https://linear.app/codercom/issue/CODAGT-322/improve-advisor-icon-and-duplicate-loading-ui

<details>
<summary>Coder Agents disclosure</summary>

This PR was generated by Coder Agents.

</details>
2026-05-07 11:06:14 +02:00
Ethan ef0151601e feat: report insufficient quota build failures in chat tools (#24956)
## Summary

When a workspace build fails because the user is over their group quota,
the chat tools currently surface the failure as a bare `"workspace build
failed: insufficient quota"` string with no machine-readable error code
and no visibility into the user's current usage. Agents and the UI
cannot distinguish quota failures from any other Terraform error, so
users see an opaque message and have no clear path to recovery.

This PR tags quota failures with a typed error code at the source and
propagates it through the chat tool layer so callers can react to it
explicitly.

Relates to CODAGT-20

## Changes

**Provisioner runner**

- Add `InsufficientQuotaErrorCode = "INSUFFICIENT_QUOTA"` and set it
explicitly at the `commitQuota` failure site via a new
`failedWorkspaceBuildfCode` helper, so `provisioner_jobs.error_code` is
populated only on the genuine quota path. The substring matcher used for
externally produced sentinels (e.g. `"missing parameter"`, `"required
template variables"`) is intentionally not extended; provider errors
that happen to mention "insufficient quota" stay classified as generic
build failures.

**SDK and API contract**

- Add `JobErrorCodeInsufficientQuota` and a
`JobIsInsufficientQuotaErrorCode` helper to `codersdk`.
- Extend the swagger `enums` tag on `ProvisionerJob.ErrorCode` to
include `INSUFFICIENT_QUOTA`.
- Regenerate `coderd/apidoc`, `docs/reference/api/*`, and
`site/src/api/typesGenerated.ts`.

**chattool create_workspace / start_workspace**

- `waitForBuild` now returns a typed `*workspaceBuildError` carrying
both the message and the `JobErrorCode`, instead of a bare error string.
- New `quotaerror.go` introduces a structured `quotaErrorResult` (with
`error_code`, `title`, `message`, `build_id`, and optional `quota`) and
a best-effort `workspaceQuotaDetails` lookup that wraps owner
authorization internally and fetches `credits_consumed` and `budget`
from the database. Quota lookup failures (including authorization
failures) never block the failure payload.
- On quota-coded build failures, both `create_workspace` and
`start_workspace` now return the structured response (with the recovery
guidance inlined into `message`) instead of the bare `"insufficient
quota"` string. This applies to all three failure paths: post-creation,
an in-progress existing build, and a freshly triggered start build.
Non-quota build failures continue to use the existing
`buildToolResponse` / `newBuildError` path.
- Owner authorization is wrapped only on the call sites that need it
(the `CreateFn` and `StartFn` invocations and the quota-detail lookup),
so idempotent fast paths (already running, already in progress,
existing-workspace early returns) do not pay for an extra RBAC
round-trip or fail when role lookup is transient.

## Out of scope

- No changes to quota math, allowances, or bypass behavior.
- No automatic retries.
- No new quota-inspection tools and no changes to MCP
`coder_create_workspace` (which returns immediately and never observed
the build outcome here).
- No frontend UI changes; those will land in a follow-up PR that
consumes the new `INSUFFICIENT_QUOTA` code.
2026-05-07 15:01:58 +10:00
TJ 6737e2588e fix(site): reduce agents beta badge size from sm to xs (#25011)
Reduces the beta badge size in the Coder Agents UI from `sm` to `xs` for
better visual balance with the logo.

## Changes

- Added `xs` size variant to `FeatureStageBadge`, styled to match the
existing `Badge` component's `xs` variant (`text-2xs`, `h-[18px]`,
`border-0`, `rounded`)
- Updated both usages in `AgentPageHeader` (mobile) and `AgentsSidebar`
(desktop) from `size="sm"` to `size="xs"`
- Added `ExtraSmallBeta` Storybook story for the new size

> Generated by Coder Agents
2026-05-06 18:25:41 -07:00
Kayla はな 9d1315ffba refactor(site): align user settings layout with organization settings (#25016) 2026-05-06 17:29:01 -06:00
Jake Howell 8c2b1c7d69 chore: de-emotion style constant (#24835)
This pull-request looks at all (most) of our instances of `const styles
= { ... }` and attempts to smooth them down into the minimum viable
Tailwind equivalent 🙂
2026-05-07 09:21:24 +10:00
Kayla はな d19e5f86a7 fix(site): use ExternalImage on template insights (#25010) 2026-05-06 17:16:28 -06:00
Danielle Maywood 8ac4b9ab45 refactor: remove unnecessary typeof window checks (#24999) 2026-05-06 22:39:59 +01:00
Kayla はな f5ad6fb4cb fix(site): improve info icon styles in audit and connection log rows (#25009) 2026-05-06 13:27:31 -06:00
Kayla はな 4e1dccaabd refactor: remove ChooseOne component (#24983) 2026-05-06 12:08:51 -06:00
Bartek Gatz 894a1e0f7f feat(site): add Codernauts lunar lander game (#25001) 2026-05-06 17:50:46 +00:00