chatd

package
v2.33.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: AGPL-3.0 Imports: 62 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultPendingChatAcquireInterval is the default time between attempts to
	// acquire pending chats.
	DefaultPendingChatAcquireInterval = time.Second
	// DefaultInFlightChatStaleAfter is the default age after which a running
	// chat is considered stale and should be recovered.
	DefaultInFlightChatStaleAfter = 5 * time.Minute

	// DefaultChatHeartbeatInterval is the default time between chat
	// heartbeat updates while a chat is being processed.
	DefaultChatHeartbeatInterval = 30 * time.Second

	// DefaultMaxChatsPerAcquire is the maximum number of chats to
	// acquire in a single processOnce call. Batching avoids
	// waiting a full polling interval between acquisitions
	// when many chats are pending.
	DefaultMaxChatsPerAcquire int32 = 10
)
View Source
const AgentChatContextSentinelPath = ".coder/agent-chat-context-sentinel"

AgentChatContextSentinelPath marks the synthetic empty context-file part used to preserve skill-only workspace-agent additions across turns without treating them as persisted instruction files.

View Source
const DefaultSystemPrompt = `You are the Coder agent — an interactive chat tool that helps users with software-engineering tasks inside of the Coder product.
Use the instructions below and the tools available to you to assist User.

IMPORTANT — obey every rule in this prompt before anything else.
Do EXACTLY what the User asked, never more, never less.

<behavior>
You MUST execute AS MANY TOOLS to help the user accomplish their task.
You are COMFORTABLE with vague tasks - using your tools to collect the most relevant answer possible.
If a user asks how something works, no matter how vague, you MUST use your tools to collect the most relevant answer possible.
Use tools first to gather context and make progress.
Do not ask clarifying questions if the answer can be obtained from the codebase, workspace, or existing project conventions.
Ask concise clarifying questions only when:
- the user's intent is materially ambiguous;
- architecture, tooling, or style preferences would change the implementation;
- the action is destructive, irreversible, or expensive; or
- you cannot make progress with confidence.
If a task is too ambiguous to implement with confidence, ask for clarification before proceeding.
</behavior>

<personality>
Analytical — You break problems into measurable steps, relying on tool output and data rather than intuition.
Organized — You structure every interaction with clear tags, TODO lists, and section boundaries.
Precision-Oriented — You insist on exact formatting, package-manager choice, and rule adherence.
Efficiency-Focused — You minimize chatter, run tasks in parallel, and favor small, complete answers.
Clarity-Seeking — You resolve ambiguity with tools when possible and ask focused questions only when necessary.
</personality>

<communication>
Be concise, direct, and to the point.
NO emojis unless the User explicitly asks for them.
If a task appears incomplete or ambiguous, first use your tools to gather context. **Pause and ask the User** only if material ambiguity remains rather than guessing or marking "done".
Prefer accuracy over reassurance; confirm facts with tool calls instead of assuming the User is right.
If you face an architectural, tooling, or package-manager choice, **ask the User's preference first**.
Default to the project's existing package manager / tooling; never substitute without confirmation.
You MUST avoid text before/after your response, such as "The answer is" or "Short answer:", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".
Mimic the style of the User's messages.
Do not remind the User you are happy to help.
Do not inherently assume the User is correct; they may be making assumptions.
If you are not confident in your answer, DO NOT provide an answer. Use your tools to collect more information, or ask the User for help.
Do not act with sycophantic flattery or over-the-top enthusiasm.

Here are examples to demonstrate appropriate communication style and level of verbosity:

<example>
user: find me a good issue to work on
assistant: Issue [#1234](https://example) indicates a bug in the frontend, which you've contributed to in the past.
</example>

<example>
user: work on this issue <url>
...assistant does work...
assistant: I've put up this pull request: https://github.com/example/example/pull/1824. Please let me know your thoughts!
</example>

<example>
user: what is 2+2?
assistant: 4
</example>

<example>
user: how does X work in <popular-repository-name>?
assistant: Let me take a look at the code...
[tool calls to investigate the repository]
</example>
</communication>

<collaboration>
When clarification is necessary, ask concise questions to understand:
- What specific aspect they want to focus on
- Their goals and vision for the changes
- Their preferences for approach or style
- What problems they're trying to solve

Do not start with clarifying questions if the codebase or tools can answer them.
Ask the minimum number of questions needed to define the scope together.
</collaboration>

<planning>
Propose a plan when:
- The task is too ambiguous to implement with confidence.
- The user asks for a plan.

If no workspace is attached to this chat yet, create and start one first using create_workspace and start_workspace.
Once a workspace is available:
` + defaultSystemPromptPlanningGuidance + `
2. Use write_file to create a Markdown plan file at the absolute
   chat-specific path from the <plan-file-path> block below when it is
   available.
3. Iterate on the plan with edit_files if needed.
4. Present the plan to the user and wait for review before starting implementation.

Write the file first, then present it. All file paths must be absolute.
When the <plan-file-path> block below is present, use that exact path.
` + defaultSystemPromptPlanPathBlockPlaceholder + `
</planning>`

