Files
coder/coderd/database/queries/chatprojects.sql
T
Garrett DelfosseandClaude Opus 5.5 7cd1b2040b feat: add chat projects API (#29242)
- Experiment, behind `chat-projects` (off by default). First of three
stacked PRs: #29301 (memory), #29583 (UI). This PR is API only; the
screenshots below are from the UI in #29583.
- CRUD under
`/api/experimental/organizations/{organization}/chats/projects/{project}`.
`GET /api/experimental/chats/projects` lists the caller's projects
across all organizations.
- `project_id` on chat create and as a chat list filter. A chat joins a
project only at creation; there is no move.
- Projects are identified by ID. Names are display labels (64
characters, description 1024) with no uniqueness constraint. An optional
`icon` URL (usually an emoji image under `/emojis`) is capped at 256
characters and audited.
- A user can own at most 100 projects across all organizations; creating
another answers 409. Creates count and insert under a per-owner advisory
lock so concurrent requests cannot overshoot.
- Projects are private to their `owner_id`. Members may only create
their own, and a chat can only be bound to a project its owner owns. An
unreadable or foreign project on chat create answers 404 so IDs cannot
be probed; a project from another organization answers 400.
- Permissions ride on the `agents-access` role next to chats (after
#30004), so whoever can use Coder Agents can use projects. Owners and
org admins reach every project.

### Examples

Create a project in an organization:

```http
POST /api/experimental/organizations/{organization}/chats/projects
{"name": "Platform", "description": "Infra and deploy work", "icon": "/emojis/1f680.png"}
```

```json
{
  "id": "7c0e4a3e-5b1f-4f7d-9a51-2f3c8f6d2b10",
  "organization_id": "bb640d07-ca8a-4869-b6bc-ae61ebb2fda1",
  "owner_id": "0ed9befc-4911-4ccf-a8e2-559bf72daa94",
  "name": "Platform",
  "description": "Infra and deploy work",
  "icon": "/emojis/1f680.png",
  "created_at": "2026-09-30T15:04:05Z",
  "updated_at": "2026-09-30T15:04:05Z"
}
```

List the caller's projects across organizations with `GET
/api/experimental/chats/projects` (an array of the object above). Update
with `PATCH .../chats/projects/{project}` and any of `name`,
`description`, `icon`; delete with `DELETE`, which detaches its chats.

Create a chat in the project, and list only its chats:

```http
POST /api/v2/chats
{"organization_id": "bb640d07-…", "project_id": "7c0e4a3e-…", "content": [{"type": "text", "text": "Plan the deploy"}]}

GET /api/v2/chats?project_id=7c0e4a3e-…
```

At the limit:

```json
409 {"message": "You can have at most 100 chat projects. Delete a project to create another."}
```

<details>
<summary>Screenshots (UI from #29583)</summary>

![Create
project](https://raw.githubusercontent.com/coder/coder/f0ssel/chat-projects-assets/v3/pr1-01-create-project-dialog.png)
![Sidebar
folders](https://raw.githubusercontent.com/coder/coder/f0ssel/chat-projects-assets/v3/pr1-02-sidebar-folders.png)
![Project
page](https://raw.githubusercontent.com/coder/coder/f0ssel/chat-projects-assets/v3/pr1-03-project-page.png)
![Folder
menu](https://raw.githubusercontent.com/coder/coder/f0ssel/chat-projects-assets/v3/pr1-05-folder-menu.png)
![Another member sees no
projects](https://raw.githubusercontent.com/coder/coder/f0ssel/chat-projects-assets/v3/pr5-member-sees-no-projects.png)

</details>

---
Generated with Coder Agents on behalf of @f0ssel.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:49:21 -04:00

42 lines
923 B
SQL

-- name: InsertChatProject :one
INSERT INTO chat_projects (id, organization_id, owner_id, name, description, icon)
VALUES (
COALESCE(sqlc.narg('id')::uuid, gen_random_uuid()),
@organization_id::uuid,
@owner_id::uuid,
@name::text,
@description::text,
@icon::text
)
RETURNING *;
-- name: GetChatProjectByID :one
SELECT *
FROM chat_projects
WHERE id = @id::uuid;
-- name: GetChatProjectsByOwnerID :many
SELECT *
FROM chat_projects
WHERE owner_id = @owner_id::uuid
ORDER BY lower(name), id;
-- name: UpdateChatProjectByID :one
UPDATE chat_projects
SET
name = @name::text,
description = @description::text,
icon = @icon::text,
updated_at = now()
WHERE id = @id::uuid
RETURNING *;
-- name: DeleteChatProjectByID :exec
DELETE FROM chat_projects
WHERE id = @id::uuid;
-- name: CountChatProjectsByOwnerID :one
SELECT COUNT(*)::bigint
FROM chat_projects
WHERE owner_id = @owner_id::uuid;