chatd

package
v2.32.5 Latest Latest
Warning

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

Go to latest
Published: May 30, 2026 License: AGPL-3.0 Imports: 54 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 DefaultSystemPrompt = `` /* 4978-byte string literal not displayed */

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

View Source
const MaxQueueSize = 20

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

Variables

View Source
var (
	// 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")
)
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 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 ResolveUsageLimitStatus

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

ResolveUsageLimitStatus resolves the current usage-limit status for userID.

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
	WebpushDispatcher              webpush.Dispatcher
	UsageTracker                   *workspacestats.UsageTracker
	Clock                          quartz.Clock
}

Config configures a chat processor.

type CreateOptions

type CreateOptions struct {
	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
	SystemPrompt       string
	InitialUserContent []codersdk.ChatMessagePart
	MCPServerIDs       []uuid.UUID
	Labels             database.StringMap
}

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
	ModelConfigID   *uuid.UUID
}

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
	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) 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) 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) 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) 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 publishes created events for each affected chat so watching clients see every chat that reappeared.

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 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 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