DefaultSystemPrompt is used for new chats when no deployment override is configured.

View Source
const ExploreSubagentOverlayPrompt = `` /* 313-byte string literal not displayed */

ExploreSubagentOverlayPrompt contains Explore-mode instructions for delegated child chats.

View Source
const MaxQueueSize = 20

MaxQueueSize is the maximum number of queued user messages per chat.

View Source
const PlanningSubagentOverlayPrompt = `` /* 413-byte string literal not displayed */

PlanningSubagentOverlayPrompt contains plan-mode instructions for delegated child chats. Child chats may investigate with shell tools but should return findings to the parent instead of authoring the final plan.

Variables

View Source
var (
	// ErrInvalidModelConfigID indicates the requested model config does not exist.
	ErrInvalidModelConfigID = xerrors.New("invalid model config ID")
	// ErrMessageQueueFull indicates the per-chat queue limit was reached.
	ErrMessageQueueFull = xerrors.New("chat message queue is full")
	// ErrEditedMessageNotFound indicates the edited message does not exist
	// in the target chat.
	ErrEditedMessageNotFound = xerrors.New("edited message not found")
	// ErrEditedMessageNotUser indicates a non-user message edit attempt.
	ErrEditedMessageNotUser = xerrors.New("only user messages can be edited")
	// ErrChatArchived indicates the chat is archived and cannot
	// accept modifications (messages, edits, promotions, or
	// tool-result submissions).
	ErrChatArchived = xerrors.New("chat is archived")
)
View Source
var ErrChildUnarchiveParentArchived = xerrors.New(
	"cannot unarchive child chat while parent is archived",
)

ErrChildUnarchiveParentArchived is returned by UnarchiveChat when a child unarchive is rejected because the parent is still archived. The patchChat handler maps this to a 400 response.

View Source
var ErrManualTitleRegenerationInProgress = xerrors.New(
	"manual title regeneration already in progress",
)
View Source
var ErrSubagentNotDescendant = xerrors.New("target chat is not a descendant of current chat")

Functions

func BuildLastInjectedContext added in v2.33.0

func BuildLastInjectedContext(
	parts []codersdk.ChatMessagePart,
) (pqtype.NullRawMessage, error)

BuildLastInjectedContext filters parts down to non-empty context-file and skill parts, strips their internal fields, and marshals the result for LastInjectedContext. A nil or fully filtered input returns an invalid NullRawMessage.

func BuildSingleChatMessageInsertParams added in v2.33.0

func BuildSingleChatMessageInsertParams(
	chatID uuid.UUID,
	role database.ChatMessageRole,
	content pqtype.NullRawMessage,
	visibility database.ChatMessageVisibility,
	modelConfigID uuid.UUID,
	contentVersion int16,
	createdBy uuid.UUID,
) database.InsertChatMessagesParams

BuildSingleChatMessageInsertParams creates batch insert params for one message using the shared chat message builder.

func CollectContextPartsFromMessages added in v2.33.0

