Files
coder/coderd/workspacestats
Michael Suchacz 5dde49dffb feat: add chat workspace file upload endpoint (#27079)
## What

Adds `POST /api/v2/chats/{chat}/workspace-files`, which streams the
request body to the chat's bound workspace agent and returns the final
path/name/size, plus the `workspace-file-reference` message part type
that carries the uploaded file's metadata through chat messages:

- `coderd/exp_chats.go`: owner-only middleware (chat RBAC update +
reject archived), workspace exec authorization (403/409 like the
existing chat exec paths), agent selection (prefer the chat's bound
agent, else deterministic `agentselect.FindChatAgent`, without
persisting a binding), 30s dial timeout, chat/ancestor tracking headers
on the agent connection, and agent error passthrough (agent HTTP status
preserved, 502 with a generic detail for transport failures; the real
error is logged). Deleted and non-running workspaces get a specific 409
("Workspace is stopped. Start the workspace before uploading files.")
before agent lookup. Each failed upload logs one structured warning, and
`coderd_chat_workspace_upload_total{status}` counts outcomes. The route
shares the files rate limit and documents its 429.
- Input validation: client-supplied `workspace-file-reference` parts
must point inside `/.coder/chats/<chat-id>/files/` with a matching
basename, non-negative size, and a `workspace_file_workspace_id` equal
to the chat's currently bound workspace (the upload response echoes it
as `workspace_id`). References uploaded before a workspace rebind are
rejected instead of silently sending the agent an unreadable path. The
file name must already be in sanitized form
(`SanitizeWorkspaceUploadName(name) == name`), and the cleaned path and
base media type are what gets persisted; message edits share the same
validation. Parts are rejected on chat creation (no chat ID exists yet).
- `coderd/x/chatd/chatprompt`: references render into the prompt as text
only, `[workspace file: <name> (<size>) at <path>]`; file bytes never
reach the LLM. Title derivation (`TitleText`) uses the bare file name,
so fallback titles carry no internal marker.
- `codersdk`: part-type constants/fields,
`ChatMessageWorkspaceFileReference` builder, and
`Client.UploadChatWorkspaceFile` (RFC 5987 filename in
`Content-Disposition`).

No DB migration: parts persist as JSON in `chat_messages.content`.

## Why

Bridges chat and the workspace filesystem for CODAGT-211: users drop a
file in chat, the bytes land where the agent's tools can operate on
them, and the model learns the path from the reference part.

## How tested

- `go test ./coderd -run 'WorkspaceFile|ChatInput|UploadChat'` including
an integration test that runs a real in-memory agent, uploads through
the coderd route, verifies bytes on disk, and exercises collision
suffixes.
- `go test ./codersdk ./coderd/x/chatd/...`, `make gen`
(swagger/types/docs), `make lint`.
- Rebased onto main 2026-09-21: the chat API moved from
`/api/experimental` to `/api/v2`, so the route is registered in
`coderd/chat_routes.go` and the SDK method lives on `codersdk.Client`.

## Follow-ups

- The files rate limit counts requests, not bytes or concurrency;
whether this route should use a per-user concurrency cap instead, and
whether the new-chat page should keep partial successes on a 429, is
still open
([CODAGT-1171](https://linear.app/codercom/issue/CODAGT-1171)).

Part of CODAGT-211. Stack: agent API -> coderd endpoint (this PR) -> UI.

> This PR was authored by Mux (AI) working on Mike's behalf.
2026-09-26 21:41:25 +02:00
..