Documentation
¶
Index ¶
- Constants
- Variables
- func BuildLastInjectedContext(parts []codersdk.ChatMessagePart) (pqtype.NullRawMessage, error)
- func BuildSingleChatMessageInsertParams(chatID uuid.UUID, role database.ChatMessageRole, content pqtype.NullRawMessage, ...) database.InsertChatMessagesParams
- func CollectContextPartsFromMessages(ctx context.Context, logger slog.Logger, messages []database.ChatMessage, ...) ([]codersdk.ChatMessagePart, error)
- func ComputeUsagePeriodBounds(now time.Time, period codersdk.ChatUsageLimitPeriod) (start, end time.Time)
- func FilterContextParts(parts []codersdk.ChatMessagePart, keepEmptyContextFiles bool) []codersdk.ChatMessagePart
- func FilterContextPartsToLatestAgent(parts []codersdk.ChatMessagePart) []codersdk.ChatMessagePart
- func PlanningOverlayPrompt() string
- func ResolveUsageLimitStatus(ctx context.Context, db database.Store, userID uuid.UUID, ...) (*codersdk.ChatUsageLimitStatus, error)
- func SanitizePromptText(s string) string
- type AgentConnFunc
- type Config
- type CreateOptions
- type DialFunc
- type DialResult
- type EditMessageOptions
- type EditMessageResult
- type PromoteQueuedOptions
- type PromoteQueuedResult
- type SendMessageBusyBehavior
- type SendMessageOptions
- type SendMessageResult
- type Server
- func (p *Server) ArchiveChat(ctx context.Context, chat database.Chat) error
- func (p *Server) Close() error
- func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.Chat, error)
- func (p *Server) DeleteQueued(ctx context.Context, chatID uuid.UUID, queuedMessageID int64) error
- func (p *Server) EditMessage(ctx context.Context, opts EditMessageOptions) (EditMessageResult, error)
- func (p *Server) InterruptChat(ctx context.Context, chat database.Chat) database.Chat
- func (p *Server) PromoteQueued(ctx context.Context, opts PromoteQueuedOptions) (PromoteQueuedResult, error)
- func (p *Server) ProposeChatTitle(ctx context.Context, chat database.Chat) (string, error)
- func (p *Server) PublishDiffStatusChange(ctx context.Context, chatID uuid.UUID) error
- func (p *Server) PublishTitleChange(chat database.Chat)
- func (p *Server) RefreshStatus(ctx context.Context, chatID uuid.UUID) error
- func (p *Server) RegenerateChatTitle(ctx context.Context, chat database.Chat) (database.Chat, error)
- func (p *Server) RenameChatTitle(ctx context.Context, chat database.Chat, newTitle string) (updated database.Chat, wrote bool, err error)
- func (p *Server) SendMessage(ctx context.Context, opts SendMessageOptions) (SendMessageResult, error)
- func (p *Server) SubmitToolResults(ctx context.Context, opts SubmitToolResultsOptions) error
- func (p *Server) Subscribe(ctx context.Context, chatID uuid.UUID, requestHeader http.Header, ...) ([]codersdk.ChatStreamEvent, <-chan codersdk.ChatStreamEvent, func(), bool)
- func (p *Server) UnarchiveChat(ctx context.Context, chat database.Chat) error
- type StatusNotification
- type SubmitToolResultsOptions
- type SubscribeFn
- type SubscribeFnParams
- type ToolResultStatusConflictError
- type ToolResultValidationError
- type UsageLimitExceededError
- type ValidateFunc
Constants ¶
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 )
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.
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.
const ExploreSubagentOverlayPrompt = `` /* 313-byte string literal not displayed */
ExploreSubagentOverlayPrompt contains Explore-mode instructions for delegated child chats.
const MaxQueueSize = 20
MaxQueueSize is the maximum number of queued user messages per chat.
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 ¶
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") )
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.
var ErrManualTitleRegenerationInProgress = xerrors.New(
"manual title regeneration already in progress",
)
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 ¶
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 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 ¶
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 ¶
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 ¶
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) CreateChat ¶
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 ¶
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
ProposeChatTitle generates a title suggestion from the chat's visible messages without persisting it.
func (*Server) PublishDiffStatusChange ¶
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
PublishTitleChange broadcasts a title_change event for the given chat.
func (*Server) RefreshStatus ¶
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) UnarchiveChat ¶
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
func (e *ToolResultStatusConflictError) Error() string
type ToolResultValidationError ¶ added in v2.33.0
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 ¶
UsageLimitExceededError indicates the user has exceeded their chat spend limit.
func (*UsageLimitExceededError) Error ¶
func (e *UsageLimitExceededError) Error() string
Source Files
¶
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
|
|