feat: add file/image attachment support to chat input (#22604)

This change adds support for image attachments to chat via add button
and clipboard paste. Files are stored in a new `chat_files` table and
referenced by ID in message content. File data is resolved from storage
at LLM dispatch time, keeping the message content column small.

Upload validates MIME types via content type or content sniffing against
an allowlist (png, jpeg, gif, webp). The retrieval endpoint serves files
with immutable caching headers. On the frontend, uploads start eagerly
on attach with a background fetch to pre-warm the browser HTTP cache so
the timeline renders instantly after send.
This commit is contained in:
Mathias Fredriksson
2026-03-06 21:05:26 +02:00
committed by GitHub
parent 30a736c49e
commit a104d608a3
46 changed files with 3090 additions and 163 deletions
+12
View File
@@ -132,6 +132,18 @@ are queued and delivered when the agent completes its current step, so there is
no need to wait for a response before providing additional context or changing
direction.
### Image attachments
Users can attach images to chat messages by pasting from the clipboard, dragging
files into the input area, or using the attachment button. Supported formats are
PNG, JPEG, GIF, and WebP up to 10 MB per file. Images are sent to the model as
multimodal content alongside the text prompt.
This is useful for sharing screenshots of errors, UI mockups, terminal output,
or other visual context that helps the agent understand the task. Messages can
contain images alone or combined with text. Image attachments require a model
that supports vision input.
## Security benefits of the control plane architecture
Running the agent loop in the control plane rather than inside the developer
+78
View File
@@ -1,5 +1,83 @@
# Chats
## Upload a chat file
### Code samples
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/chats/files?organization=497f6eca-6276-4993-bfeb-53cbbbba6f08 \
-H 'Accept: application/json' \
-H 'Content-Type: string' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /chats/files`
### Parameters
| Name | In | Type | Required | Description |
|----------------|--------|--------------|----------|-----------------------------------------------------------------------------------|
| `Content-Type` | header | string | true | Content-Type must be an image type (image/png, image/jpeg, image/gif, image/webp) |
| `organization` | query | string(uuid) | true | Organization ID |
### Example responses
> 201 Response
```json
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
}
```
### Responses
| Status | Meaning | Description | Schema |
|--------|----------------------------------------------------------------------------|--------------------------|------------------------------------------------------------------------------|
| 201 | [Created](https://tools.ietf.org/html/rfc7231#section-6.3.2) | Created | [codersdk.UploadChatFileResponse](schemas.md#codersdkuploadchatfileresponse) |
| 400 | [Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1) | Bad Request | [codersdk.Response](schemas.md#codersdkresponse) |
| 401 | [Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1) | Unauthorized | [codersdk.Response](schemas.md#codersdkresponse) |
| 413 | [Payload Too Large](https://tools.ietf.org/html/rfc7231#section-6.5.11) | Request Entity Too Large | [codersdk.Response](schemas.md#codersdkresponse) |
| 500 | [Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1) | Internal Server Error | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get a chat file
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/chats/files/{file} \
-H 'Accept: */*' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /chats/files/{file}`
### Parameters
| Name | In | Type | Required | Description |
|--------|------|--------------|----------|-------------|
| `file` | path | string(uuid) | true | File ID |
### Example responses
> 400 Response
### Responses
| Status | Meaning | Description | Schema |
|--------|----------------------------------------------------------------------------|-----------------------|--------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
| 400 | [Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1) | Bad Request | [codersdk.Response](schemas.md#codersdkresponse) |
| 401 | [Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1) | Unauthorized | [codersdk.Response](schemas.md#codersdkresponse) |
| 404 | [Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4) | Not Found | [codersdk.Response](schemas.md#codersdkresponse) |
| 500 | [Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1) | Internal Server Error | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Archive a chat
### Code samples
+14
View File
@@ -9847,6 +9847,20 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|----------|---------|----------|--------------|-------------|
| `ttl_ms` | integer | false | | |
## codersdk.UploadChatFileResponse
```json
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|------|--------|----------|--------------|-------------|
| `id` | string | false | | |
## codersdk.UploadResponse
```json