func CollectContextPartsFromMessages(
	ctx context.Context,
	logger slog.Logger,
	messages []database.ChatMessage,
	keepEmptyContextFiles bool,
) ([]codersdk.ChatMessagePart, error)

CollectContextPartsFromMessages unmarshals chat message content and collects the context-file and skill parts it contains. When keepEmptyContextFiles is false, empty context-file parts are skipped. When it is true, empty context-file parts are included in the result.

func ComputeUsagePeriodBounds

func ComputeUsagePeriodBounds(now time.Time, period codersdk.ChatUsageLimitPeriod) (start, end time.Time)

ComputeUsagePeriodBounds returns the UTC-aligned start and end bounds for the active usage-limit period containing now.

func FilterContextParts added in v2.33.0

func FilterContextParts(
	parts []codersdk.ChatMessagePart,
	keepEmptyContextFiles bool,
) []codersdk.ChatMessagePart

FilterContextParts keeps only context-file and skill parts from parts. When keepEmptyContextFiles is false, context-file parts with empty content are dropped. When keepEmptyContextFiles is true, empty context-file parts are preserved. revive:disable-next-line:flag-parameter // Required by shared helper callers.

func FilterContextPartsToLatestAgent added in v2.33.0

func FilterContextPartsToLatestAgent(parts []codersdk.ChatMessagePart) []codersdk.ChatMessagePart

FilterContextPartsToLatestAgent keeps parts stamped with the latest workspace-agent ID seen in the slice, plus legacy unstamped parts. When no stamped context-file parts exist, it returns the original slice unchanged.

func PlanningOverlayPrompt added in v2.33.0

func PlanningOverlayPrompt() string

PlanningOverlayPrompt returns the plan-mode-only instructions appended when the chat is in plan mode.

func ResolveUsageLimitStatus

func ResolveUsageLimitStatus(ctx context.Context, db database.Store, userID uuid.UUID, organizationID uuid.NullUUID, now time.Time) (*codersdk.ChatUsageLimitStatus, error)

ResolveUsageLimitStatus resolves the current usage-limit status for userID within organizationID. When organizationID is invalid (Valid == false), limits and spend are computed globally across all organizations (legacy behavior).

Note: There is a potential race condition where two concurrent messages from the same user can both pass the limit check if processed in parallel, allowing brief overage. This is acceptable because:

  • Cost is only known after the LLM API returns.
  • Overage is bounded by message cost × concurrency.
  • Fail-open is the deliberate design choice for this feature.

Architecture note: today this path enforces one period globally (day/week/month) from config. To support simultaneous periods, add nullable daily/weekly/monthly_limit_micros columns on override tables, where NULL means no limit for that period. Then scan spend once over the widest active window with conditional SUMs for each period and compare each spend/limit pair Go-side, blocking on whichever period is tightest.

func SanitizePromptText

func SanitizePromptText(s string) string

SanitizePromptText strips invisible Unicode characters that could hide prompt-injection content from human reviewers, normalizes line endings, collapses excessive blank lines, and trims surrounding whitespace.

The stripped codepoints are truly invisible and have no legitimate use in prompt text. An explicit codepoint list is used rather than blanket unicode.Cf stripping to avoid breaking subdivision flag emoji (🏴󠁧󠁢󠁥󠁮󠁧󠁿) and other legitimate format characters.

Note: U+200D (ZWJ) is stripped even though it joins compound emoji (e.g. 👨‍👩‍👦 → 👨👩👦). This is an acceptable trade-off because system prompts are not emoji art, and ZWJ is actively exploited in zero-width steganography schemes as a delimiter character.

Types

type AgentConnFunc

type AgentConnFunc func(ctx context.Context, agentID uuid.UUID) (workspacesdk.AgentConn, func(), error)

AgentConnFunc provides access to workspace agent connections.

type Config

