mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
## What Adds a dedicated workspace-agent endpoint, `POST /api/v0/upload-chat-file`, that streams a request body into the chat's upload directory `~/.coder/chats/<chat-id>/files/<name>`, plus the SDK plumbing to call it: - `coderd/x/chatfiles`: upload-name sanitization (`SanitizeWorkspaceUploadName`), collision suffixes (compound extensions such as `.tar.gz` stay intact: `archive_2.tar.gz`), and the shared `~/.coder/chats/<id>/files` path layout. - `agent/agentfiles`: the HTTP handler. On Linux/macOS the directory chain is opened with `openat(O_NOFOLLOW)`, so symlinked path components are rejected. The body streams into a hidden `.<name>.upload-<random>` temp file and is published under its final name only when complete, via `linkat` over the `_2`, `_3`, ... collision sequence, so existing files are never overwritten and a half-written file is never visible under its final name. Filesystems without hard links fall back to an exclusive placeholder plus `renameat`. Other platforms use an afero-based fallback with the same semantics. Stale temp files older than an hour are swept before each upload, and failures are logged with a structured warning. - `codersdk/workspacesdk`: `AgentConn.UploadChatFile` + regenerated `agentconnmock`. ## Why Coder Agents chat can attach images/text/PDF to prompts, but arbitrary files (zips, datasets, binaries) have nowhere to land where the agent's tools can act on them. This is the agent-side foundation; the coderd route that fronts it is stacked on top. Design notes: - There is deliberately no size cap; the resource profile matches existing SSH/WebSocket proxying, and client cancellation aborts the stream (a failed write removes the temp file, so no partial files remain). Because the agent HTTP server uses fixed 20 s read/write timeouts, the handler pushes both deadlines forward on every received chunk, so a slow upload only fails after a 20 s stall. If the connection does not support deadline changes, the agent logs one warning per request. - This endpoint is not gated by `CODER_AGENT_BLOCK_FILE_TRANSFER`. That flag targets SSH-transport file tools (scp/rsync/sftp). Chat uploads are an agent API capability like `write-file` and `edit-files`, which already bypass the flag; the interaction is documented in the FAQ. ## How tested - `go test ./agent/agentfiles ./coderd/x/chatfiles ./codersdk/workspacesdk` (unit + umask/symlink/collision integration tests). - UAT on a dogfood-style deployment (2026-09-21): uploads longer than 20 s went from 502 to 201 once the workspace agent was restarted on this build; a coderd restart alone keeps the old agent running, so the per-chunk deadline extension needs the agent rolled (older agents get the 409 "Restart the workspace" from #27079). Part of CODAGT-211. Stack: agent API (this PR) -> coderd endpoint -> UI. > This PR was authored by Mux (AI) working on Mike's behalf.
70 lines
1.8 KiB
Go
70 lines
1.8 KiB
Go
package agentfiles
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/spf13/afero"
|
|
|
|
"cdr.dev/slog/v3"
|
|
"github.com/coder/coder/v2/agent/agentgit"
|
|
"github.com/coder/coder/v2/agent/usershell"
|
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
|
)
|
|
|
|
// API exposes file-related operations performed through the agent.
|
|
type API struct {
|
|
logger slog.Logger
|
|
filesystem afero.Fs
|
|
pathStore *agentgit.PathStore
|
|
envInfo usershell.EnvInfoer
|
|
bundleFilesLimits workspacesdk.BundleFilesLimits
|
|
}
|
|
|
|
// Option configures the API.
|
|
type Option func(*API)
|
|
|
|
// WithBundleFilesLimits overrides the bundle files collection limits.
|
|
func WithBundleFilesLimits(limits workspacesdk.BundleFilesLimits) Option {
|
|
return func(api *API) {
|
|
api.bundleFilesLimits = limits
|
|
}
|
|
}
|
|
|
|
// WithEnvInfo overrides how the agent user's home directory is resolved.
|
|
func WithEnvInfo(envInfo usershell.EnvInfoer) Option {
|
|
return func(api *API) {
|
|
api.envInfo = envInfo
|
|
}
|
|
}
|
|
|
|
func NewAPI(logger slog.Logger, filesystem afero.Fs, pathStore *agentgit.PathStore, opts ...Option) *API {
|
|
api := &API{
|
|
logger: logger,
|
|
filesystem: filesystem,
|
|
pathStore: pathStore,
|
|
envInfo: usershell.SystemEnvInfo{},
|
|
bundleFilesLimits: defaultBundleFilesLimits,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(api)
|
|
}
|
|
return api
|
|
}
|
|
|
|
// Routes returns the HTTP handler for file-related routes.
|
|
func (api *API) Routes() http.Handler {
|
|
r := chi.NewRouter()
|
|
|
|
r.Post("/list-directory", api.HandleLS)
|
|
r.Get("/resolve-path", api.HandleResolvePath)
|
|
r.Get("/read-file", api.HandleReadFile)
|
|
r.Get("/read-file-lines", api.HandleReadFileLines)
|
|
r.Post("/write-file", api.HandleWriteFile)
|
|
r.Post("/upload-chat-file", api.HandleUploadChatFile)
|
|
r.Post("/edit-files", api.HandleEditFiles)
|
|
r.Post("/bundle-files", api.HandleBundleFiles)
|
|
|
|
return r
|
|
}
|