chatd

package
v2.35.1 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: AGPL-3.0 Imports: 80 Imported by: 0

Documentation

Overview

Package chatd implements the internal chat service used by Agents.

Provider configuration glossary

This package uses AI Provider language for new provider configuration code:

  • AI Provider: a database-backed LLM provider configuration stored in ai_providers. It is the source of truth for Agents provider identity.
  • Legacy Chat Provider: the pre-migration chat-specific provider source. Legacy rows only exist as migration input during the stack.
  • Provider Type: the provider implementation family stored in ai_providers.type, such as openai, anthropic, azure, bedrock, google, openai-compat, openrouter, and vercel.
  • Provider Name: the unique instance identifier stored in ai_providers.name. It is not the implementation family.
  • Model Config: an Agents model selection record. In the target state it references one concrete AI Provider by ID.
  • Provider-scoped AI Provider Key: an administrator-managed credential in ai_provider_keys, attached to one AI Provider.
  • User AI Provider Key: a user-owned credential attached to one user and one AI Provider.
  • BYOK: the deployment-level AI Bridge policy that controls whether user keys may be written or used. Disabling BYOK does not delete stored user keys.
  • AI Bridge: the product area that introduced AI provider records. Agents consume the same records through chatd, but this package does not define the full AI Bridge runtime roadmap.

Model configs should use provider IDs for identity. Provider types choose runtime implementation details. Provider names are instance identifiers for administrators and APIs.

When BYOK is enabled, a user key for the selected provider takes precedence over provider-scoped keys. When BYOK is disabled, chatd ignores user keys and uses provider-scoped keys only.

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 is the canonical path of the synthetic empty context-file part that legacy chats used to mark skill-only workspace-agent context. New turns no longer emit it; it is retained as the canonical value so historical-message handling and the chatopenai chain-mode tests stay in sync.

View Source
const ChatPersonalModelOverrideKeyPrefix = "chat_personal_model_override:"

ChatPersonalModelOverrideKeyPrefix is the user config key prefix for chat personal model overrides. Values under this prefix should be parsed with ParseChatPersonalModelOverride so malformed values use one fallback.

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.
When no workspace is attached, use available non-workspace tools first. Do not create a workspace by default.
Reuse existing chat and workspace context. Do not clone repositories already present in the workspace. Treat injected <workspace-context> files, including AGENTS.md, as read; re-read only for exact current contents or suspected changes.
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>

<version-control-safety>
Before committing or pushing in a Git repository, check the current branch and push target.
Do not commit directly to default or protected branches, including main, master, trunk, or the repository's remote default branch, unless the user explicitly confirms after you identify the exact branch.
Do not push when the target would update a default or protected branch unless the user explicitly confirms. Before asking for confirmation, warn that the push bypasses the normal feature branch or pull request workflow and state the exact remote ref that would be updated.
Do not run plain git push while checked out on a default or protected branch. When pushing after explicit confirmation, use an explicit refspec.
If the user asks you to commit or push from a default or protected branch without that confirmation, create and switch to a feature branch first. If a branch name is not obvious, choose a concise descriptive branch name that follows the repository's conventions, or ask when the choice is material.
Never treat the original request as confirmation. Confirmation must be separate and must name the exact protected branch or accept the exact branch you named.
</version-control-safety>

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

<workspace-template-selection>
When no workspace is attached and you need to create one:
- Call list_templates with concise search terms from the user's task, then follow its ` + chattool.NextStepField + `: use the recommended template, or ask the user to choose when none is recommended.
- Call read_template only when you need parameter or preset details before create_workspace.
</workspace-template-selection>

<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, do not create one as the first action merely because you are planning.
First use the conversation, provider tools such as web_search when available, configured external MCP tools, and template metadata when they are sufficient.
Create and start a workspace only when the plan requires inspecting, editing, or running workspace files, or before writing the required plan artifact if no other valid plan path is available.
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>