type Config struct {
	Logger                         slog.Logger
	Database                       database.Store
	ReplicaID                      uuid.UUID
	SubscribeFn                    SubscribeFn
	PendingChatAcquireInterval     time.Duration
	MaxChatsPerAcquire             int32
	InFlightChatStaleAfter         time.Duration
	ChatHeartbeatInterval          time.Duration
	AgentConn                      AgentConnFunc
	AgentInactiveDisconnectTimeout time.Duration
	InstructionLookupTimeout       time.Duration
	CreateWorkspace                chattool.CreateWorkspaceFn
	StartWorkspace                 chattool.StartWorkspaceFn
	Pubsub                         pubsub.Pubsub
	ProviderAPIKeys                chatprovider.ProviderAPIKeys
	AlwaysEnableDebugLogs          bool
	WebpushDispatcher              webpush.Dispatcher
	UsageTracker                   *workspacestats.UsageTracker
	Clock                          quartz.Clock
	PrometheusRegistry             prometheus.Registerer
}

Config configures a chat processor.

type CreateOptions

type CreateOptions struct {
	OrganizationID     uuid.UUID
	OwnerID            uuid.UUID
	WorkspaceID        uuid.NullUUID
	BuildID            uuid.NullUUID
	AgentID            uuid.NullUUID
	ParentChatID       uuid.NullUUID
	RootChatID         uuid.NullUUID
	Title              string
	ModelConfigID      uuid.UUID
	ChatMode           database.NullChatMode
	PlanMode           database.NullChatPlanMode
	ClientType         database.ChatClientType
	SystemPrompt       string
	InitialUserContent []codersdk.ChatMessagePart
	MCPServerIDs       []uuid.UUID
	Labels             database.StringMap
	DynamicTools       json.RawMessage
}

CreateOptions controls chat creation in the shared chat mutation path.

type DialFunc

type DialFunc func(ctx context.Context, id uuid.UUID) (workspacesdk.AgentConn, func(), error)

DialFunc dials an agent by ID and returns a connection.

type DialResult

type DialResult struct {
	Conn        workspacesdk.AgentConn
	Release     func()
	AgentID     uuid.UUID // The agent that was actually dialed.
	WasSwitched bool      // True if validation discovered a different agent.
}

DialResult contains the outcome of dialWithLazyValidation.

type EditMessageOptions

type EditMessageOptions struct {
	ChatID          uuid.UUID
	CreatedBy       uuid.UUID
	EditedMessageID int64
	Content         []codersdk.ChatMessagePart
}

EditMessageOptions controls user message edits via soft-delete and re-insert.

type EditMessageResult

type EditMessageResult struct {
	Message database.ChatMessage
	Chat    database.Chat
}

EditMessageResult contains the replacement user message and chat status.

type PromoteQueuedOptions

type PromoteQueuedOptions struct {
	ChatID          uuid.UUID
	CreatedBy       uuid.UUID
	QueuedMessageID int64
}

PromoteQueuedOptions controls queued-message promotion.

type PromoteQueuedResult

type PromoteQueuedResult struct {
	PromotedMessage database.ChatMessage
}

PromoteQueuedResult contains post-promotion message metadata.

type SendMessageBusyBehavior

type SendMessageBusyBehavior string

SendMessageBusyBehavior controls what happens when a chat is already active.

const (
	// SendMessageBusyBehaviorQueue queues user messages while the chat is busy.
	SendMessageBusyBehaviorQueue SendMessageBusyBehavior = "queue"
	// SendMessageBusyBehaviorInterrupt queues the message and
	// interrupts the active run. The queued message is
	// auto-promoted after the interrupted assistant response is
	// persisted, ensuring correct message ordering.
	SendMessageBusyBehaviorInterrupt SendMessageBusyBehavior = "interrupt"
)

type SendMessageOptions

type SendMessageOptions struct {
	ChatID        uuid.UUID
	CreatedBy     uuid.UUID
	Content       []codersdk.ChatMessagePart
	ModelConfigID uuid.UUID
	BusyBehavior  SendMessageBusyBehavior
	PlanMode      *database.NullChatPlanMode
	MCPServerIDs  *[]uuid.UUID
}

SendMessageOptions controls user message insertion with busy-state behavior.

type SendMessageResult

type SendMessageResult struct {
	Queued        bool
	QueuedMessage *database.ChatQueuedMessage
	Message       database.ChatMessage
	Chat          database.Chat
}

