Files
coder/coderd/x/chatd/context_helpers.go
T
Hugo Dutka 658a04d28f pr 3
2026-06-04 18:51:22 +00:00

83 lines
2.6 KiB
Go

package chatd
import (
"bytes"
"encoding/json"
"github.com/google/uuid"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
)
// agentChatContextSentinelPath marks the synthetic empty context-file
// part used to record an attempted workspace-context fetch when no
// AGENTS.md content is available. It mirrors the constant of the same
// value in the chatd package so the worker can recognize sentinel
// parts without importing chatd (which would be a cycle).
const agentChatContextSentinelPath = ".coder/agent-chat-context-sentinel"
// contextFileAgentIDFromMessages returns the most recent workspace
// agent ID stamped on a persisted context-file part, ignoring the
// skill-only sentinel. Returns uuid.Nil, false when no stamped
// non-sentinel context-file parts exist.
//
// This mirrors chatd.contextFileAgentID. It is duplicated here as a
// small pure helper so chatworker can decide whether workspace
// context is current without importing chatd.
func contextFileAgentIDFromMessages(messages []database.ChatMessage) (uuid.UUID, bool) {
var lastID uuid.UUID
found := false
for _, msg := range messages {
if !msg.Content.Valid || !bytes.Contains(msg.Content.RawMessage, []byte(`"context-file"`)) {
continue
}
var parts []codersdk.ChatMessagePart
if err := json.Unmarshal(msg.Content.RawMessage, &parts); err != nil {
continue
}
for _, p := range parts {
if p.Type != codersdk.ChatMessagePartTypeContextFile ||
!p.ContextFileAgentID.Valid ||
p.ContextFilePath == agentChatContextSentinelPath {
continue
}
lastID = p.ContextFileAgentID.UUID
found = true
break
}
}
return lastID, found
}
// hasPersistedContextFileForAgent reports whether messages include
// any persisted context-file marker for the given agent, including
// the skill-only sentinel. This is true once the
// persist_workspace_context action has committed at least one
// context-file row for the agent (with or without content), so a
// subsequent decision pass will not loop on the same agent.
func hasPersistedContextFileForAgent(messages []database.ChatMessage, agentID uuid.UUID) bool {
if agentID == uuid.Nil {
return false
}
for _, msg := range messages {
if !msg.Content.Valid || !bytes.Contains(msg.Content.RawMessage, []byte(`"context-file"`)) {
continue
}
var parts []codersdk.ChatMessagePart
if err := json.Unmarshal(msg.Content.RawMessage, &parts); err != nil {
continue
}
for _, p := range parts {
if p.Type != codersdk.ChatMessagePartTypeContextFile ||
!p.ContextFileAgentID.Valid {
continue
}
if p.ContextFileAgentID.UUID == agentID {
return true
}
}
}
return false
}