` + subagentOrchestrationPromptBlock

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 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")
	// 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 ErrArchiveRequiresRootChat = xerrors.New(
	"chat archive state can only be changed on the root chat",
)

ErrArchiveRequiresRootChat is returned by Server.ArchiveChat and Server.UnarchiveChat when the supplied chat is a child chat. Archive state changes must always target the root chat so the whole family flips together.

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")
View Source
var ErrSubagentWaitTimeout = xerrors.New("timed out waiting for delegated subagent completion")

ErrSubagentWaitTimeout is returned by awaitSubagentCompletion when the wait deadline elapses before the subagent reaches a terminal status. The agent is still working and the wait can be retried.

Functions

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

BuildSingleUserChatMessageInsertParams creates batch insert params for one user message, requiring an apiKeyID for AI Gateway attribution. BuildSingleChatMessageInsertParams creates batch insert params for one non-user message using the shared chat message builder.

func BuildSingleUserChatMessageInsertParams added in v2.34.0

func BuildSingleUserChatMessageInsertParams(
	chatID uuid.UUID,
	apiKeyID string,
	content pqtype.NullRawMessage,
	visibility database.ChatMessageVisibility,
	modelConfigID uuid.UUID,
	contentVersion int16,
	createdBy uuid.UUID,
) database.InsertChatMessagesParams

func ChatPersonalModelOverrideKey added in v2.34.0

func ChatPersonalModelOverrideKey(
	overrideContext codersdk.ChatPersonalModelOverrideContext,
) string

ChatPersonalModelOverrideKey returns the user config key for a chat personal model override context. Values stored at the returned key should use ParseChatPersonalModelOverride so malformed values fall back safely.

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

func ValidateAIGatewayProviderModel added in v2.34.1

func ValidateAIGatewayProviderModel(provider database.AIProvider, model string) error

ValidateAIGatewayProviderModel rejects slash-namespaced models on OpenRouter-like providers typed as openai, where the provider type strips the vendor prefix.

func WaitUntilIdleForTest added in v2.34.0

func WaitUntilIdleForTest(server *Server)

WaitUntilIdleForTest waits for background chat work tracked by the server to finish without shutting the server down. Tests use this to assert final database state only after asynchronous chat processing has completed. Close waits for the same tracked work, but also stops the server.

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
	// StreamPartsDialer dials remote stream parts. Nil uses the local
	// in-process channel dialer for every stream.
	StreamPartsDialer              StreamPartsDialer
	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
	StopWorkspace                  chattool.StopWorkspaceFn
	ProviderAPIKeys                chatprovider.ProviderAPIKeys
	AllowBYOK                      bool
	AllowBYOKSet                   bool
	AlwaysEnableDebugLogs          bool
	WebpushDispatcher              webpush.Dispatcher
	UsageTracker                   *workspacestats.UsageTracker
	Clock                          quartz.Clock
	AIBridgeTransportFactory       *atomic.Pointer[aibridge.TransportFactory]
	AIGatewayRoutingEnabled        bool
	Experiments                    codersdk.Experiments

	PrometheusRegistry prometheus.Registerer

	// OIDCTokenSource resolves the calling user's OIDC access
	// token for MCP servers configured with auth_type=user_oidc.
	// May be nil if the deployment has no OIDC provider; servers
	// using user_oidc will then send no Authorization header.
	OIDCTokenSource mcpclient.UserOIDCTokenSource

	NotificationsEnqueuer notifications.Enqueuer
	Auditor               *atomic.Pointer[audit.Auditor]
}

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
	APIKeyID           string
	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
	APIKeyID        string
	// ModelConfigID, when non-zero, overrides the model used for
	// the replacement user message. When set to uuid.Nil the
	// original message's model is preserved.
	ModelConfigID uuid.UUID
}

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 LocalStreamPartsDialerConfig added in v2.35.0

type LocalStreamPartsDialerConfig struct {
	Buffer *messagepartbuffer.Buffer
	Logger slog.Logger
}

LocalStreamPartsDialerConfig configures an in-process stream parts dialer.

type ParsedChatPersonalModelOverride added in v2.34.0

type ParsedChatPersonalModelOverride struct {
	Mode          codersdk.ChatPersonalModelOverrideMode
	ModelConfigID uuid.UUID
	Malformed     bool
}

ParsedChatPersonalModelOverride is a parsed personal model override value. When Malformed is true, Mode is the provided default and ModelConfigID is uuid.Nil.

func ParseChatPersonalModelOverride added in v2.34.0

func ParseChatPersonalModelOverride(
	raw string,
	defaultMode codersdk.ChatPersonalModelOverrideMode,
) ParsedChatPersonalModelOverride

ParseChatPersonalModelOverride parses a stored personal model override. Empty values return defaultMode without marking the value malformed. Malformed values return defaultMode, uuid.Nil, and Malformed true.

type PromoteQueuedOptions

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

PromoteQueuedOptions controls queued-message promotion.

type PromoteQueuedResult

type PromoteQueuedResult struct {
	// PromotedMessage is the inserted user message. For a chat that
	// was running at promote time, the insertion is deferred to the
	// worker's auto-promote and PromotedMessage is the zero value.
	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
	APIKeyID      string
	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(ps pubsub.Pubsub, cfg Config) *Server

New creates a new chat processor with the required pubsub dependency. 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 root chat and every child in its family through the chatstate state machine. The transition is atomic over the whole family: either every member is archived or none is. The state machine only permits archive from the idle / error execution states (W, E0, E1); active members cause a state conflict that the HTTP handler maps to a client error.

Child chats must not be archived independently. ArchiveChat rejects them with ErrArchiveRequiresRootChat so callers cannot silently break the parent-implies-child archive invariant.

func (*Server) Close

func (p *Server) Close() error

Close stops the processor and waits for it to finish.

func (*Server) ContextResources added in v2.35.0

func (server *Server) ContextResources(
	ctx context.Context,
	chat database.Chat,
) ([]codersdk.ChatContextResource, error)

ContextResources returns the chat's pinned context resource list (metadata only). It is read-only and intended for the single-chat GET handler; list and watch payloads omit this detail to stay lightweight.

The returned list is the chat's full pinned inventory (instruction files, skills, and MCP configs/servers), each stamped with its per-resource status so the UI can explain why a resource was dropped from the prompt instead of silently omitting it.

func (*Server) CreateChat

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

CreateChat creates a chat with its initial history through chatstate.CreateChat. The new chat starts in `running` status per the chat execution state model. Ownership hints wake chat workers.

func (*Server) DeleteQueued

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

DeleteQueued removes a queued user message through the chatstate state machine. Stream side effects are handled by chat:update consumers.

func (*Server) EditMessage

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

EditMessage replaces an earlier user message and discards the active-history suffix through chatstate.EditMessage. Model-config override validation and usage-limit admission run in the same transaction as the state-machine transition.

func (*Server) GenerateChatTitleAsync added in v2.35.0

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

GenerateChatTitleAsync fires a best-effort, automatic title-generation pass for a freshly created chat. It is intended to be called from the chat-creation endpoint right after the chat and its initial user message are persisted.

The work runs in a tracked goroutine with a context detached from the request but bound to the server: it neither blocks the HTTP response nor is canceled when the request completes, and Close cancels it instead of blocking on the title timeout while a provider is unreachable. It resolves the chat's model and provider keys, then delegates to maybeGenerateChatTitle, which only acts on the first user turn (see titleInput) and is otherwise a no-op. Errors are logged and swallowed.

func (*Server) HydrateAndMarkChatsDirty added in v2.35.0

func (p *Server) HydrateAndMarkChatsDirty(ctx context.Context, tx database.Store, agentID uuid.UUID, aggregateHash []byte, snapshotError string, now time.Time) (func(), error)

HydrateAndMarkChatsDirty implements agentapi.ContextDirtyMarker. It runs inside the PushContextState transaction: it stamps the pushed snapshot hash on chats for the agent that have not been hydrated yet (no dirty event), then flips already-pinned chats whose hash differs to dirty. It returns a callback that publishes the dirty watch events; the caller invokes it only after the transaction commits, and the callback is a no-op when nothing transitioned to dirty.

The pinned hash on dirtied chats is intentionally left unchanged; the refresh endpoint re-pins it.

func (*Server) InterruptChat

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

InterruptChat interrupts execution through the chatstate.Interrupt transition. Active runs land in `interrupting`; requires-action chats synthesize cancellation messages and return to running.

Returns the post-transition chat and an error so callers can map state conflicts deliberately. Idle chats return a chatstate.ErrTransitionNotAllowed wrapper.

func (*Server) PromoteQueued

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

PromoteQueued promotes a queued message through the chatstate state machine. From running / interrupting states the state machine transitions the chat to `interrupting` so the worker can drain the in-flight generation before promoting; from idle / error / requires action states it inserts the user message into history synchronously.

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) ReconcileInvalidStateChat added in v2.35.0

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

ReconcileInvalidStateChat recovers a chat stuck in an invalid execution-state combination by running the chatstate.ReconcileInvalidState transition. The chat lands in an error state (E0/E1); queued messages are preserved and pending dynamic-tool calls are closed with synthetic cancellations.

Returns the post-transition chat. When the chat is not actually in an invalid state the transition returns a wrapped chatstate.ErrTransitionNotAllowed; a missing chat returns chatstate.ErrChatNotFound. Callers map these to deliberate HTTP responses.

func (*Server) RefreshChatContext added in v2.35.0

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

RefreshChatContext re-pins a chat to its agent's latest context snapshot (hash, error, and resource bodies) and clears the dirty marker. It backs PUT /chats/{chat}/context (no body). A chat with no bound agent, or whose agent has no snapshot, simply has its pinned hash, dirty marker, and resources cleared.

The snapshot read and the re-pin run in one repeatable-read transaction so a concurrent push cannot land between them and leave the chat pinned to a stale hash with the dirty marker cleared.

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 admits a user message through the chatstate.SendMessage transition. Pre-transition admission policy (usage limit, plan-mode metadata update, MCP server ID update, model-config resolution, queue cap) runs inside the same chatstate transaction via the transactional store so everything commits or rolls back together.

func (*Server) ServeStreamPartsAuthorized added in v2.35.0

func (p *Server) ServeStreamPartsAuthorized(rw http.ResponseWriter, r *http.Request, chat database.Chat) error

ServeStreamPartsAuthorized serves the internal episode-selected parts stream for an already authorized chat route.

func (*Server) Start added in v2.34.0

func (p *Server) Start() *Server

Start runs the background acquire/wake loop that picks up pending chats and processes them. Callers that want a passive server (e.g. tests) can skip this call; heartbeat, stream janitor, and stale recovery still run.

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, returning the chat to running through the chatstate state machine. Validation runs inside the same transaction as the transition so the assistant message and pending tool calls cannot drift between reads.

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) SubscribeAuthorized added in v2.34.0

func (p *Server) SubscribeAuthorized(
	ctx context.Context,
	chat database.Chat,
	requestHeader http.Header,
	afterMessageID int64,
) (
	[]codersdk.ChatStreamEvent,
	<-chan codersdk.ChatStreamEvent,
	func(),
	bool,
)

SubscribeAuthorized subscribes an already-authorized chat to stream updates.

func (*Server) UnarchiveChat

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

UnarchiveChat unarchives a root chat and every child in its family through the chatstate state machine. Like ArchiveChat the cascade is atomic; ChildChat unarchive attempts are rejected with ErrArchiveRequiresRootChat.

type StreamPart added in v2.35.0

type StreamPart struct {
	HistoryVersion    int64
	GenerationAttempt int64
	Seq               int64
	Role              codersdk.ChatMessageRole
	Part              codersdk.ChatMessagePart
}

StreamPart is a live preview part scoped to one chat history episode.

func StreamPartFromEvent added in v2.35.0

func StreamPartFromEvent(event codersdk.ChatStreamEvent) (StreamPart, bool)

type StreamPartsDialInput added in v2.35.0

type StreamPartsDialInput struct {
	ChatID        uuid.UUID
	WorkerID      uuid.UUID
	RequestHeader http.Header
}

StreamPartsDialInput carries the metadata needed to dial a parts source.

type StreamPartsDialer added in v2.35.0

type StreamPartsDialer func(ctx context.Context, input StreamPartsDialInput) (StreamPartsSession, error)

StreamPartsDialer dials an episode-aware source of message parts.

func NewLocalStreamPartsDialer added in v2.35.0

func NewLocalStreamPartsDialer(cfg LocalStreamPartsDialerConfig) StreamPartsDialer

NewLocalStreamPartsDialer returns a dialer that streams message parts through in-process channels while using the same stream serving loop as WebSockets.

type StreamPartsJSONSession added in v2.35.0

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

func NewStreamPartsJSONSession added in v2.35.0

func NewStreamPartsJSONSession(ctx context.Context, conn *websocket.Conn) *StreamPartsJSONSession

func (StreamPartsJSONSession) Close added in v2.35.0

func (s StreamPartsJSONSession) Close() error

func (StreamPartsJSONSession) Parts added in v2.35.0

func (s StreamPartsJSONSession) Parts() <-chan StreamPart

func (StreamPartsJSONSession) SelectEpisode added in v2.35.0

func (s StreamPartsJSONSession) SelectEpisode(ctx context.Context, historyVersion, generationAttempt int64) error

type StreamPartsSession added in v2.35.0

type StreamPartsSession interface {
	SelectEpisode(ctx context.Context, historyVersion, generationAttempt int64) error
	Parts() <-chan StreamPart
	Close() error
}

StreamPartsSession streams message parts for selected episodes.

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 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 chatretry provides retry logic for transient LLM provider errors.
Package chatretry provides retry logic for transient LLM provider errors.
Package chatstate owns the durable execution-state transitions for the chatd subsystem.
Package chatstate owns the durable execution-state transitions for the chatd subsystem.
internal
Package messagepartbuffer stores the transient message-part stream that a chat worker emits before those parts are committed to durable chat history.
Package messagepartbuffer stores the transient message-part stream that a chat worker emits before those parts are committed to durable chat history.

Jump to

Keyboard shortcuts

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