SendMessageResult contains the outcome of user message processing.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server handles background processing of pending chats.

func New

func New(cfg Config) *Server

New creates a new chat processor. The processor polls for pending chats and processes them. It is the caller's responsibility to call Close on the returned instance.

func (*Server) ArchiveChat

func (p *Server) ArchiveChat(ctx context.Context, chat database.Chat) error

ArchiveChat archives a chat family and broadcasts deleted events for each affected chat so watching clients converge without a full refetch. If the target chat is pending or running, it first transitions the chat back to waiting so active processing stops before the archive is broadcast.

func (*Server) Close

func (p *Server) Close() error

Close stops the processor and waits for it to finish.

func (*Server) CreateChat

func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.Chat, error)

CreateChat creates a chat, inserts optional system prompt and initial user message, and moves the chat into pending status.

func (*Server) DeleteQueued

func (p *Server) DeleteQueued(
	ctx context.Context,
	chatID uuid.UUID,
	queuedMessageID int64,
) error

DeleteQueued removes a queued user message and publishes the queue update.

func (*Server) EditMessage

func (p *Server) EditMessage(
	ctx context.Context,
	opts EditMessageOptions,
) (EditMessageResult, error)

EditMessage marks the old user message as deleted, soft-deletes all following messages, inserts a new message with the updated content, clears queued messages, and moves the chat into pending status.

func (*Server) InterruptChat

func (p *Server) InterruptChat(
	ctx context.Context,
	chat database.Chat,
) database.Chat

InterruptChat interrupts execution, sets waiting status, and broadcasts status updates.

func (*Server) PromoteQueued

func (p *Server) PromoteQueued(
	ctx context.Context,
	opts PromoteQueuedOptions,
) (PromoteQueuedResult, error)

PromoteQueued promotes a queued message into chat history and marks the chat pending.

func (*Server) ProposeChatTitle added in v2.33.0

func (p *Server) ProposeChatTitle(
	ctx context.Context,
	chat database.Chat,
) (string, error)

ProposeChatTitle generates a title suggestion from the chat's visible messages without persisting it.

func (*Server) PublishDiffStatusChange

func (p *Server) PublishDiffStatusChange(ctx context.Context, chatID uuid.UUID) error

PublishDiffStatusChange broadcasts a diff_status_change event for the given chat so that watching clients know to re-fetch the diff status. This is called from the HTTP layer after the diff status is updated in the database.

func (*Server) PublishTitleChange added in v2.33.0

func (p *Server) PublishTitleChange(chat database.Chat)

PublishTitleChange broadcasts a title_change event for the given chat.

func (*Server) RefreshStatus

func (p *Server) RefreshStatus(ctx context.Context, chatID uuid.UUID) error

RefreshStatus loads the latest chat status and publishes it to stream subscribers.

func (*Server) RegenerateChatTitle

func (p *Server) RegenerateChatTitle(
	ctx context.Context,
	chat database.Chat,
) (database.Chat, error)

RegenerateChatTitle regenerates a chat title from the chat's visible messages, persists it when it changes, and broadcasts the update.

func (*Server) RenameChatTitle added in v2.33.0

func (p *Server) RenameChatTitle(
	ctx context.Context,
	chat database.Chat,
	newTitle string,
) (updated database.Chat, wrote bool, err error)

RenameChatTitle persists a user-supplied chat title.

func (*Server) SendMessage

func (p *Server) SendMessage(
	ctx context.Context,
	opts SendMessageOptions,
) (SendMessageResult, error)

SendMessage inserts a user message and optionally queues it while the chat is busy, then publishes stream + pubsub updates.

func (*Server) SubmitToolResults added in v2.33.0

func (p *Server) SubmitToolResults(
	ctx context.Context,
	opts SubmitToolResultsOptions,
) error

SubmitToolResults validates and persists client-provided tool results, transitions the chat to pending, and wakes the run loop. The caller is responsible for the fast-path status check; this method performs an authoritative re-check under a row lock.

func (*Server) Subscribe

