feat: add experimental agents support (#22290)

feat: add AI chat system with agent tools and chat UI

Introduce the chatd subsystem and Agents UI for AI-powered chat
within Coder workspaces.

- Add chatd package with chat loop, message compaction, prompt
  management, and LLM provider integration (OpenAI, Anthropic)
- Add agent tools: create workspace, list/read templates, read/write/
  edit files, execute commands
- Add chat API endpoints with streaming, message editing, and
  durable reconnection
- Add database schema and migrations for chats, chat messages, chat
  providers, and chat model configs
- Add RBAC policies and dbauthz enforcement for chat resources
- Add Agents UI pages with conversation timeline, queued messages
  list, diff viewer, and model configuration panel
- Add comprehensive test coverage including coderd integration tests,
  chatd unit tests, and Storybook stories
- Gate feature behind experiments flag

---------

Co-authored-by: Cian Johnston <cian@coder.com>
Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
Co-authored-by: Jeremy Ruppel <jeremy@coder.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle Carberry
2026-02-27 16:50:56 +00:00
committed by GitHub
co-authored by Cian Johnston Danielle Maywood Jeremy Ruppel Claude Sonnet 4.6
parent 67da4e8b56
commit edee917d88
201 changed files with 44828 additions and 1859 deletions
+15 -8
View File
@@ -404,7 +404,9 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
AvatarURL: member.AvatarURL,
}
w, err := createWorkspace(ctx, aReq, apiKey.UserID, api, owner, req, r, nil)
w, err := createWorkspace(ctx, aReq, apiKey.UserID, api, owner, req, &createWorkspaceOptions{
remoteAddr: r.RemoteAddr,
})
if err != nil {
httperror.WriteResponseError(ctx, rw, err)
return
@@ -500,7 +502,9 @@ func (api *API) postUserWorkspaces(rw http.ResponseWriter, r *http.Request) {
defer commitAudit()
w, err := createWorkspace(ctx, aReq, apiKey.UserID, api, owner, req, r, nil)
w, err := createWorkspace(ctx, aReq, apiKey.UserID, api, owner, req, &createWorkspaceOptions{
remoteAddr: r.RemoteAddr,
})
if err != nil {
httperror.WriteResponseError(ctx, rw, err)
return
@@ -522,6 +526,10 @@ type createWorkspaceOptions struct {
// postCreateInTX is a function that is called within the transaction, after
// the workspace is created but before the workspace build is created.
postCreateInTX func(ctx context.Context, tx database.Store, workspace database.Workspace) error
// remoteAddr is the IP address of the request initiator, used for
// audit logging. HTTP handlers should pass r.RemoteAddr;
// programmatic callers may leave it empty.
remoteAddr string
}
func createWorkspace(
@@ -531,7 +539,6 @@ func createWorkspace(
api *API,
owner workspaceOwner,
req codersdk.CreateWorkspaceRequest,
r *http.Request,
opts *createWorkspaceOptions,
) (codersdk.Workspace, error) {
if opts == nil {
@@ -545,7 +552,7 @@ func createWorkspace(
// This is a premature auth check to avoid doing unnecessary work if the user
// doesn't have permission to create a workspace.
if !api.Authorize(r, policy.ActionCreate,
if !api.HTTPAuth.AuthorizeContext(ctx, policy.ActionCreate,
rbac.ResourceWorkspace.InOrg(template.OrganizationID).WithOwner(owner.ID.String())) {
// If this check fails, return a proper unauthorized error to the user to indicate
// what is going on.
@@ -562,14 +569,14 @@ func createWorkspace(
// Do this upfront to save work. If this fails, the rest of the work
// would be wasted.
if !api.Authorize(r, policy.ActionCreate,
if !api.HTTPAuth.AuthorizeContext(ctx, policy.ActionCreate,
rbac.ResourceWorkspace.InOrg(template.OrganizationID).WithOwner(owner.ID.String())) {
return codersdk.Workspace{}, httperror.ErrResourceNotFound
}
// The user also needs permission to use the template. At this point they have
// read perms, but not necessarily "use". This is also checked in `db.InsertWorkspace`.
// Doing this up front can save some work below if the user doesn't have permission.
if !api.Authorize(r, policy.ActionUse, template) {
if !api.HTTPAuth.AuthorizeContext(ctx, policy.ActionUse, template) {
return codersdk.Workspace{}, httperror.NewResponseError(http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Unauthorized access to use the template %q.", template.Name),
Detail: "Although you are able to view the template, you are unable to create a workspace using it. " +
@@ -801,9 +808,9 @@ func createWorkspace(
db,
api.FileCache,
func(action policy.Action, object rbac.Objecter) bool {
return api.Authorize(r, action, object)
return api.HTTPAuth.AuthorizeContext(ctx, action, object)
},
audit.WorkspaceBuildBaggageFromRequest(r),
audit.WorkspaceBuildBaggage{IP: opts.remoteAddr},
)
return err
}, nil)