Files
coder/coderd/x/chatd/agentselect/agentselect.go
T

87 lines
2.6 KiB
Go

package agentselect
import (
"cmp"
"slices"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
)
// Suffix marks chat-designated agents during the current PoC. This naming
// convention is an implementation detail, not a stable contract.
const Suffix = "-coderd-chat"
// IsChatAgent reports whether name uses the chat-agent suffix convention.
func IsChatAgent(name string) bool {
return strings.HasSuffix(strings.ToLower(name), Suffix)
}
// FindChatAgent picks the best workspace agent for a chat session from the
// provided candidates. It applies these rules in order:
// 1. Filter to root agents only (ParentID is null).
// 2. Sort stably and deterministically by DisplayOrder ASC, then Name ASC
// (case-insensitive), then Name ASC, then ID ASC.
// 3. If exactly one root agent name ends with Suffix (case-insensitive),
// return it.
// 4. If zero root agents match the suffix, return the first root agent after
// sorting (deterministic fallback).
// 5. If more than one root agent matches the suffix, return an error with an
// actionable message.
// 6. If no root agents exist at all, return an error.
func FindChatAgent(
agents []database.WorkspaceAgent,
) (database.WorkspaceAgent, error) {
rootAgents := make([]database.WorkspaceAgent, 0, len(agents))
matchingAgents := make([]database.WorkspaceAgent, 0, 1)
for _, agent := range agents {
if agent.ParentID.Valid {
continue
}
rootAgents = append(rootAgents, agent)
if IsChatAgent(agent.Name) {
matchingAgents = append(matchingAgents, agent)
}
}
if len(rootAgents) == 0 {
return database.WorkspaceAgent{}, xerrors.New(
"no eligible workspace agents found",
)
}
compareAgents := func(a, b database.WorkspaceAgent) int {
if order := cmp.Compare(a.DisplayOrder, b.DisplayOrder); order != 0 {
return order
}
if order := cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)); order != 0 {
return order
}
if order := cmp.Compare(a.Name, b.Name); order != 0 {
return order
}
return cmp.Compare(a.ID.String(), b.ID.String())
}
slices.SortStableFunc(rootAgents, compareAgents)
slices.SortStableFunc(matchingAgents, compareAgents)
switch len(matchingAgents) {
case 0:
return rootAgents[0], nil
case 1:
return matchingAgents[0], nil
default:
names := make([]string, 0, len(matchingAgents))
for _, agent := range matchingAgents {
names = append(names, agent.Name)
}
return database.WorkspaceAgent{}, xerrors.Errorf(
"multiple agents match the chat suffix %q: %s; only one agent should use this suffix",
Suffix,
strings.Join(names, ", "),
)
}
}