mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
e57525002c
Remove the `ExperimentAgents` feature flag so the Agents feature is always available without requiring `--experiments=agents`. The feature is now in beta. Existing deployments that still pass `--experiments=agents` will get a harmless "ignoring unknown experiment" warning on startup. ### Changes **Backend:** - Remove `RequireExperimentWithDevBypass` middleware from chat and MCP server routes - Always include `AgentsAccessRole` in assignable site roles (later refactored to org-scoped on main; rebase keeps that) - Always set `AgentsTabVisible = true`, then drop the entire dead `AgentsTabVisible` metadata pipeline (Go htmlState field, populateHTMLState goroutine, HTML meta tag, useEmbeddedMetadata registration, mock); no production consumer reads it. `AgentsNavItem` already gates on `permissions.createChat`. - Make `blob:` CSP `img-src` addition unconditional - Remove `ExperimentAgents` constant, `DisplayName` case, and `ExperimentsKnown` entry **CLI:** - Graduate the agents TUI from `coder exp agents` to `coder agents` (moved from `AGPLExperimental()` to `CoreSubcommands()`) - Drop the `agent` alias so it does not collide with the hidden workspace-agent command - Rename implementation files `cli/exp_agents_*.go` -> `cli/agents_*.go` and internal identifiers (`expChatsTUIModel` -> `chatsTUIModel`, `newExpChatsTUIModel` -> `newChatsTUIModel`, `setupExpAgentsBackend` -> `setupAgentsBackend`, `startExpAgentsSession` -> `startAgentsSession`, `expAgentsPtr` -> `agentsPtr`, `expAgentsSession` -> `agentsSession`, `TestExpAgents*` -> `TestAgents*`). `expClient` (the `*codersdk.ExperimentalClient` local) is kept; `coderd/exp_chats*.go` and other still-experimental `cli/exp_*.go` commands are intentionally untouched. **Frontend:** - Remove experiment check from `AgentsNavItem` - render when `canCreateChat` is true - Remove `agentsEnabled` experiment check from `WorkspacesPage`, then gate `chatsByWorkspace` on `permissions.createChat` so users without chat access don't trigger the per-page DB query (Copilot review feedback) - Add `FeatureStageBadge` (beta) next to the Coder logo in the Agents sidebar (desktop + mobile) **Docs:** - Remove experiment flag setup instructions from `early-access.md` and `getting-started.md` (and rename `early-access.md`'s "Enable Coder Agents" heading to "Set up Coder Agents", since there is no enablement step left) - Update `chats-api.md` and `getting-started.md`'s Chats API note to say "beta" instead of "experimental" - `docs/manifest.json`: drop "experimental" from the Chats API sidebar description - `make gen` regenerated `docs/reference/cli/agents.md` and the CLI index - `scripts/check_emdash.sh`: exclude `cli/testdata/*.golden` and `enterprise/cli/testdata/*.golden` from the new repo-wide emdash lint, since serpent emits emdash borders in every generated `--help` golden file **Tests:** - Remove `ExperimentAgents` setup from all test files (14 occurrences across 7 files) - Update stale "with the agents experiment" comments in `coderd/x/chatd/integration_test.go` and `coderd/mcp_test.go` <img width="1185" height="900" alt="image" src="https://github.com/user-attachments/assets/b420bc8f-41d6-42c6-abd8-ad572533d651" /> > 🤖 Generated by Coder Agents
181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"slices"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
type (
|
|
chatsListedMsg struct {
|
|
chats []codersdk.Chat
|
|
err error
|
|
}
|
|
chatOpenedMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
chat codersdk.Chat
|
|
err error
|
|
}
|
|
chatHistoryMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
messages []codersdk.ChatMessage
|
|
err error
|
|
}
|
|
chatCreatedMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
chat codersdk.Chat
|
|
err error
|
|
}
|
|
chatPlanModeUpdatedMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
err error
|
|
}
|
|
messageSentMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
resp codersdk.CreateChatMessageResponse
|
|
err error
|
|
}
|
|
chatInterruptedMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
chat codersdk.Chat
|
|
err error
|
|
}
|
|
modelsListedMsg struct {
|
|
catalog codersdk.ChatModelsResponse
|
|
err error
|
|
}
|
|
diffContentsMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
diff codersdk.ChatDiffContents
|
|
err error
|
|
}
|
|
chatStreamEventMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
event codersdk.ChatStreamEvent
|
|
err error
|
|
}
|
|
// showAskUserQuestionMsg tells the parent model to open the
|
|
// ask-user-question overlay.
|
|
showAskUserQuestionMsg struct {
|
|
state *askUserQuestionState
|
|
}
|
|
// hideAskUserQuestionMsg tells the parent model to close the
|
|
// ask-user-question overlay.
|
|
hideAskUserQuestionMsg struct{}
|
|
// toolResultsSubmittedMsg is sent after the async SubmitToolResults
|
|
// call completes.
|
|
toolResultsSubmittedMsg struct {
|
|
generation uint64
|
|
chatID uuid.UUID
|
|
err error
|
|
}
|
|
streamRetryMsg struct {
|
|
generation uint64
|
|
}
|
|
toggleModelPickerMsg struct{}
|
|
toggleDiffDrawerMsg struct{}
|
|
)
|
|
|
|
func scheduleStreamRetry(generation uint64, delay time.Duration) tea.Cmd {
|
|
return tea.Tick(delay, func(time.Time) tea.Msg {
|
|
return streamRetryMsg{generation: generation}
|
|
})
|
|
}
|
|
|
|
func apiCmd[T any](fn func() (T, error), wrap func(T, error) tea.Msg) tea.Cmd {
|
|
return func() tea.Msg {
|
|
value, err := fn()
|
|
return wrap(value, err)
|
|
}
|
|
}
|
|
|
|
func loadChatHistoryCmd(ctx context.Context, client *codersdk.ExperimentalClient, chatID uuid.UUID, generation uint64) tea.Cmd {
|
|
return apiCmd(func() ([]codersdk.ChatMessage, error) {
|
|
var (
|
|
allMessages []codersdk.ChatMessage
|
|
opts *codersdk.ChatMessagesPaginationOptions
|
|
)
|
|
|
|
for {
|
|
resp, err := client.GetChatMessages(ctx, chatID, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allMessages = append(allMessages, resp.Messages...)
|
|
if !resp.HasMore || len(resp.Messages) == 0 {
|
|
break
|
|
}
|
|
|
|
opts = &codersdk.ChatMessagesPaginationOptions{
|
|
BeforeID: resp.Messages[len(resp.Messages)-1].ID,
|
|
}
|
|
}
|
|
|
|
slices.SortStableFunc(allMessages, func(a, b codersdk.ChatMessage) int {
|
|
switch {
|
|
case a.CreatedAt.Before(b.CreatedAt):
|
|
return -1
|
|
case a.CreatedAt.After(b.CreatedAt):
|
|
return 1
|
|
case a.ID < b.ID:
|
|
return -1
|
|
case a.ID > b.ID:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
})
|
|
|
|
return allMessages, nil
|
|
}, func(messages []codersdk.ChatMessage, err error) tea.Msg {
|
|
return chatHistoryMsg{generation: generation, chatID: chatID, messages: messages, err: err}
|
|
})
|
|
}
|
|
|
|
func submitAskUserQuestionCmd(client *codersdk.Client, chatID uuid.UUID, generation uint64, state *askUserQuestionState) tea.Cmd {
|
|
output, err := buildAskUserQuestionToolResult(state)
|
|
if err != nil {
|
|
return func() tea.Msg {
|
|
return toolResultsSubmittedMsg{generation: generation, chatID: chatID, err: err}
|
|
}
|
|
}
|
|
|
|
req := codersdk.SubmitToolResultsRequest{
|
|
Results: []codersdk.ToolResult{{
|
|
ToolCallID: state.ToolCallID,
|
|
Output: output,
|
|
IsError: false,
|
|
}},
|
|
}
|
|
return apiCmd(func() (struct{}, error) {
|
|
return struct{}{}, codersdk.NewExperimentalClient(client).SubmitToolResults(context.Background(), chatID, req)
|
|
}, func(_ struct{}, err error) tea.Msg {
|
|
return toolResultsSubmittedMsg{generation: generation, chatID: chatID, err: err}
|
|
})
|
|
}
|
|
|
|
func listenToStream(chatID uuid.UUID, generation uint64, eventCh <-chan codersdk.ChatStreamEvent) tea.Cmd {
|
|
return func() tea.Msg {
|
|
event, ok := <-eventCh
|
|
if !ok {
|
|
return chatStreamEventMsg{generation: generation, chatID: chatID, err: io.EOF}
|
|
}
|
|
return chatStreamEventMsg{generation: generation, chatID: chatID, event: event}
|
|
}
|
|
}
|