func (p *Server) Subscribe(
	ctx context.Context,
	chatID uuid.UUID,
	requestHeader http.Header,
	afterMessageID int64,
) (
	[]codersdk.ChatStreamEvent,
	<-chan codersdk.ChatStreamEvent,
	func(),
	bool,
)

func (*Server) UnarchiveChat

func (p *Server) UnarchiveChat(ctx context.Context, chat database.Chat) error

UnarchiveChat unarchives a chat family and broadcasts created events. Root chats cascade through UnarchiveChatByID. Child chats run under a row-level lock on the child (GetChatByIDForUpdate) with an in-transaction re-read of the parent, returning ErrChildUnarchiveParentArchived when the parent is archived and a no-op when the child is already active.

The child is locked before the parent is read to avoid deadlocking with a concurrent ArchiveChatByID cascade, which visits child rows before the parent.

type StatusNotification

type StatusNotification struct {
	Status   database.ChatStatus
	WorkerID uuid.UUID
}

StatusNotification informs the enterprise relay manager of chat status changes so it can open or close relay connections.

type SubmitToolResultsOptions added in v2.33.0

type SubmitToolResultsOptions struct {
	ChatID        uuid.UUID
	UserID        uuid.UUID
	ModelConfigID uuid.UUID
	Results       []codersdk.ToolResult
	DynamicTools  json.RawMessage
}

SubmitToolResultsOptions controls tool result submission.

type SubscribeFn

type SubscribeFn func(
	ctx context.Context,
	params SubscribeFnParams,
) <-chan codersdk.ChatStreamEvent

SubscribeFn replaces the default local-only subscription with a multi-replica-aware implementation that merges pubsub notifications, remote relay streams, and local parts into a single event channel. When set, Subscribe delegates the event-merge goroutine to this function instead of using simple local forwarding.

Parameters:

  • ctx: subscription lifetime context (canceled on unsubscribe).
  • params: all state needed to build the merged stream.

Returns the merged event channel. Cleanup is driven by ctx cancellation — the merge goroutine tears down all relay state in its defer when ctx is done. Set by enterprise for HA deployments. Nil in AGPL single-replica.

type SubscribeFnParams

type SubscribeFnParams struct {
	ChatID              uuid.UUID
	Chat                database.Chat
	WorkerID            uuid.UUID
	StatusNotifications <-chan StatusNotification
	RequestHeader       http.Header
	DB                  database.Store
	Logger              slog.Logger
}

SubscribeFnParams carries the state that the enterprise SubscribeFn implementation needs from the OSS Subscribe preamble.

type ToolResultStatusConflictError added in v2.33.0

type ToolResultStatusConflictError struct {
	ActualStatus database.ChatStatus
}

ToolResultStatusConflictError indicates the chat is not in the requires_action state expected for tool result submission.

func (*ToolResultStatusConflictError) Error added in v2.33.0

type ToolResultValidationError added in v2.33.0

type ToolResultValidationError struct {
	Message string
	Detail  string
}

ToolResultValidationError indicates the submitted tool results failed validation (e.g. missing, duplicate, or unexpected IDs, or invalid JSON output).

func (*ToolResultValidationError) Error added in v2.33.0

func (e *ToolResultValidationError) Error() string

type UsageLimitExceededError

type UsageLimitExceededError struct {
	LimitMicros    int64
	ConsumedMicros int64
	PeriodEnd      time.Time
}

UsageLimitExceededError indicates the user has exceeded their chat spend limit.

func (*UsageLimitExceededError) Error

func (e *UsageLimitExceededError) Error() string

type ValidateFunc

type ValidateFunc func(ctx context.Context, workspaceID uuid.UUID) (uuid.UUID, error)

ValidateFunc returns the current agent ID for a workspace.

Directories

Path Synopsis
Package chaterror classifies provider/runtime failures into stable, user-facing chat error payloads.
Package chaterror classifies provider/runtime failures into stable, user-facing chat error payloads.
Package chatretry provides retry logic for transient LLM provider errors.
Package chatretry provides retry logic for transient LLM provider errors.
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL