package codersdk import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "mime" "net/http" "net/url" "slices" "strconv" "strings" "time" "github.com/google/uuid" "github.com/invopop/jsonschema" "golang.org/x/xerrors" "github.com/coder/websocket" "github.com/coder/websocket/wsjson" ) // ChatCompactionThresholdKeyPrefix scopes per-model chat compaction // threshold settings. const ChatCompactionThresholdKeyPrefix = "chat_compaction_threshold_pct:" // Defaults for the chat limits in [ChatConfig]. const ( // DefaultChatMaxStepsPerTurn is the default maximum number of steps in // a chat turn. DefaultChatMaxStepsPerTurn = 1200 // DefaultChatMaxGenerationRetries is the default maximum number of // consecutive retries after a model generation fails with a transient // error. DefaultChatMaxGenerationRetries = 25 // DefaultChatMaxQueuedMessagesPerChat is the default maximum number of // messages that can be queued in a chat. DefaultChatMaxQueuedMessagesPerChat = 20 // DefaultChatMaxAttachmentsPerChat is the default maximum number of // files linked to a chat. DefaultChatMaxAttachmentsPerChat = 50 // DefaultChatMaxPromptBytes is the default maximum size in bytes of the // deployment system prompt, the plan mode instructions, and each // user's custom prompt. DefaultChatMaxPromptBytes = 128 * 1024 // DefaultChatMaxConcurrentRecordingUploads is the default maximum // number of virtual desktop recordings that each Coder server stores // at the same time. DefaultChatMaxConcurrentRecordingUploads = 25 ) // MaxChatFileSizeBytes is the upload-endpoint cap for chat // attachments. const MaxChatFileSizeBytes = 10 * 1024 * 1024 // Inline MCP server declaration caps. Clients can validate before sending. const ( MaxInlineMCPServers = 5 MaxInlineMCPServersBytes = 24 * 1024 MaxInlineMCPServerSlugBytes = 32 MaxInlineMCPServerURLBytes = 2048 MaxInlineMCPServerHeaders = 16 MaxInlineMCPServerHeaderNameBytes = 128 MaxInlineMCPServerHeaderValueBytes = 8 * 1024 MaxInlineMCPServerToolFilters = 64 MaxInlineMCPServerToolNameBytes = 128 ) // AnthropicInlineImageCapBytes is Anthropic's documented per-image // wire limit; the same cap applies to Bedrock-hosted Claude. Other // providers have no documented per-image cap. const AnthropicInlineImageCapBytes = 5 * 1024 * 1024 // ChatAttachmentMediaType is a media type that is allowed for durable // chat file storage. The set is intentionally narrow; byte-level // classification and inline-render rules live alongside the enforcement // helpers in coderd/chatfiles. type ChatAttachmentMediaType string const ( ChatAttachmentMediaTypeApplicationJSON ChatAttachmentMediaType = "application/json" ChatAttachmentMediaTypeApplicationPDF ChatAttachmentMediaType = "application/pdf" ChatAttachmentMediaTypeImageGIF ChatAttachmentMediaType = "image/gif" ChatAttachmentMediaTypeImageJPEG ChatAttachmentMediaType = "image/jpeg" ChatAttachmentMediaTypeImagePNG ChatAttachmentMediaType = "image/png" ChatAttachmentMediaTypeImageSVG ChatAttachmentMediaType = "image/svg+xml" ChatAttachmentMediaTypeImageWEBP ChatAttachmentMediaType = "image/webp" ChatAttachmentMediaTypeTextCSV ChatAttachmentMediaType = "text/csv" ChatAttachmentMediaTypeTextMarkdown ChatAttachmentMediaType = "text/markdown" ChatAttachmentMediaTypeTextPlain ChatAttachmentMediaType = "text/plain" ) // AllChatAttachmentMediaTypes enumerates every durable chat attachment // media type in the same lexical order the guts-generated TypeScript // list uses, so the frontend file picker and the backend enforcement // map stay in lockstep. Add new values in sorted order. var AllChatAttachmentMediaTypes = []ChatAttachmentMediaType{ ChatAttachmentMediaTypeApplicationJSON, ChatAttachmentMediaTypeApplicationPDF, ChatAttachmentMediaTypeImageGIF, ChatAttachmentMediaTypeImageJPEG, ChatAttachmentMediaTypeImagePNG, ChatAttachmentMediaTypeImageSVG, ChatAttachmentMediaTypeImageWEBP, ChatAttachmentMediaTypeTextCSV, ChatAttachmentMediaTypeTextMarkdown, ChatAttachmentMediaTypeTextPlain, } // CompactionThresholdKey returns the user-config key for a specific // model configuration's compaction threshold. func CompactionThresholdKey(modelID uuid.UUID) string { return ChatCompactionThresholdKeyPrefix + modelID.String() } // ChatStatus represents the status of a chat. type ChatStatus string const ( ChatStatusWaiting ChatStatus = "waiting" ChatStatusRunning ChatStatus = "running" ChatStatusError ChatStatus = "error" ChatStatusRequiresAction ChatStatus = "requires_action" ChatStatusInterrupting ChatStatus = "interrupting" // ChatStatusPaused: a turn finished at a queued message under edit. // The chat continues when that edit ends or the message is promoted // or deleted; new messages queue behind it. ChatStatusPaused ChatStatus = "paused" ) // ChatClientType indicates whether a chat was created from the // web UI or programmatically via the API. type ChatClientType string const ( ChatClientTypeUI ChatClientType = "ui" ChatClientTypeAPI ChatClientType = "api" ) // ChatTitleSource is where a chat's title came from. type ChatTitleSource string const ( // ChatTitleSourceFallback is derived from the first prompt, or is the // default title of a chat created without one. ChatTitleSourceFallback ChatTitleSource = "fallback" // ChatTitleSourceGenerated is written by automatic title generation. ChatTitleSourceGenerated ChatTitleSource = "generated" // ChatTitleSourceUser is supplied by the caller at creation or by // rename. ChatTitleSourceUser ChatTitleSource = "user" ) // Chat represents a chat session with an AI agent. type Chat struct { ID uuid.UUID `json:"id" format:"uuid"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` OwnerID uuid.UUID `json:"owner_id" format:"uuid"` OwnerUsername string `json:"owner_username,omitempty"` OwnerName string `json:"owner_name,omitempty"` WorkspaceID *uuid.UUID `json:"workspace_id,omitempty" format:"uuid"` ProjectID *uuid.UUID `json:"project_id,omitempty" format:"uuid"` BuildID *uuid.UUID `json:"build_id,omitempty" format:"uuid"` AgentID *uuid.UUID `json:"agent_id,omitempty" format:"uuid"` ParentChatID *uuid.UUID `json:"parent_chat_id,omitempty" format:"uuid"` RootChatID *uuid.UUID `json:"root_chat_id,omitempty" format:"uuid"` LastModelConfigID uuid.UUID `json:"last_model_config_id" format:"uuid"` LastReasoningEffort *string `json:"last_reasoning_effort,omitempty"` Title string `json:"title"` // TitleSource is where Title came from. A title write applies only when // the current source ranks the same as or lower than the incoming one, // in the order fallback, generated, user. TitleSource ChatTitleSource `json:"title_source"` // TitleUpdatedAt orders title changes. Title writes do not change // UpdatedAt. TitleUpdatedAt time.Time `json:"title_updated_at" format:"date-time"` Status ChatStatus `json:"status"` PlanMode ChatPlanMode `json:"plan_mode,omitempty"` LastError *ChatError `json:"last_error,omitempty"` LastTurnSummary *string `json:"last_turn_summary"` // Summary is the persisted whole-chat summary, generated in the background. // It is nil until the first summary has been produced. Summary *string `json:"summary"` // DiffStatus is the primary pull request. It is the ref with the // most recent git report. DiffStatus *ChatDiffStatus `json:"diff_status,omitempty"` // DiffStatuses lists every ref the chat tracks. The order is // stable and follows the first report of each ref. DiffStatus // marks the primary. DiffStatuses []ChatDiffStatus `json:"diff_statuses,omitempty"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` Archived bool `json:"archived"` // Shared is true when this chat's root chat has explicit user or group ACL entries. Shared bool `json:"shared"` PinOrder int32 `json:"pin_order"` MCPServerIDs []uuid.UUID `json:"mcp_server_ids" format:"uuid"` Labels map[string]string `json:"labels"` Files []ChatFileMetadata `json:"files,omitempty"` // HasUnread is true when assistant messages exist beyond // the owner's read cursor, which updates on stream // connect and disconnect and via UpdateChatRequest.Read. HasUnread bool `json:"has_unread"` // Context reports the chat's pinned workspace-context state and // whether it has drifted from the agent's latest pushed snapshot. // Nil when the chat has no pinned context yet. Context *ChatContext `json:"context,omitempty"` // QueuedForCapacity reports that the chat is waiting for a concurrent // agent slot. Single-chat reads derive it; list responses leave it false. QueuedForCapacity bool `json:"queued_for_capacity,omitempty"` Warnings []string `json:"warnings,omitempty"` ClientType ChatClientType `json:"client_type"` // InlineMCPServers lists the inline MCP servers declared on the chat, // without headers. Only the single-chat GET sets it. // Experimental. InlineMCPServers []InlineMCPServer `json:"inline_mcp_servers,omitempty"` // Children holds child (subagent) chats nested under this root // chat. Always initialized to an empty slice so the JSON field // is present as []. Child chats cannot create their own // subagents, so nesting depth is capped at 1 and this slice is // always empty for child chats. Children []Chat `json:"children"` } // ChatProject groups related chats in an organization. type ChatProject struct { ID uuid.UUID `json:"id" format:"uuid"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` OwnerID uuid.UUID `json:"owner_id" format:"uuid"` // Name is a display label and is not unique; ID identifies the project. Name string `json:"name"` Description string `json:"description"` // Icon is a URL, typically an emoji image under /emojis, or empty for the // default folder glyph. Icon string `json:"icon"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` Permissions ChatProjectPermissions `json:"permissions"` } // ChatProjectPermissions are the actions the caller may take on a project. type ChatProjectPermissions struct { Update bool `json:"update"` Delete bool `json:"delete"` Share bool `json:"share"` } // CreateChatProjectRequest creates a chat project in the organization named // by the route. type CreateChatProjectRequest struct { Name string `json:"name" validate:"required"` Description string `json:"description"` Icon string `json:"icon,omitempty"` } // UpdateChatProjectRequest updates a chat project. type UpdateChatProjectRequest struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` Icon *string `json:"icon,omitempty"` } // ChatProjectRole is the access a project ACL grants. type ChatProjectRole string const ( // ChatProjectRoleUse can start chats and read and write memories. ChatProjectRoleUse ChatProjectRole = "use" // ChatProjectRoleAdmin can also edit the project and its instructions, // which every chat in the project gets as system prompt, and share it, but // not delete it. ChatProjectRoleAdmin ChatProjectRole = "admin" ChatProjectRoleDeleted ChatProjectRole = "" ) type ChatProjectUser struct { MinimalUser Role ChatProjectRole `json:"role" enums:"use,admin"` } type ChatProjectGroup struct { Group Role ChatProjectRole `json:"role" enums:"use,admin"` } // ChatProjectACL lists who a chat project is shared with and who owns // it. The Everyone group's ID is the organization ID. type ChatProjectACL struct { // Owner is the project owner, who is never listed in Users. Owner MinimalUser `json:"owner"` Users []ChatProjectUser `json:"users"` Groups []ChatProjectGroup `json:"groups"` } // UpdateChatProjectACL changes only the listed principals. // ChatProjectRoleDeleted removes an entry. type UpdateChatProjectACL struct { UserRoles map[string]ChatProjectRole `json:"user_roles,omitempty"` GroupRoles map[string]ChatProjectRole `json:"group_roles,omitempty"` } // ChatProjectInstructions are instructions added to the system prompt of // every chat in a project, except chats whose owner can no longer use the // project. A project without instructions has an empty Instructions // string and nil UpdatedBy and UpdatedAt. type ChatProjectInstructions struct { ProjectID uuid.UUID `json:"project_id" format:"uuid"` Instructions string `json:"instructions"` // UpdatedBy is the user who last changed the instructions. It is nil // when the instructions are unset or the user was deleted. UpdatedBy *MinimalUser `json:"updated_by"` UpdatedAt *time.Time `json:"updated_at" format:"date-time"` } // UpdateChatProjectInstructionsRequest sets a project's instructions. Use // DeleteChatProjectInstructions to clear them. type UpdateChatProjectInstructionsRequest struct { Instructions string `json:"instructions"` } // ChatProjectMemory is a durable memory shared by chats in a project. type ChatProjectMemory struct { ID uuid.UUID `json:"id" format:"uuid"` ProjectID uuid.UUID `json:"project_id" format:"uuid"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` Name string `json:"name"` Description string `json:"description"` Body string `json:"body"` CreatedBy uuid.UUID `json:"created_by" format:"uuid"` CreatedByUsername string `json:"created_by_username"` CreatedAt time.Time `json:"created_at" format:"date-time"` } type CreateChatProjectMemoryRequest struct { Name string `json:"name" validate:"required"` Description string `json:"description" validate:"required"` Body string `json:"body" validate:"required"` } // ChatContext reports a chat's pinned workspace context and whether it has // drifted from the agent's latest pushed snapshot. The chat stays usable // when dirty; refreshing re-pins it to the latest snapshot. type ChatContext struct { // Dirty is true when the agent's latest snapshot hash differs from the // chat's pinned hash. Dirty bool `json:"dirty"` // DirtySince is when drift was first detected; nil when not dirty. DirtySince *time.Time `json:"dirty_since,omitempty" format:"date-time"` // Error is the snapshot-level error copied from the pinned snapshot // (empty when healthy). Error string `json:"error,omitempty"` // Resources is the chat's pinned workspace-context inventory, // metadata only (no bodies). It is populated only on the single-chat // GET response; list and watch // payloads leave it nil to stay lightweight. Resources []ChatContextResource `json:"resources,omitempty"` } // ChatContextResourceKind classifies a pinned workspace-context resource. type ChatContextResourceKind string const ( ChatContextResourceKindInstructionFile ChatContextResourceKind = "instruction_file" ChatContextResourceKindSkill ChatContextResourceKind = "skill" ChatContextResourceKindMCPConfig ChatContextResourceKind = "mcp_config" ChatContextResourceKindMCPServer ChatContextResourceKind = "mcp_server" ChatContextResourceKindACPHarness ChatContextResourceKind = "acp_harness" ) // ChatContextResource is one resource in the chat's pinned workspace-context // inventory. It is metadata only; bodies are omitted. Reported // only on the single-chat GET response. type ChatContextResource struct { // Source is the resource locator: the canonical file path for an // instruction file, the skill directory for a skill, the file path for // an MCP config, the server name for an MCP server, or an ACP harness slug. Source string `json:"source"` Kind ChatContextResourceKind `json:"kind"` // SourcePath is the configuration directory for an ACP harness. SourcePath string `json:"source_path,omitempty"` // SizeBytes is the original payload size in bytes. SizeBytes int64 `json:"size_bytes"` // SkillName and SkillDescription are populated only for skill kinds. SkillName string `json:"skill_name,omitempty"` SkillDescription string `json:"skill_description,omitempty"` // Tools lists the tools exposed by an MCP server. Populated only for the // mcp_server kind; nil otherwise. Tools []ChatContextTool `json:"tools,omitempty"` // Status is the resource's health. Non-ok resources (invalid, unreadable, // oversize, excluded) are still reported so the UI can surface why a // resource was dropped from the prompt instead of silently omitting it; // their body-specific fields (skill name, tools) are empty. Status ChatContextResourceStatus `json:"status"` // Error explains a non-ok Status; empty when healthy. May also carry a // non-fatal warning when Status is ok. Error string `json:"error,omitempty"` } // ChatContextResourceStatus is the health of a pinned context resource, // mirroring the agent resolver's per-resource status. type ChatContextResourceStatus string const ( ChatContextResourceStatusOK ChatContextResourceStatus = "ok" ChatContextResourceStatusOversize ChatContextResourceStatus = "oversize" ChatContextResourceStatusUnreadable ChatContextResourceStatus = "unreadable" ChatContextResourceStatusInvalid ChatContextResourceStatus = "invalid" ChatContextResourceStatusExcluded ChatContextResourceStatus = "excluded" ) // ChatContextTool is one tool exposed by a pinned MCP server, reported on the // single-chat GET response. Metadata only; the input schema is omitted. type ChatContextTool struct { // Name is the tool name with the `__` prefix the agent adds // stripped, so it reads as the server exposes it. Name string `json:"name"` // Description is the tool's human-readable summary; may be empty. Description string `json:"description,omitempty"` } // ChatFileMetadata contains lightweight metadata about a file // associated with a chat, excluding the file content itself. type ChatFileMetadata struct { ID uuid.UUID `json:"id" format:"uuid"` OwnerID uuid.UUID `json:"owner_id" format:"uuid"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` Name string `json:"name"` MimeType string `json:"mime_type"` SizeBytes int64 `json:"size_bytes"` CreatedAt time.Time `json:"created_at" format:"date-time"` } // ChatACPTranscriptMessage is a workspace-local ACP message returned by // acp_wait_agent without database message metadata. type ChatACPTranscriptMessage struct { Role ChatMessageRole `json:"role"` Content []ChatMessagePart `json:"content"` } // ChatMessage represents a single message in a chat. type ChatMessage struct { ID int64 `json:"id"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` CreatedBy *uuid.UUID `json:"created_by,omitempty" format:"uuid"` ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` CreatedAt time.Time `json:"created_at" format:"date-time"` Role ChatMessageRole `json:"role"` Content []ChatMessagePart `json:"content,omitempty"` Usage *ChatMessageUsage `json:"usage,omitempty"` // QueuedMessageID is the ID of the queued message this message was // promoted from. It matches ChatQueuedMessage.ID in the response that // queued the message. It is nil when the message was not promoted from // the queue (edits create a new message without it) or when a server // version that did not record the link created it. QueuedMessageID *int64 `json:"queued_message_id,omitempty"` } // ChatMessageUsage contains token usage information for a chat message. type ChatMessageUsage struct { InputTokens *int64 `json:"input_tokens,omitempty"` OutputTokens *int64 `json:"output_tokens,omitempty"` TotalTokens *int64 `json:"total_tokens,omitempty"` // ReasoningTokens counts reasoning, which output_tokens already // includes, so do not add the two. ReasoningTokens *int64 `json:"reasoning_tokens,omitempty"` CacheCreationTokens *int64 `json:"cache_creation_tokens,omitempty"` CacheReadTokens *int64 `json:"cache_read_tokens,omitempty"` ContextLimit *int64 `json:"context_limit,omitempty"` } // ChatMessageRole represents the role of a chat message sender. type ChatMessageRole string // ChatMessageRole enums. const ( ChatMessageRoleSystem ChatMessageRole = "system" ChatMessageRoleUser ChatMessageRole = "user" ChatMessageRoleAssistant ChatMessageRole = "assistant" ChatMessageRoleTool ChatMessageRole = "tool" ) // ChatMessagePartType represents a structured message part type. type ChatMessagePartType string const ( ChatMessagePartTypeText ChatMessagePartType = "text" ChatMessagePartTypeReasoning ChatMessagePartType = "reasoning" ChatMessagePartTypeToolCall ChatMessagePartType = "tool-call" ChatMessagePartTypeToolResult ChatMessagePartType = "tool-result" ChatMessagePartTypeSource ChatMessagePartType = "source" ChatMessagePartTypeFile ChatMessagePartType = "file" ChatMessagePartTypeFileReference ChatMessagePartType = "file-reference" ChatMessagePartTypeContextFile ChatMessagePartType = "context-file" ChatMessagePartTypeSkill ChatMessagePartType = "skill" ChatMessagePartTypeWorkspaceFileReference ChatMessagePartType = "workspace-file-reference" // ChatMessagePartTypeSkillInvocation is a skill the user attached to // a message. The server loads the skill body when the message is sent // and renders it into the user's prompt. ChatMessagePartTypeSkillInvocation ChatMessagePartType = "skill-invocation" // ChatMessagePartTypeHookContext is model context injected into a user // prompt by a lifecycle hook. It is included in model prompt assembly // and stripped from every client-facing conversion; the server rejects // it in client-submitted content. ChatMessagePartTypeHookContext ChatMessagePartType = "hook-context" // ChatMessagePartTypeHookNotice is a user-facing lifecycle hook notice, // either attached to a prompt or in its own row. It is excluded from model // prompts and rejected in client-submitted content. ChatMessagePartTypeHookNotice ChatMessagePartType = "hook-notice" ) // AllChatMessagePartTypes returns all known ChatMessagePartType values. func AllChatMessagePartTypes() []ChatMessagePartType { return []ChatMessagePartType{ ChatMessagePartTypeText, ChatMessagePartTypeReasoning, ChatMessagePartTypeToolCall, ChatMessagePartTypeToolResult, ChatMessagePartTypeSource, ChatMessagePartTypeFile, ChatMessagePartTypeFileReference, ChatMessagePartTypeContextFile, ChatMessagePartTypeSkill, ChatMessagePartTypeWorkspaceFileReference, ChatMessagePartTypeSkillInvocation, ChatMessagePartTypeHookContext, ChatMessagePartTypeHookNotice, } } // ChatMessagePart is a structured chunk of a chat message. // // WARNING: This type is both an API wire type and a database // persistence format. Its JSON layout is stored in the // chat_messages.content column. Field additions, renames, type // changes, and omitempty behavior all affect backward-compatible // deserialization of stored rows. Treat changes to this struct // with the same care as a database migration. // // The variants struct tag declares which discriminated-union // variants include each field in the generated TypeScript. Bare // name = required, ? suffix = optional. Fields without a variants // tag are excluded from the generated union. See // scripts/apitypings/main.go for the codegen that reads these. // // omitempty rules (enforced by TestChatMessagePartVariantTags): // - If a field is required (no ? suffix) in ANY variant, it // must NOT use omitempty. Go would silently drop zero values // that TypeScript expects to always be present. // - If a field is optional (? suffix) in ALL of its variants, // it MUST use omitempty. Sending zero values for fields that // the frontend does not expect adds noise to the wire format // and wastes space in persisted chat_messages rows. type ChatMessagePart struct { Type ChatMessagePartType `json:"type"` Text string `json:"text" variants:"text,reasoning,hook-notice"` ToolCallID string `json:"tool_call_id,omitempty" variants:"tool-call?,tool-result?,source?"` ToolName string `json:"tool_name,omitempty" variants:"tool-call?,tool-result?"` MCPServerConfigID uuid.NullUUID `json:"mcp_server_config_id,omitempty" format:"uuid" variants:"tool-call?,tool-result?"` Args json.RawMessage `json:"args,omitempty" variants:"tool-call?"` ArgsDelta string `json:"args_delta,omitempty" variants:"tool-call?"` // ParsedCommands holds parsed programs from an execute tool call's // shell command, one entry per simple command in source order. Each // entry is [program] or [program, arg] where arg is the first non-flag // positional argument. Program names are normalized to their base // name (e.g. /usr/bin/go becomes go). Only populated when ToolName // is "execute" and the command parses successfully; nil otherwise. ParsedCommands [][]string `json:"parsed_commands,omitempty" variants:"tool-call?"` // Result is the tool output, which can be any JSON value. Result json.RawMessage `json:"result,omitempty" variants:"tool-result?"` ResultDelta string `json:"result_delta,omitempty" variants:"tool-result?"` ReasoningDelta string `json:"reasoning_delta,omitempty" variants:"tool-result?"` ResultReset bool `json:"result_reset,omitempty" variants:"tool-result?"` IsError bool `json:"is_error,omitempty" variants:"tool-result?"` IsMedia bool `json:"is_media,omitempty" variants:"tool-result?"` SourceID string `json:"source_id,omitempty" variants:"source?"` URL string `json:"url" variants:"source"` Title string `json:"title,omitempty" variants:"source?"` MediaType string `json:"media_type" variants:"file"` Name string `json:"name,omitempty" variants:"file?"` // Data holds an inline file's bytes, which JSON carries as a base64 string. Data []byte `json:"data,omitempty" swaggertype:"string" format:"byte" variants:"file?"` FileID uuid.NullUUID `json:"file_id,omitempty" format:"uuid" variants:"file?"` FileName string `json:"file_name" variants:"file-reference"` StartLine int `json:"start_line" variants:"file-reference"` EndLine int `json:"end_line" variants:"file-reference"` // The code content from the diff that was commented on. Content string `json:"content" variants:"file-reference"` // ProviderMetadata holds provider-specific response metadata // (e.g. Anthropic cache control hints) as raw JSON. Internal // only: stripped by db2sdk before API responses. ProviderMetadata json.RawMessage `json:"provider_metadata,omitempty" typescript:"-" swaggerignore:"true"` // ProviderIdentity records the provider that produced a reasoning // part, because a model config's provider can change after the // message was written. Internal only: stripped before API responses. ProviderIdentity string `json:"provider_identity,omitempty" typescript:"-" swaggerignore:"true"` // ProviderModel records the model that produced a reasoning part, // because a model config's model can change after the message was // written. Internal only: stripped before API responses. ProviderModel string `json:"provider_model,omitempty" typescript:"-" swaggerignore:"true"` // ProviderCredential records whether a reasoning part was produced // with the deployment's credential or the user's personal API key, // because encrypted reasoning only decrypts for the organization // behind that credential. Internal only: stripped before API responses. ProviderCredential string `json:"provider_credential,omitempty" typescript:"-" swaggerignore:"true"` // ReasoningEpoch records how many times the provider had rejected the // chat's encrypted reasoning from this provider, model, and credential // before this reasoning part was produced. A step that produced no // reasoning after a rejection records its epoch, provider, model, and // credential on its first part instead. Internal only: stripped before // API responses. ReasoningEpoch int `json:"reasoning_epoch,omitempty" typescript:"-" swaggerignore:"true"` // ProviderExecuted indicates the tool call was executed by // the provider (e.g. Anthropic computer use). ProviderExecuted bool `json:"provider_executed,omitempty" variants:"tool-call?,tool-result?"` // HookRewritten indicates that a lifecycle hook replaced model-proposed tool input. HookRewritten bool `json:"hook_rewritten,omitempty" variants:"tool-call?"` // Narration marks assistant text that the model labeled as commentary // on its work rather than its answer. Only providers that label their // output set it. Narration bool `json:"narration,omitempty" variants:"text?"` // CreatedAt is the timestamp this part carries. The semantics // depend on the part type: for tool-call and tool-result parts // it is the time the call was emitted or the result was // produced (tool duration is the result's created_at minus the // call's created_at); for reasoning parts it is the time // reasoning started streaming. CreatedAt *time.Time `json:"created_at,omitempty" format:"date-time" variants:"tool-call?,tool-result?,reasoning?"` // CompletedAt is the time a reasoning part finished streaming, // so reasoning duration can be computed as completed_at minus // created_at. For interrupted reasoning, this is the // interruption time. Absent when reasoning timestamp data was // not recorded (e.g. messages persisted before this feature // was added). CompletedAt *time.Time `json:"completed_at,omitempty" format:"date-time" variants:"reasoning?"` // ContextFilePath is the absolute path of a file loaded into // the LLM context (e.g. an AGENTS.md instruction file). ContextFilePath string `json:"context_file_path" variants:"context-file"` // ContextFileContent holds the file content sent to the LLM. // Internal only: stripped before API responses to keep // payloads small. The backend reads it when building the // prompt via partsToMessageParts. ContextFileContent string `json:"context_file_content,omitempty" typescript:"-"` // ContextFileTruncated indicates the file exceeded the 64KiB // instruction file limit and was truncated. ContextFileTruncated bool `json:"context_file_truncated,omitempty" variants:"context-file?"` // ContextFileAgentID is the workspace agent that provided // this context file. Used to detect when the agent changes // (e.g. workspace rebuilt) so instruction files can be // re-persisted with fresh content. ContextFileAgentID uuid.NullUUID `json:"context_file_agent_id,omitempty" format:"uuid" variants:"context-file?"` // ContextFileOS is the operating system of the workspace // agent. Internal only: used during prompt expansion so // the LLM knows the OS even on turns where InsertSystem // is not called. ContextFileOS string `json:"context_file_os,omitempty" typescript:"-"` // ContextFileDirectory is the working directory of the // workspace agent. Internal only: same purpose as // ContextFileOS. ContextFileDirectory string `json:"context_file_directory,omitempty" typescript:"-"` // SkillName is the kebab-case name of the skill. SkillName string `json:"skill_name" variants:"skill,skill-invocation"` // SkillSource is where the invoked skill of a skill-invocation // part is stored. SkillSource ChatSkillSource `json:"skill_source" variants:"skill-invocation"` // SkillBody is the invoked skill body loaded when the message was // sent, so past turns keep the instructions the model received. // Internal only: stripped before API responses. SkillBody string `json:"skill_body,omitempty" typescript:"-" swaggerignore:"true"` // SkillDescription is the short description from the skill's // SKILL.md frontmatter. SkillDescription string `json:"skill_description,omitempty" variants:"skill?"` // SkillDir is the absolute path to the skill directory inside // the workspace filesystem. Internal only: used by // read_skill/read_skill_file tools to locate skill files, and // shown to the model for an invoked workspace skill so it can // run the skill's scripts. SkillDir string `json:"skill_dir,omitempty" typescript:"-"` // SkillWorkspaceID is the workspace whose filesystem holds the // SkillDir of an invoked workspace skill. Internal only: the // prompt shows SkillDir only while the chat stays bound to this // workspace. SkillWorkspaceID uuid.UUID `json:"skill_workspace_id,omitzero" format:"uuid" typescript:"-" swaggerignore:"true"` // ContextFileSkillMetaFile is the basename of the skill // meta file (e.g. "SKILL.md") at the time of persistence. // Internal only: restored on subsequent turns so the // read_skill tool uses the correct filename even when the // agent configured a non-default value. ContextFileSkillMetaFile string `json:"context_file_skill_meta_file,omitempty" typescript:"-"` // WorkspaceFilePath is the absolute path of a workspace upload. // The bytes live on the workspace filesystem; only metadata is // persisted on the message. WorkspaceFilePath string `json:"workspace_file_path" variants:"workspace-file-reference"` // WorkspaceFileName is the sanitized basename of a workspace upload. WorkspaceFileName string `json:"workspace_file_name" variants:"workspace-file-reference"` // WorkspaceFileSize is the byte size of a workspace upload. WorkspaceFileSize int64 `json:"workspace_file_size" variants:"workspace-file-reference"` // WorkspaceFileMediaType is the best-effort declared MIME type. WorkspaceFileMediaType string `json:"workspace_file_media_type,omitempty" variants:"workspace-file-reference?"` // WorkspaceFileWorkspaceID identifies the workspace whose // filesystem holds the uploaded bytes. References are only // readable while the chat stays bound to that workspace. WorkspaceFileWorkspaceID uuid.UUID `json:"workspace_file_workspace_id" format:"uuid" variants:"workspace-file-reference"` } // StripInternal removes internal-only fields that must not be // sent to API clients. Call before publishing via REST or SSE. // // Note: ArgsDelta, ResultDelta, ReasoningDelta, and ResultReset are // intentionally preserved. They are streaming-only fields consumed by the // frontend via WebSocket message_part events. ArgsDelta is produced by // chatloop; ResultDelta, ReasoningDelta, and ResultReset are produced by // the advisor streaming callbacks. func (p *ChatMessagePart) StripInternal() { p.ProviderMetadata = nil p.ProviderIdentity = "" p.ProviderModel = "" p.ProviderCredential = "" p.ReasoningEpoch = 0 if p.FileID.Valid { p.Data = nil } p.ContextFileContent = "" p.ContextFileOS = "" p.ContextFileDirectory = "" p.SkillDir = "" p.SkillWorkspaceID = uuid.Nil p.ContextFileSkillMetaFile = "" p.SkillBody = "" } // ChatMessageText builds a text chat message part. func ChatMessageText(text string) ChatMessagePart { return ChatMessagePart{Type: ChatMessagePartTypeText, Text: text} } // ChatMessageReasoning builds a reasoning chat message part. func ChatMessageReasoning(text string) ChatMessagePart { return ChatMessagePart{Type: ChatMessagePartTypeReasoning, Text: text} } // ChatMessageToolCall builds a tool-call chat message part. func ChatMessageToolCall(toolCallID, toolName string, args json.RawMessage) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeToolCall, ToolCallID: toolCallID, ToolName: toolName, Args: args, } } // ChatMessageToolResult builds a tool-result chat message part. // The isMedia flag marks the result as carrying binary media content // (e.g. a screenshot) so that round-trip reconstruction preserves // the media type instead of sending raw base64 as text tokens. func ChatMessageToolResult(toolCallID, toolName string, result json.RawMessage, isError bool, isMedia bool) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeToolResult, ToolCallID: toolCallID, ToolName: toolName, Result: result, IsError: isError, IsMedia: isMedia, } } // ChatMessageFile builds a file chat message part. func ChatMessageFile(fileID uuid.UUID, mediaType string, name string) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeFile, FileID: uuid.NullUUID{UUID: fileID, Valid: true}, MediaType: mediaType, Name: name, } } // ChatMessageFileReference builds a file-reference chat message part. func ChatMessageFileReference(fileName string, startLine, endLine int, content string) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeFileReference, FileName: fileName, StartLine: startLine, EndLine: endLine, Content: content, } } // ChatMessageWorkspaceFileReference builds a workspace-file-reference // chat message part. The bytes live on the filesystem of workspace // workspaceID at path; only metadata is persisted on the message. func ChatMessageWorkspaceFileReference(workspaceID uuid.UUID, path, name string, size int64, mediaType string) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeWorkspaceFileReference, WorkspaceFilePath: path, WorkspaceFileName: name, WorkspaceFileSize: size, WorkspaceFileMediaType: mediaType, WorkspaceFileWorkspaceID: workspaceID, } } // ChatMessageSkillInvocation builds a skill-invocation chat message // part without a body; the server loads the body at send time. func ChatMessageSkillInvocation(source ChatSkillSource, name string) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeSkillInvocation, SkillSource: source, SkillName: name, } } // ChatMessageSource builds a source chat message part. func ChatMessageSource(sourceID, sourceURL, title string) ChatMessagePart { return ChatMessagePart{ Type: ChatMessagePartTypeSource, SourceID: sourceID, URL: sourceURL, Title: title, } } // ChatInputPartType represents an input part type for user chat input. type ChatInputPartType string const ( ChatInputPartTypeText ChatInputPartType = "text" ChatInputPartTypeFile ChatInputPartType = "file" ChatInputPartTypeFileReference ChatInputPartType = "file-reference" ChatInputPartTypeWorkspaceFileReference ChatInputPartType = "workspace-file-reference" ChatInputPartTypeSkillInvocation ChatInputPartType = "skill-invocation" ) // ChatSkillSource identifies where an invoked skill is stored. type ChatSkillSource string const ( ChatSkillSourcePersonal ChatSkillSource = "personal" ChatSkillSourceOrganization ChatSkillSource = "org" ChatSkillSourceProject ChatSkillSource = "project" ChatSkillSourceWorkspace ChatSkillSource = "workspace" ) // ChatInputPart is a single user input part for creating a chat. type ChatInputPart struct { Type ChatInputPartType `json:"type"` Text string `json:"text,omitempty"` FileID uuid.UUID `json:"file_id,omitempty" format:"uuid"` // The following fields are only set when Type is // ChatInputPartTypeFileReference. FileName string `json:"file_name,omitempty"` StartLine int `json:"start_line,omitempty"` EndLine int `json:"end_line,omitempty"` // The code content from the diff that was commented on. Content string `json:"content,omitempty"` // The following fields are only set when Type is // ChatInputPartTypeWorkspaceFileReference. WorkspaceFilePath string `json:"workspace_file_path,omitempty"` WorkspaceFileName string `json:"workspace_file_name,omitempty"` WorkspaceFileSize int64 `json:"workspace_file_size,omitempty"` WorkspaceFileMediaType string `json:"workspace_file_media_type,omitempty"` // WorkspaceFileWorkspaceID is the workspace the file was uploaded // to, as returned by the upload endpoint. It must match the chat's // currently bound workspace. WorkspaceFileWorkspaceID uuid.UUID `json:"workspace_file_workspace_id,omitempty" format:"uuid"` // The following fields are only set when Type is // ChatInputPartTypeSkillInvocation. The server loads the skill // as the chat owner when the message is sent. SkillSource ChatSkillSource `json:"skill_source,omitempty"` SkillName string `json:"skill_name,omitempty"` } // SubmitToolResultsRequest is the body for POST /chats/{id}/tool-results. type SubmitToolResultsRequest struct { Results []ToolResult `json:"results"` } // ToolResult is the client's response to a dynamic tool call. type ToolResult struct { ToolCallID string `json:"tool_call_id"` // Output is the tool output, which can be any JSON value. Output json.RawMessage `json:"output"` IsError bool `json:"is_error"` } // MaxChatTitleRunes is the longest title accepted at creation or rename, // counted in Unicode code points after trimming. const MaxChatTitleRunes = 200 // CreateChatRequest is the request to create a new chat. type CreateChatRequest struct { OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` // OwnerID makes another user the chat owner. It defaults to the // caller. The chat runs with the owner's credentials, so setting it // requires site-wide authority over that user. OwnerID *uuid.UUID `json:"owner_id,omitempty" format:"uuid"` // Content is the initial user message. It is optional: when // empty, the chat is created idle with no initial user message // and generation starts with the first message POSTed to // /chats/{chat}/messages. Content []ChatInputPart `json:"content"` // Title, when set, is stored as the user title and automatic title // generation does not run. It is trimmed and must then be non-empty // and at most 200 Unicode code points (MaxChatTitleRunes), else the // request fails with 400. When omitted, the title is derived from the // first prompt and may later be replaced by a generated title. Title *string `json:"title,omitempty"` SystemPrompt string `json:"system_prompt,omitempty"` WorkspaceID *uuid.UUID `json:"workspace_id,omitempty" format:"uuid"` ProjectID *uuid.UUID `json:"project_id,omitempty" format:"uuid"` ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` MCPServerIDs []uuid.UUID `json:"mcp_server_ids,omitempty" format:"uuid"` Labels map[string]string `json:"labels,omitempty"` // UnsafeDynamicTools declares client-executed tools that the // LLM can invoke. This API is highly experimental and highly // subject to change. UnsafeDynamicTools []DynamicTool `json:"unsafe_dynamic_tools,omitempty"` // InlineMCPServers declares MCP servers by value on this chat, next // to the org-configured servers selected by MCPServerIDs. Experimental. InlineMCPServers []InlineMCPServerRequest `json:"inline_mcp_servers,omitempty"` PlanMode ChatPlanMode `json:"plan_mode,omitempty"` ClientType ChatClientType `json:"client_type,omitempty"` } // InlineMCPServerRequest declares a streamable HTTP MCP server by value on // one chat. Headers are never returned. Header values are encrypted at // rest when database encryption is configured. type InlineMCPServerRequest struct { Slug string `json:"slug"` URL string `json:"url"` Headers map[string]string `json:"headers,omitempty"` ToolAllowList []string `json:"tool_allow_list,omitempty"` ToolDenyList []string `json:"tool_deny_list,omitempty"` AllowInSubagents bool `json:"allow_in_subagents,omitempty"` ForwardCoderHeaders bool `json:"forward_coder_headers,omitempty"` } // InlineMCPServer is the redacted view of an inline MCP server. type InlineMCPServer struct { ID uuid.UUID `json:"id" format:"uuid"` Slug string `json:"slug"` // URL is empty unless the chat owner makes the request. URL string `json:"url"` HasCustomHeaders bool `json:"has_custom_headers"` ToolAllowList []string `json:"tool_allow_list"` ToolDenyList []string `json:"tool_deny_list"` AllowInSubagents bool `json:"allow_in_subagents"` ForwardCoderHeaders bool `json:"forward_coder_headers"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` } // UpdateChatRequest is the request to update a chat. type UpdateChatRequest struct { // Title, when set, is stored as the user title even when its text is // unchanged, so a generated title never replaces it afterwards. It is // validated like CreateChatRequest.Title. Title *string `json:"title,omitempty"` Archived *bool `json:"archived,omitempty"` WorkspaceID *uuid.UUID `json:"workspace_id,omitempty" format:"uuid"` // ProjectID moves a root chat, and with it its subagent chats. // - nil: no change. // - uuid.Nil: remove the chat from its project. // - other: move the chat into that project. The chat's owner must own // the project or have it shared with them, and the project must be // in the chat's organization. // The server returns 409 while the chat or a subagent chat is not idle. ProjectID *uuid.UUID `json:"project_id,omitempty" format:"uuid"` // PinOrder controls the chat's pinned state and position. // - nil: no change to pin state. // - 0: unpin the chat. // - >0 (chat is unpinned): pin the chat, appending it to // the end of the pinned list. The specific value is // ignored; the server assigns the next available position. // - >0 (chat is already pinned): move the chat to the // requested position, shifting neighbors as needed. The // value is clamped to [1, pinned_count]. PinOrder *int32 `json:"pin_order,omitempty"` Labels *map[string]string `json:"labels,omitempty"` // Read moves the owner's read cursor, which drives HasUnread. // - nil: no change. // - true: mark every existing message as read. // - false: clear the cursor so the chat reads as unread again. // // The cursor is owner-scoped, so only the chat owner may set this. // Opening a chat's stream marks it read, so marking the chat the // owner is currently viewing as unread does not persist. Read *bool `json:"read,omitempty"` // PlanMode switches the chat's persistent plan mode. // nil: no change, ptr to "plan": enable, ptr to "": clear. PlanMode *ChatPlanMode `json:"plan_mode,omitempty"` } // ChatBusyBehavior controls when a message sent to a busy chat reaches // the model. A waiting chat receives the message at once for every value. type ChatBusyBehavior string const ( // ChatBusyBehaviorSteer delivers the message before the next model // call. This is the default. ChatBusyBehaviorSteer ChatBusyBehavior = "steer" // ChatBusyBehaviorQueue delivers the message after the active turn // ends. ChatBusyBehaviorQueue ChatBusyBehavior = "queue" ) // ChatPlanMode represents the persistent plan mode state of a chat. type ChatPlanMode string const ( // ChatPlanModePlan activates plan mode for the chat. ChatPlanModePlan ChatPlanMode = "plan" ) // CreateChatMessageRequest is the request to add a message to a chat. type CreateChatMessageRequest struct { Content []ChatInputPart `json:"content"` ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` MCPServerIDs *[]uuid.UUID `json:"mcp_server_ids,omitempty" format:"uuid"` // InlineMCPServers replaces the inline MCP servers. // nil: no change, empty: remove all. InlineMCPServers *[]InlineMCPServerRequest `json:"inline_mcp_servers,omitempty"` // BusyBehavior controls when the message reaches the agent if the // chat is busy. "steer", the default, delivers it before the next // model call. "queue" delivers it after the active turn ends. BusyBehavior ChatBusyBehavior `json:"busy_behavior,omitempty"` // PlanMode switches the chat's persistent plan mode. // nil: no change, ptr to "plan": enable, ptr to "": clear. PlanMode *ChatPlanMode `json:"plan_mode,omitempty"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` } // EditChatMessageRequest is the request to edit a user message in a chat. type EditChatMessageRequest struct { Content []ChatInputPart `json:"content"` // ModelConfigID, when set, overrides the model used for the // replacement user message and the assistant turn that follows. // When nil the original message's model is preserved. ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` // MCPServerIDs, when set, replaces the chat's MCP server selection // before the replacement turn runs. When nil the current selection // is preserved. MCPServerIDs *[]uuid.UUID `json:"mcp_server_ids,omitempty" format:"uuid"` } // CreateChatMessageResponse is the response from adding a message to a chat. type CreateChatMessageResponse struct { Message *ChatMessage `json:"message,omitempty"` // Messages contains all user-visible messages inserted by the send, in // insertion order. A send to an errored or paused chat can also insert // queued messages: a steer send inserts the queued steer messages // before the new message, and a queue send to an errored chat inserts // the steer lane, or the first queue message when that lane is empty. // Clients must upsert the full batch. Messages []ChatMessage `json:"messages,omitempty"` QueuedMessage *ChatQueuedMessage `json:"queued_message,omitempty"` Queued bool `json:"queued"` Warnings []string `json:"warnings,omitempty"` } // EditChatMessageResponse is the response from editing a message in a chat. type EditChatMessageResponse struct { Message ChatMessage `json:"message"` // Messages holds every user-visible message inserted by the edit, in // insertion order. Hook-generated suffix messages may follow Message, // so clients must upsert the full batch. Messages []ChatMessage `json:"messages,omitempty"` // DeletedMessageIDs holds the IDs of previously visible messages the // edit removed, including stale hook notices from the edited turn. // Clients should drop them from local caches. DeletedMessageIDs []int64 `json:"deleted_message_ids,omitempty"` Warnings []string `json:"warnings,omitempty"` } // UploadChatFileResponse is the response from uploading a chat file. type UploadChatFileResponse struct { ID uuid.UUID `json:"id" format:"uuid"` } // ChatFileDownloadURLResponse contains a short-lived URL for downloading a chat file. type ChatFileDownloadURLResponse struct { URL string `json:"url" format:"uri"` ExpiresAt time.Time `json:"expires_at" format:"date-time"` SHA256 string `json:"sha256"` SizeBytes int64 `json:"size_bytes"` Name string `json:"name"` MimeType string `json:"mime_type"` } // UploadChatWorkspaceFileResponse describes a file uploaded to a // chat's workspace filesystem. type UploadChatWorkspaceFileResponse struct { // Path is the absolute path of the file on the workspace. Path string `json:"path"` // Name is the final basename of the uploaded file. Name string `json:"name"` // Size is the number of bytes written to the workspace. Size int64 `json:"size"` // MediaType is the client-declared content type for display. MediaType string `json:"media_type"` // WorkspaceID is the workspace whose filesystem received the // bytes. Message parts referencing this upload must carry it. WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"` } // ChatMessagesResponse contains the messages and queued messages for a chat. type ChatMessagesResponse struct { Messages []ChatMessage `json:"messages"` // QueuedMessages lists the queued messages in delivery order: steer // messages first, then queue messages. QueuedMessages []ChatQueuedMessage `json:"queued_messages"` HasMore bool `json:"has_more"` // TurnStartID is the ID of the user prompt that starts the turn // containing the page's oldest message. Omitted for after_id-only polls // and when no prompt is at or before that message. TurnStartID *int64 `json:"turn_start_id,omitempty"` } // ChatPrompt is a single user-authored prompt in a chat, returned by // GET /api/v2/chats/{chat}/prompts. The text field contains // the concatenated text payload of the underlying chat message; non-text // parts (tool calls, files, attachments) are omitted by the server. type ChatPrompt struct { ID int64 `json:"id"` Text string `json:"text"` } // ChatPromptsResponse is the payload of // GET /api/v2/chats/{chat}/prompts. Prompts are returned // newest first so the client can index directly into the slice for // up/down arrow history cycling. type ChatPromptsResponse struct { Prompts []ChatPrompt `json:"prompts"` } // ChatAttachment is a file part of a chat message, returned by // GET /api/v2/chats/{chat}/attachments. type ChatAttachment struct { MessageID int64 `json:"message_id"` // PartIndex is the file part's position in the message content the // messages endpoint returns. PartIndex int `json:"part_index"` Role ChatAttachmentRole `json:"role"` File ChatAttachmentFile `json:"file"` } // ChatAttachmentRole is the role of the message that holds an attachment. // The attachments endpoint lists only user and assistant messages. type ChatAttachmentRole string const ( ChatAttachmentRoleUser ChatAttachmentRole = "user" ChatAttachmentRoleAssistant ChatAttachmentRole = "assistant" ) // ChatAttachmentFileType is the part type of a ChatAttachmentFile, which // is always a file part. type ChatAttachmentFileType string const ChatAttachmentFileTypeFile ChatAttachmentFileType = "file" // ChatAttachmentFile holds the fields of a file part, the only part type // the attachments endpoint returns. type ChatAttachmentFile struct { Type ChatAttachmentFileType `json:"type"` MediaType string `json:"media_type"` Name string `json:"name,omitempty"` // Data holds an inline file's bytes, which JSON carries as a base64 string. Data []byte `json:"data,omitempty" swaggertype:"string" format:"byte"` // Omitted, not null, for an inline file. FileID uuid.NullUUID `json:"file_id,omitzero" format:"uuid"` } // ChatAttachmentsResponse is the payload of // GET /api/v2/chats/{chat}/attachments. Attachments are returned newest // first. type ChatAttachmentsResponse struct { Attachments []ChatAttachment `json:"attachments"` } // ChatModelProviderUnavailableReason explains why a provider cannot be used. type ChatModelProviderUnavailableReason string const ( ChatModelProviderUnavailableMissingAPIKey ChatModelProviderUnavailableReason = "missing_api_key" ChatModelProviderUnavailableFetchFailed ChatModelProviderUnavailableReason = "fetch_failed" // #nosec G101 ChatModelProviderUnavailableReasonUserAPIKeyRequired ChatModelProviderUnavailableReason = "user_api_key_required" ) // ChatUnsupportedProvider is a configured provider the Agents harness cannot // use. type ChatUnsupportedProvider struct { // Provider is the provider type, e.g. "copilot". Provider string `json:"provider"` DisplayName string `json:"display_name"` } // ChatSystemPromptResponse is the response body for the chat system prompt // configuration endpoint. type ChatSystemPromptResponse struct { SystemPrompt string `json:"system_prompt"` IncludeDefaultSystemPrompt bool `json:"include_default_system_prompt"` DefaultSystemPrompt string `json:"default_system_prompt"` } // UpdateChatSystemPromptRequest is the request body for updating the chat // system prompt configuration. type UpdateChatSystemPromptRequest struct { SystemPrompt string `json:"system_prompt"` IncludeDefaultSystemPrompt *bool `json:"include_default_system_prompt,omitempty"` } // OrganizationChatSystemPromptResponse is the response body for the // organization chat system prompt endpoint. type OrganizationChatSystemPromptResponse struct { SystemPrompt string `json:"system_prompt"` } // UpdateOrganizationChatSystemPromptRequest is the request body for updating // an organization's chat system prompt. type UpdateOrganizationChatSystemPromptRequest struct { SystemPrompt string `json:"system_prompt"` } // ChatPlanModeInstructionsResponse is the response body for the // plan mode instructions configuration endpoint. type ChatPlanModeInstructionsResponse struct { PlanModeInstructions string `json:"plan_mode_instructions"` } // UpdateChatPlanModeInstructionsRequest is the request body for // updating the plan mode instructions configuration. type UpdateChatPlanModeInstructionsRequest struct { PlanModeInstructions string `json:"plan_mode_instructions"` } // ChatModelOverrideContext identifies which chat model override context an // organization override applies to. type ChatModelOverrideContext string const ( ChatModelOverrideContextGeneral ChatModelOverrideContext = "general" ChatModelOverrideContextExplore ChatModelOverrideContext = "explore" ChatModelOverrideContextTitleGeneration ChatModelOverrideContext = "title_generation" ChatModelOverrideContextCompaction ChatModelOverrideContext = "compaction" ChatModelOverrideContextAdvisor ChatModelOverrideContext = "advisor" ChatModelOverrideContextComputerUse ChatModelOverrideContext = "computer_use" ) // Valid reports whether the override context is one of the supported values. func (c ChatModelOverrideContext) Valid() bool { switch c { case ChatModelOverrideContextGeneral, ChatModelOverrideContextExplore, ChatModelOverrideContextTitleGeneration, ChatModelOverrideContextCompaction, ChatModelOverrideContextAdvisor, ChatModelOverrideContextComputerUse: return true default: return false } } // AllChatModelOverrideContexts returns all supported override contexts. func AllChatModelOverrideContexts() []ChatModelOverrideContext { return []ChatModelOverrideContext{ ChatModelOverrideContextGeneral, ChatModelOverrideContextExplore, ChatModelOverrideContextTitleGeneration, ChatModelOverrideContextCompaction, ChatModelOverrideContextAdvisor, ChatModelOverrideContextComputerUse, } } // ChatModelOverrideResponse is the response body for one chat model override. type ChatModelOverrideResponse struct { Context ChatModelOverrideContext `json:"context"` ModelConfigID string `json:"model_config_id"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` } // ChatModelOverridesResponse is the response body for organization chat model overrides. type ChatModelOverridesResponse struct { Overrides []ChatModelOverrideResponse `json:"overrides"` } // UpdateChatModelOverrideRequest is the request body for updating the chat // model override configuration endpoint. type UpdateChatModelOverrideRequest struct { ModelConfigID string `json:"model_config_id"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` } // ChatPersonalModelOverrideContext identifies which chat context the user // personal model override applies to. type ChatPersonalModelOverrideContext string const ( ChatPersonalModelOverrideContextRoot ChatPersonalModelOverrideContext = "root" ChatPersonalModelOverrideContextGeneral ChatPersonalModelOverrideContext = "general" ChatPersonalModelOverrideContextExplore ChatPersonalModelOverrideContext = "explore" ) // ChatPersonalModelOverrideMode identifies how a user personal model override // should resolve the effective model. type ChatPersonalModelOverrideMode string const ( ChatPersonalModelOverrideModeDeploymentDefault ChatPersonalModelOverrideMode = "deployment_default" ChatPersonalModelOverrideModeChatDefault ChatPersonalModelOverrideMode = "chat_default" ChatPersonalModelOverrideModeModel ChatPersonalModelOverrideMode = "model" ) // ChatPersonalModelOverride is a resolved user personal model override. type ChatPersonalModelOverride struct { Context ChatPersonalModelOverrideContext `json:"context"` Mode ChatPersonalModelOverrideMode `json:"mode"` ModelConfigID string `json:"model_config_id"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` IsSet bool `json:"is_set"` } // ChatPersonalModelOverrideDeploymentDefaults describes the deployment-level // defaults used when a personal override selects deployment_default. type ChatPersonalModelOverrideDeploymentDefaults struct { General ChatModelOverrideResponse `json:"general"` Explore ChatModelOverrideResponse `json:"explore"` } // UserChatPersonalModelOverridesResponse is the response body for user // personal model override settings. type UserChatPersonalModelOverridesResponse struct { Enabled bool `json:"enabled"` Root ChatPersonalModelOverride `json:"root"` General ChatPersonalModelOverride `json:"general"` Explore ChatPersonalModelOverride `json:"explore"` DeploymentDefaults ChatPersonalModelOverrideDeploymentDefaults `json:"deployment_defaults"` } // UpdateUserChatPersonalModelOverrideRequest is the request body for updating // a user personal model override. type UpdateUserChatPersonalModelOverrideRequest struct { Mode ChatPersonalModelOverrideMode `json:"mode"` ModelConfigID string `json:"model_config_id"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` } // ChatPersonalModelOverridesAdminSettings describes whether users may manage // personal model override settings. type ChatPersonalModelOverridesAdminSettings struct { AllowUsers bool `json:"allow_users"` } // UpdateChatPersonalModelOverridesAdminSettingsRequest is the request body for // updating personal model override admin settings. type UpdateChatPersonalModelOverridesAdminSettingsRequest struct { AllowUsers bool `json:"allow_users"` } // UserChatCustomPrompt is the request and response body for the // user chat custom prompt configuration endpoint. type UserChatCustomPrompt struct { CustomPrompt string `json:"custom_prompt"` } // UserChatCompactionThreshold is a user's per-model chat compaction // threshold override. type UserChatCompactionThreshold struct { ModelConfigID uuid.UUID `json:"model_config_id" format:"uuid"` ThresholdPercent int32 `json:"threshold_percent"` } // UserChatCompactionThresholds wraps the user's per-model chat // compaction threshold overrides. type UserChatCompactionThresholds struct { Thresholds []UserChatCompactionThreshold `json:"thresholds"` } // UpdateUserChatCompactionThresholdRequest sets a user's per-model // chat compaction threshold override. type UpdateUserChatCompactionThresholdRequest struct { ThresholdPercent int32 `json:"threshold_percent" validate:"min=0,max=100"` } // AdvisorConfig is the deployment-wide runtime configuration for the // chat advisor. type AdvisorConfig struct { // Enabled is always true and cannot be changed. Enabled bool `json:"enabled"` // MaxUsesPerRun caps how many times the advisor can be invoked per // chat turn. 0 means unlimited. MaxUsesPerRun int `json:"max_uses_per_run"` // MaxOutputTokens caps the advisor model response tokens. 0 means // use the runtime default. MaxOutputTokens int64 `json:"max_output_tokens"` } // UpdateAdvisorConfigRequest is the request body for updating advisor runtime configuration. type UpdateAdvisorConfigRequest struct { MaxUsesPerRun int `json:"max_uses_per_run"` MaxOutputTokens int64 `json:"max_output_tokens"` } // ChatComputerUseProvider identifies the computer use tool flavor. Chatd // derives it from the API format of the client for the model that a computer // use chat runs on, so a Claude model on AWS Bedrock uses the Anthropic tool. // Each value names that client. The openai-compat client speaks the chat // completions API, which has no hosted computer tool, so its flavor is an // ordinary function tool. type ChatComputerUseProvider string const ( ChatComputerUseProviderAnthropic ChatComputerUseProvider = "anthropic" ChatComputerUseProviderOpenAI ChatComputerUseProvider = "openai" ChatComputerUseProviderOpenAICompat ChatComputerUseProvider = "openai-compat" ) // AllChatComputerUseProviders contains every ChatComputerUseProvider value. var AllChatComputerUseProviders = []ChatComputerUseProvider{ ChatComputerUseProviderAnthropic, ChatComputerUseProviderOpenAI, ChatComputerUseProviderOpenAICompat, } // Valid reports whether p is a supported computer use provider. func (p ChatComputerUseProvider) Valid() bool { return slices.Contains(AllChatComputerUseProviders, p) } // ChatDebugLoggingAdminSettings describes the runtime admin setting // that allows users to opt into chat debug logging. type ChatDebugLoggingAdminSettings struct { AllowUsers bool `json:"allow_users"` ForcedByDeployment bool `json:"forced_by_deployment"` } // UserChatDebugLoggingSettings describes whether debug logging is // active for the current user and whether the user may control it. type UserChatDebugLoggingSettings struct { DebugLoggingEnabled bool `json:"debug_logging_enabled"` UserToggleAllowed bool `json:"user_toggle_allowed"` ForcedByDeployment bool `json:"forced_by_deployment"` } // UpdateChatDebugLoggingAllowUsersRequest is the admin request to // toggle whether users may opt into chat debug logging. type UpdateChatDebugLoggingAllowUsersRequest struct { AllowUsers bool `json:"allow_users"` } // UpdateUserChatDebugLoggingRequest is the per-user request to // opt into or out of chat debug logging. type UpdateUserChatDebugLoggingRequest struct { DebugLoggingEnabled bool `json:"debug_logging_enabled"` } // ChatDebugStatus enumerates the lifecycle states shared by debug // runs and steps. These values must match the literals used in // insert/update callers. type ChatDebugStatus string const ( ChatDebugStatusInProgress ChatDebugStatus = "in_progress" ChatDebugStatusCompleted ChatDebugStatus = "completed" ChatDebugStatusError ChatDebugStatus = "error" ChatDebugStatusInterrupted ChatDebugStatus = "interrupted" ) // AllChatDebugStatuses contains every ChatDebugStatus value. // Update this when adding new constants above. var AllChatDebugStatuses = []ChatDebugStatus{ ChatDebugStatusInProgress, ChatDebugStatusCompleted, ChatDebugStatusError, ChatDebugStatusInterrupted, } // ChatDebugRunKind labels the operation that produced the debug // run. Each value corresponds to a distinct call-site in chatd. type ChatDebugRunKind string const ( ChatDebugRunKindChatTurn ChatDebugRunKind = "chat_turn" ChatDebugRunKindTitleGeneration ChatDebugRunKind = "title_generation" ChatDebugRunKindQuickgen ChatDebugRunKind = "quickgen" ChatDebugRunKindCompaction ChatDebugRunKind = "compaction" ) // AllChatDebugRunKinds contains every ChatDebugRunKind value. // Update this when adding new constants above. var AllChatDebugRunKinds = []ChatDebugRunKind{ ChatDebugRunKindChatTurn, ChatDebugRunKindTitleGeneration, ChatDebugRunKindQuickgen, ChatDebugRunKindCompaction, } // ChatDebugStepOperation labels the model interaction type for a // debug step. type ChatDebugStepOperation string const ( ChatDebugStepOperationStream ChatDebugStepOperation = "stream" ChatDebugStepOperationGenerate ChatDebugStepOperation = "generate" ) // AllChatDebugStepOperations contains every ChatDebugStepOperation // value. Update this when adding new constants above. var AllChatDebugStepOperations = []ChatDebugStepOperation{ ChatDebugStepOperationStream, ChatDebugStepOperationGenerate, } // ChatDebugRunSummary is a lightweight run entry for list endpoints. type ChatDebugRunSummary struct { ID uuid.UUID `json:"id" format:"uuid"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` Kind ChatDebugRunKind `json:"kind"` Status ChatDebugStatus `json:"status"` Provider *string `json:"provider,omitempty"` Model *string `json:"model,omitempty"` Summary map[string]any `json:"summary"` StartedAt time.Time `json:"started_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` FinishedAt *time.Time `json:"finished_at,omitempty" format:"date-time"` } // ChatDebugRun is the detailed run response returned by the run-detail // endpoint. It includes the same summary fields as ChatDebugRunSummary // along with the full step history for the run. type ChatDebugRun struct { ID uuid.UUID `json:"id" format:"uuid"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` RootChatID *uuid.UUID `json:"root_chat_id,omitempty" format:"uuid"` ParentChatID *uuid.UUID `json:"parent_chat_id,omitempty" format:"uuid"` ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` TriggerMessageID *int64 `json:"trigger_message_id,omitempty"` HistoryTipMessageID *int64 `json:"history_tip_message_id,omitempty"` Kind ChatDebugRunKind `json:"kind"` Status ChatDebugStatus `json:"status"` Provider *string `json:"provider,omitempty"` Model *string `json:"model,omitempty"` Summary map[string]any `json:"summary"` StartedAt time.Time `json:"started_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` FinishedAt *time.Time `json:"finished_at,omitempty" format:"date-time"` Steps []ChatDebugStep `json:"steps"` } // ChatDebugStep is a single step within a debug run. type ChatDebugStep struct { ID uuid.UUID `json:"id" format:"uuid"` RunID uuid.UUID `json:"run_id" format:"uuid"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` StepNumber int32 `json:"step_number"` Operation ChatDebugStepOperation `json:"operation"` Status ChatDebugStatus `json:"status"` HistoryTipMessageID *int64 `json:"history_tip_message_id,omitempty"` AssistantMessageID *int64 `json:"assistant_message_id,omitempty"` NormalizedRequest map[string]any `json:"normalized_request"` NormalizedResponse map[string]any `json:"normalized_response,omitempty"` Usage map[string]any `json:"usage,omitempty"` Attempts []map[string]any `json:"attempts"` Error map[string]any `json:"error,omitempty"` Metadata map[string]any `json:"metadata"` StartedAt time.Time `json:"started_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` FinishedAt *time.Time `json:"finished_at,omitempty" format:"date-time"` } // DefaultChatWorkspaceTTL is the default TTL for chat workspaces. // Zero means disabled; the template's own autostop setting applies. const DefaultChatWorkspaceTTL = 0 // DefaultChatAutoArchiveDays is the default auto-archive window, in // days, applied when no site config row exists. Zero disables // auto-archival. const DefaultChatAutoArchiveDays int32 = 0 // DefaultChatDebugRetentionDays is the default chat debug run retention // window, in days, applied when no site config row exists. Set the // config value to zero to disable the purge. const DefaultChatDebugRetentionDays int32 = 30 // ChatWorkspaceTTLResponse is the response for getting the chat // workspace TTL setting. type ChatWorkspaceTTLResponse struct { // WorkspaceTTLMillis is the workspace TTL in milliseconds. // Zero means disabled; the template's own autostop setting applies. WorkspaceTTLMillis int64 `json:"workspace_ttl_ms"` } // UpdateChatWorkspaceTTLRequest is the request to update the chat // workspace TTL setting. type UpdateChatWorkspaceTTLRequest struct { // WorkspaceTTLMillis is the workspace TTL in milliseconds. // Zero means disabled; the template's own autostop setting applies. WorkspaceTTLMillis int64 `json:"workspace_ttl_ms"` } // ChatRetentionDaysResponse contains the current chat retention setting. type ChatRetentionDaysResponse struct { RetentionDays int32 `json:"retention_days"` } // UpdateChatRetentionDaysRequest is a request to update the chat // retention period. type UpdateChatRetentionDaysRequest struct { RetentionDays int32 `json:"retention_days"` } // ChatDebugRetentionDaysResponse contains the current chat debug run // retention setting. type ChatDebugRetentionDaysResponse struct { DebugRetentionDays int32 `json:"debug_retention_days"` } // UpdateChatDebugRetentionDaysRequest is a request to update the chat // debug run retention period. type UpdateChatDebugRetentionDaysRequest struct { DebugRetentionDays int32 `json:"debug_retention_days"` } // ChatAutoArchiveDaysResponse contains the current chat auto-archive setting. type ChatAutoArchiveDaysResponse struct { AutoArchiveDays int32 `json:"auto_archive_days"` } // UpdateChatAutoArchiveDaysRequest is a request to update the chat // auto-archive period. type UpdateChatAutoArchiveDaysRequest struct { AutoArchiveDays int32 `json:"auto_archive_days"` } // ParseChatWorkspaceTTL parses a stored TTL string, returning the // default when the value is empty. func ParseChatWorkspaceTTL(s string) (time.Duration, error) { if s == "" { return DefaultChatWorkspaceTTL, nil } d, err := time.ParseDuration(s) if err != nil { return 0, xerrors.Errorf("invalid duration %q: %w", s, err) } if d < 0 { return 0, xerrors.New("duration must be non-negative") } return d, nil } // ChatProviderConfigSource describes how a provider entry is sourced. type ChatProviderConfigSource string const ( ChatProviderConfigSourceDatabase ChatProviderConfigSource = "database" ChatProviderConfigSourceEnvPreset ChatProviderConfigSource = "env_preset" ChatProviderConfigSourceSupported ChatProviderConfigSource = "supported" ) // ChatProviderConfig is an admin-managed provider configuration. type ChatProviderConfig struct { ID uuid.UUID `json:"id" format:"uuid"` Provider string `json:"provider"` DisplayName string `json:"display_name"` Icon string `json:"icon"` Enabled bool `json:"enabled"` HasAPIKey bool `json:"has_api_key"` CentralAPIKeyEnabled bool `json:"central_api_key_enabled"` AllowUserAPIKey bool `json:"allow_user_api_key"` AllowCentralAPIKeyFallback bool `json:"allow_central_api_key_fallback"` BaseURL string `json:"base_url,omitempty"` Source ChatProviderConfigSource `json:"source"` CreatedAt time.Time `json:"created_at,omitempty" format:"date-time"` UpdatedAt time.Time `json:"updated_at,omitempty" format:"date-time"` } // AIProviderSummary is provider metadata embedded in other API responses. type AIProviderSummary struct { ID uuid.UUID `json:"id" format:"uuid"` Type AIProviderType `json:"type"` Name string `json:"name"` DisplayName string `json:"display_name"` Icon string `json:"icon"` Enabled bool `json:"enabled"` Deleted bool `json:"deleted"` } // UserAIProviderKeyConfig is a provider summary from the current user's // perspective. It reports key presence but never returns key material. type UserAIProviderKeyConfig struct { Provider AIProviderSummary `json:"provider"` HasUserAPIKey bool `json:"has_user_api_key"` HasProviderAPIKey bool `json:"has_provider_api_key"` BYOKEnabled bool `json:"byok_enabled"` } // CreateUserAIProviderKeyRequest creates or replaces a user's API key // for an AI provider. type CreateUserAIProviderKeyRequest struct { APIKey string `json:"api_key"` } // UserChatProviderConfig is a summary of a provider that allows // user-supplied keys, as seen from the current user's perspective. type UserChatProviderConfig struct { ProviderID uuid.UUID `json:"provider_id" format:"uuid"` Provider string `json:"provider"` DisplayName string `json:"display_name"` Icon string `json:"icon"` Enabled bool `json:"enabled"` HasUserAPIKey bool `json:"has_user_api_key"` HasCentralAPIKeyFallback bool `json:"has_central_api_key_fallback"` BYOKEnabled bool `json:"byok_enabled"` } // CreateUserChatProviderKeyRequest creates or replaces a user's API key // for a provider. type CreateUserChatProviderKeyRequest struct { APIKey string `json:"api_key"` } // ChatModel is an org-scoped model configuration. type ChatModel struct { ID uuid.UUID `json:"id" format:"uuid"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` AIProviderID uuid.UUID `json:"ai_provider_id" format:"uuid"` Model string `json:"model"` DisplayName string `json:"display_name"` Enabled bool `json:"enabled"` IsDefault bool `json:"is_default"` ContextLimit int64 `json:"context_limit"` CompressionThreshold int32 `json:"compression_threshold"` ModelConfig *ChatModelCallConfig `json:"model_config,omitempty"` // ReasoningEfforts lists selectable reasoning effort values through // the model's configured maximum. ReasoningEfforts []string `json:"reasoning_efforts,omitempty"` // ComputerUseProvider names the computer use tool that Coder sends for // this model. It is omitted when computer use can't run on the model. ComputerUseProvider ChatComputerUseProvider `json:"computer_use_provider,omitempty"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` } // ChatModelACL is the access control list for an organization-scoped chat // model. Each principal includes the identity details needed to display and // manage the ACL without separate directory lookups. type ChatModelACL struct { Users []ChatUser `json:"users"` Groups []ChatGroup `json:"groups"` } // UpdateChatModelACLRequest is a sparse update of a chat model ACL. Only the // listed principals change. ChatRoleDeleted removes an entry, while an omitted // map or principal is unchanged. type UpdateChatModelACLRequest struct { UserRoles map[string]ChatRole `json:"user_roles,omitempty"` GroupRoles map[string]ChatRole `json:"group_roles,omitempty"` } // ChatModelProviderOptions contains typed provider-specific options. // // Note: Azure models use the `openai` options shape. // Note: Bedrock models use the `anthropic` options shape. type ChatModelProviderOptions struct { OpenAI *ChatModelOpenAIProviderOptions `json:"openai,omitempty"` Anthropic *ChatModelAnthropicProviderOptions `json:"anthropic,omitempty"` Google *ChatModelGoogleProviderOptions `json:"google,omitempty"` OpenAICompat *ChatModelOpenAICompatProviderOptions `json:"openaicompat,omitempty"` OpenRouter *ChatModelOpenRouterProviderOptions `json:"openrouter,omitempty"` Vercel *ChatModelVercelProviderOptions `json:"vercel,omitempty"` } // ChatModelOpenAIProviderOptions configures OpenAI provider behavior. type ChatModelOpenAIProviderOptions struct { Include []string `json:"include,omitempty" description:"Model names to include in discovery" hidden:"true"` Instructions *string `json:"instructions,omitempty" description:"System-level instructions prepended to the conversation" hidden:"true"` LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"` LogProbs *bool `json:"log_probs,omitempty" description:"Whether to return log probabilities of output tokens" hidden:"true"` TopLogProbs *int64 `json:"top_log_probs,omitempty" description:"Number of most likely tokens to return log probabilities for" hidden:"true"` MaxToolCalls *int64 `json:"max_tool_calls,omitempty" description:"Maximum number of tool calls per response"` ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty" description:"Whether the model may make multiple tool calls in parallel"` User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"` ReasoningSummary *string `json:"reasoning_summary,omitempty" description:"Controls whether reasoning tokens are summarized in the response" enum:"auto,concise,detailed"` MaxCompletionTokens *int64 `json:"max_completion_tokens,omitempty" description:"Upper bound on tokens the model may generate"` TextVerbosity *string `json:"text_verbosity,omitempty" description:"Controls the verbosity of the text response" enum:"low,medium,high"` Prediction map[string]any `json:"prediction,omitempty" description:"Predicted output content to speed up responses" hidden:"true"` Store *bool `json:"store,omitempty" description:"Whether to store the response on OpenAI for later retrieval via the API and dashboard logs"` Metadata map[string]any `json:"metadata,omitempty" description:"Arbitrary metadata to attach to the request" hidden:"true"` PromptCacheKey *string `json:"prompt_cache_key,omitempty" description:"Key for enabling cross-request prompt caching"` SafetyIdentifier *string `json:"safety_identifier,omitempty" description:"Developer-specific safety identifier for the request" hidden:"true"` ReasoningMode *string `json:"reasoning_mode,omitempty" providers:"openai" description:"Supported from the GPT-5.6 Sol generation of OpenAI models. Requests fail when Pro is set on a model that does not support it. Pro increases model work, latency, and token usage." enum:"pro"` ServiceTier *string `json:"service_tier,omitempty" description:"Latency tier to use for processing the request" enum:"auto,default,flex,scale,priority"` StructuredOutputs *bool `json:"structured_outputs,omitempty" description:"Whether to enable structured JSON output mode" hidden:"true"` StrictJSONSchema *bool `json:"strict_json_schema,omitempty" description:"Whether to enforce strict adherence to the JSON schema" hidden:"true"` WebSearchEnabled *bool `json:"web_search_enabled,omitempty" description:"Enable OpenAI web search tool for grounding responses with real-time information"` SearchContextSize *string `json:"search_context_size,omitempty" description:"Amount of search context to use" enum:"low,medium,high" visible_when:"web_search_enabled"` AllowedDomains []string `json:"allowed_domains,omitempty" label:"Web Search: Allowed Domains" description:"Restrict web search to these domains" visible_when:"web_search_enabled"` } // ChatModelAnthropicThinkingOptions configures Anthropic thinking budget. type ChatModelAnthropicThinkingOptions struct { BudgetTokens *int64 `json:"budget_tokens,omitempty" description:"Maximum number of tokens the model may use for thinking"` } // ChatModelAnthropicProviderOptions configures Anthropic provider behavior. type ChatModelAnthropicProviderOptions struct { SendReasoning *bool `json:"send_reasoning,omitempty" description:"Whether to include reasoning content in the response"` Thinking *ChatModelAnthropicThinkingOptions `json:"thinking,omitempty" description:"Configuration for extended thinking"` ThinkingDisplay *string `json:"thinking_display,omitempty" label:"Thinking Display" description:"Controls how Anthropic returns thinking content" enum:"summarized,omitted"` DisableParallelToolUse *bool `json:"disable_parallel_tool_use,omitempty" description:"Whether to disable parallel tool execution"` WebSearchEnabled *bool `json:"web_search_enabled,omitempty" description:"Enable Anthropic web search tool for grounding responses with real-time information"` AllowedDomains []string `json:"allowed_domains,omitempty" label:"Web Search: Allowed Domains" description:"Restrict web search to these domains (cannot be used with blocked_domains)" visible_when:"web_search_enabled" conflicts_with:"blocked_domains"` BlockedDomains []string `json:"blocked_domains,omitempty" label:"Web Search: Blocked Domains" description:"Block web search on these domains (cannot be used with allowed_domains)" visible_when:"web_search_enabled" conflicts_with:"allowed_domains"` Context1MEnabled *bool `json:"context_1m_enabled,omitempty" label:"1M Context Window" description:"Send the anthropic-beta context-1m-2025-08-07 header to unlock the 1M token context window on supported Claude models. Pair with a matching Context Limit. Long-context pricing and higher latency may apply above 200K tokens."` } // ChatModelGoogleThinkingConfig configures Google thinking behavior. type ChatModelGoogleThinkingConfig struct { ThinkingBudget *int64 `json:"thinking_budget,omitempty" description:"Maximum number of tokens the model may use for thinking (cannot be used with thinking_level)" conflicts_with:"thinking_config.thinking_level"` ThinkingLevel *string `json:"thinking_level,omitempty" description:"Thinking level for Gemini 3+ models, used when the user has not selected a reasoning effort (cannot be used with thinking_budget)" enum:"minimal,low,medium,high" conflicts_with:"thinking_config.thinking_budget"` IncludeThoughts *bool `json:"include_thoughts,omitempty" description:"Whether to include thinking content in the response"` } // ChatModelGoogleSafetySetting configures Google safety filtering. type ChatModelGoogleSafetySetting struct { Category string `json:"category,omitempty" description:"The harm category to configure"` Threshold string `json:"threshold,omitempty" description:"The blocking threshold for the harm category"` } // ChatModelGoogleProviderOptions configures Google provider behavior. type ChatModelGoogleProviderOptions struct { ThinkingConfig *ChatModelGoogleThinkingConfig `json:"thinking_config,omitempty" description:"Configuration for extended thinking"` CachedContent string `json:"cached_content,omitempty" description:"Resource name of a cached content object" hidden:"true"` SafetySettings []ChatModelGoogleSafetySetting `json:"safety_settings,omitempty" description:"Safety filtering settings for harmful content categories" hidden:"true"` Threshold string `json:"threshold,omitempty" hidden:"true"` WebSearchEnabled *bool `json:"web_search_enabled,omitempty" description:"Enable Google Search grounding for real-time information"` } // ChatModelOpenAICompatProviderOptions configures OpenAI-compatible behavior. type ChatModelOpenAICompatProviderOptions struct { User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"` } // ChatModelReasoningOptions configures reasoning behavior for model // providers that support it. type ChatModelReasoningOptions struct { Enabled *bool `json:"enabled,omitempty" description:"Whether reasoning is enabled"` Exclude *bool `json:"exclude,omitempty" description:"Whether to exclude reasoning content from the response"` MaxTokens *int64 `json:"max_tokens,omitempty" description:"Maximum number of tokens for reasoning output"` } // ChatModelOpenRouterProvider configures OpenRouter routing preferences. type ChatModelOpenRouterProvider struct { Order []string `json:"order,omitempty" description:"Ordered list of preferred provider names"` AllowFallbacks *bool `json:"allow_fallbacks,omitempty" description:"Whether to allow fallback to other providers"` RequireParameters *bool `json:"require_parameters,omitempty" description:"Whether to require all parameters to be supported by the provider"` DataCollection *string `json:"data_collection,omitempty" description:"Data collection policy preference"` Only []string `json:"only,omitempty" description:"Restrict to only these provider names"` Ignore []string `json:"ignore,omitempty" description:"Provider names to exclude from routing"` Quantizations []string `json:"quantizations,omitempty" description:"Allowed model quantization levels"` Sort *string `json:"sort,omitempty" description:"Sort order for provider selection"` } // ChatModelOpenRouterProviderOptions configures OpenRouter provider behavior. type ChatModelOpenRouterProviderOptions struct { Reasoning *ChatModelReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"` ExtraBody map[string]any `json:"extra_body,omitempty" description:"Additional fields to include in the request body" hidden:"true"` IncludeUsage *bool `json:"include_usage,omitempty" description:"Whether to include token usage information in the response" hidden:"true"` LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"` LogProbs *bool `json:"log_probs,omitempty" description:"Whether to return log probabilities of output tokens" hidden:"true"` ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty" description:"Whether the model may make multiple tool calls in parallel"` User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"` Provider *ChatModelOpenRouterProvider `json:"provider,omitempty" description:"Routing preferences for provider selection" hidden:"true"` } // ChatModelVercelGatewayProviderOptions configures Vercel routing behavior. type ChatModelVercelGatewayProviderOptions struct { Order []string `json:"order,omitempty" description:"Ordered list of preferred provider names"` Models []string `json:"models,omitempty" description:"Model identifiers to route across"` } // ChatModelVercelProviderOptions configures Vercel provider behavior. type ChatModelVercelProviderOptions struct { Reasoning *ChatModelReasoningOptions `json:"reasoning,omitempty" description:"Configuration for reasoning behavior"` ProviderOptions *ChatModelVercelGatewayProviderOptions `json:"providerOptions,omitempty" description:"Gateway routing options for provider selection" hidden:"true"` User *string `json:"user,omitempty" description:"Unique identifier for the end user for abuse monitoring" hidden:"true"` LogitBias map[string]int64 `json:"logit_bias,omitempty" description:"Token IDs mapped to bias values from -100 to 100" hidden:"true"` LogProbs *bool `json:"logprobs,omitempty" description:"Whether to return log probabilities of output tokens" hidden:"true"` TopLogProbs *int64 `json:"top_logprobs,omitempty" description:"Number of most likely tokens to return log probabilities for" hidden:"true"` ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty" description:"Whether the model may make multiple tool calls in parallel"` ExtraBody map[string]any `json:"extra_body,omitempty" description:"Additional fields to include in the request body" hidden:"true"` } // Reasoning effort levels, ordered low to high for clamping and comparison. const ( ChatModelReasoningEffortNone = "none" ChatModelReasoningEffortMinimal = "minimal" ChatModelReasoningEffortLow = "low" ChatModelReasoningEffortMedium = "medium" ChatModelReasoningEffortHigh = "high" ChatModelReasoningEffortXHigh = "xhigh" ChatModelReasoningEffortMax = "max" ) var chatModelReasoningEffortValues = []string{ ChatModelReasoningEffortNone, ChatModelReasoningEffortMinimal, ChatModelReasoningEffortLow, ChatModelReasoningEffortMedium, ChatModelReasoningEffortHigh, ChatModelReasoningEffortXHigh, ChatModelReasoningEffortMax, } // ChatModelReasoningEffortValues returns the global reasoning effort scale. func ChatModelReasoningEffortValues() []string { return slices.Clone(chatModelReasoningEffortValues) } // ChatModelReasoningEffortConfig configures per-model reasoning effort // bounds. When configured, Default and Max must both be provided before // storing. type ChatModelReasoningEffortConfig struct { Default *string `json:"default,omitempty" label:"Default Reasoning Effort" description:"Reasoning effort used when the user has not selected one" enum:"none,minimal,low,medium,high,xhigh,max"` Max *string `json:"max,omitempty" label:"Max Reasoning Effort" description:"Maximum reasoning effort the user may select" enum:"none,minimal,low,medium,high,xhigh,max"` } // ChatModelCallConfig configures per-call model behavior defaults. type ChatModelCallConfig struct { MaxOutputTokens *int64 `json:"max_output_tokens,omitempty" description:"Upper bound on tokens the model may generate"` Temperature *float64 `json:"temperature,omitempty" description:"Sampling temperature between 0 and 2"` TopP *float64 `json:"top_p,omitempty" description:"Nucleus sampling probability cutoff"` TopK *int64 `json:"top_k,omitempty" description:"Number of highest-probability tokens to keep for sampling"` PresencePenalty *float64 `json:"presence_penalty,omitempty" description:"Penalty for tokens that have already appeared in the output"` FrequencyPenalty *float64 `json:"frequency_penalty,omitempty" description:"Penalty for tokens based on their frequency in the output"` ReasoningEffort *ChatModelReasoningEffortConfig `json:"reasoning_effort,omitempty" description:"Default and max reasoning effort for the model"` OpenAIConfig *ChatModelOpenAIConfig `json:"openai_config,omitempty" description:"OpenAI client construction settings" providers:"openai"` ProviderOptions *ChatModelProviderOptions `json:"provider_options,omitempty" description:"Provider-specific option overrides"` } // ChatModelOpenAIConfig holds settings applied once when the OpenAI client // is built, not per request, including OpenAI-format models on Bedrock. type ChatModelOpenAIConfig struct { UseResponsesAPI *bool `json:"use_responses_api,omitempty" label:"Use Responses API" description:"Override which OpenAI API this model uses. Leave unset to decide from the provider SDK's known-model list, true to force the Responses API, false to force Chat Completions. Azure OpenAI providers ignore this and always follow the known-model list."` ReasoningModel *bool `json:"reasoning_model,omitempty" label:"Reasoning Model" providers:"openai,bedrock" description:"Override whether this OpenAI-format model is treated as a reasoning model (reasoning effort and summary are sent, temperature and top_p are dropped). Leave unset to decide from the model name, true for models newer than the provider SDK knows, false to force plain sampling. Ignored for Anthropic models on Bedrock."` } // UnmarshalStrict rejects unknown fields except for removed pricing fields, // which may still be present in stored model configuration JSON: the nested // cost object and the four top-level per-million-token price keys that // predate it (see migration 000435, which read both forms). func (c *ChatModelCallConfig) UnmarshalStrict(data []byte) error { type chatModelCallConfigAlias ChatModelCallConfig aux := struct { *chatModelCallConfigAlias Cost json.RawMessage `json:"cost"` InputPrice json.RawMessage `json:"input_price_per_million_tokens"` OutputPrice json.RawMessage `json:"output_price_per_million_tokens"` CacheReadPrice json.RawMessage `json:"cache_read_price_per_million_tokens"` CacheWritePrice json.RawMessage `json:"cache_write_price_per_million_tokens"` }{ chatModelCallConfigAlias: (*chatModelCallConfigAlias)(c), } dec := json.NewDecoder(bytes.NewReader(data)) dec.DisallowUnknownFields() if err := dec.Decode(&aux); err != nil { return err } if _, err := dec.Token(); !errors.Is(err, io.EOF) { return xerrors.New("unexpected trailing data after JSON value") } return nil } // CreateChatModelRequest is the request body for an organization-scoped // ChatModel. AIProviderID, Model, and a positive ContextLimit are required. // Enabled defaults to true. IsDefault defaults to false when the organization // already has a default model. The first model created in an organization is // automatically promoted to default. CompressionThreshold defaults to 70, or // 30 when ContextLimit is at least 500k tokens. An omitted ModelConfig uses the // provider defaults. type CreateChatModelRequest struct { AIProviderID *uuid.UUID `json:"ai_provider_id,omitempty" format:"uuid"` Model string `json:"model"` DisplayName string `json:"display_name,omitempty"` Enabled *bool `json:"enabled,omitempty"` IsDefault *bool `json:"is_default,omitempty"` ContextLimit *int64 `json:"context_limit,omitempty"` CompressionThreshold *int32 `json:"compression_threshold,omitempty"` ModelConfig *ChatModelCallConfig `json:"model_config,omitempty"` } // UpdateChatModelRequest updates a ChatModel. Empty Model and DisplayName // values preserve the stored values. Nil pointer fields preserve their stored // values. This request cannot clear DisplayName. type UpdateChatModelRequest struct { AIProviderID *uuid.UUID `json:"ai_provider_id,omitempty" format:"uuid"` Model string `json:"model,omitempty"` DisplayName string `json:"display_name,omitempty"` Enabled *bool `json:"enabled,omitempty"` IsDefault *bool `json:"is_default,omitempty"` ContextLimit *int64 `json:"context_limit,omitempty"` CompressionThreshold *int32 `json:"compression_threshold,omitempty"` ModelConfig *ChatModelCallConfig `json:"model_config,omitempty"` } // ChatDiffStatus represents cached diff status for a chat. The URL // may point to a pull request or a branch page depending on whether // a PR has been opened. type ChatDiffStatus struct { ChatID uuid.UUID `json:"chat_id" format:"uuid"` RemoteOrigin *string `json:"remote_origin,omitempty"` GitBranch *string `json:"git_branch,omitempty"` URL *string `json:"url,omitempty"` PullRequestState *string `json:"pull_request_state,omitempty"` PullRequestTitle string `json:"pull_request_title"` PullRequestDraft bool `json:"pull_request_draft"` ChangesRequested bool `json:"changes_requested"` Additions int32 `json:"additions"` Deletions int32 `json:"deletions"` ChangedFiles int32 `json:"changed_files"` AuthorLogin *string `json:"author_login,omitempty"` AuthorAvatarURL *string `json:"author_avatar_url,omitempty"` BaseBranch *string `json:"base_branch,omitempty"` HeadBranch *string `json:"head_branch,omitempty"` PRNumber *int32 `json:"pr_number,omitempty"` Commits *int32 `json:"commits,omitempty"` Approved *bool `json:"approved,omitempty"` ReviewerCount *int32 `json:"reviewer_count,omitempty"` RefreshedAt *time.Time `json:"refreshed_at,omitempty" format:"date-time"` StaleAt *time.Time `json:"stale_at,omitempty" format:"date-time"` } // DiffStatusRef identifies one ref that a chat tracks. A chat has // one diff status for each ref. type DiffStatusRef struct { RemoteOrigin string `json:"remote_origin"` GitBranch string `json:"git_branch"` } // ChangedDiffStatus is the diff status of one ref after a change. // When the ref has no stored status, Status has only chat_id. type ChangedDiffStatus struct { Ref DiffStatusRef `json:"ref"` Status *ChatDiffStatus `json:"status"` } // ChatDiffContents represents the resolved diff text for a chat. type ChatDiffContents struct { ChatID uuid.UUID `json:"chat_id" format:"uuid"` Provider *string `json:"provider,omitempty"` RemoteOrigin *string `json:"remote_origin,omitempty"` Branch *string `json:"branch,omitempty"` PullRequestURL *string `json:"pull_request_url,omitempty"` Diff string `json:"diff,omitempty"` } // Chat git watch error messages. These are the user-visible messages // the server returns in 400 responses from // /api/v2/chats/{id}/stream/git when the chat cannot be // observed through a workspace agent. const ( ChatGitWatchNoWorkspaceMessage = "Chat has no workspace to watch." ChatGitWatchWorkspaceNotFoundMessage = "Chat workspace not found." ChatGitWatchNoEligibleAgentMessage = "No eligible agent found for chat workspace." // ChatGitWatchAgentStatePrefix is the common prefix of the // message produced by ChatGitWatchAgentStateMessage. ChatGitWatchAgentStatePrefix = "Agent state is " ) // ChatGitWatchAgentStateMessage is the user-visible error message // returned from /api/v2/chats/{id}/stream/git when the // chat workspace's agent is not in the connected state. func ChatGitWatchAgentStateMessage(actual WorkspaceAgentStatus) string { return fmt.Sprintf("%s%q, it must be in the %q state.", ChatGitWatchAgentStatePrefix, actual, WorkspaceAgentConnected) } // ChatStreamEventType represents the kind of chat stream update. type ChatStreamEventType string const ( ChatStreamEventTypeMessagePart ChatStreamEventType = "message_part" ChatStreamEventTypeMessage ChatStreamEventType = "message" ChatStreamEventTypeStatus ChatStreamEventType = "status" ChatStreamEventTypeError ChatStreamEventType = "error" ChatStreamEventTypeQueueUpdate ChatStreamEventType = "queue_update" ChatStreamEventTypeRetry ChatStreamEventType = "retry" ChatStreamEventTypeActionRequired ChatStreamEventType = "action_required" ChatStreamEventTypePreviewReset ChatStreamEventType = "preview_reset" ChatStreamEventTypeHistoryReset ChatStreamEventType = "history_reset" ) // ChatQueuedMessage represents a queued message waiting to be processed. type ChatQueuedMessage struct { ID int64 `json:"id"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` Content []ChatMessagePart `json:"content"` // BusyBehavior is the lane of the message. Steer messages go in // before the next model call, and queue messages go in after the // turn ends. BusyBehavior ChatBusyBehavior `json:"busy_behavior" enums:"steer,queue"` CreatedAt time.Time `json:"created_at" format:"date-time"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` // EditingSince is set while the owner edits the message. A message // under edit and every message behind it wait until the edit ends. A // steer message under edit also holds the steer messages ahead of // it. A turn that ends at a message under edit pauses the chat. EditingSince *time.Time `json:"editing_since,omitempty" format:"date-time"` } // EditChatQueuedMessageRequest edits a queued message. Omitted fields // are left unchanged; a request with no fields is rejected. type EditChatQueuedMessageRequest struct { // Content replaces the queued content. An empty array is rejected. Content []ChatInputPart `json:"content,omitempty"` // ModelConfigID and ReasoningEffort require Content; sending either // without it returns 400. Omitted values keep the stored ones. ModelConfigID *uuid.UUID `json:"model_config_id,omitempty" format:"uuid"` ReasoningEffort *string `json:"reasoning_effort,omitempty"` // Editing begins (true) or ends (false) an edit of the message. A // chat has at most one message under edit; beginning another ends the // first. While the chat is paused, beginning an edit on another // message returns 409. Ending the edit of a paused chat's head sends // it. Editing *bool `json:"editing,omitempty"` } // ChatStreamMessagePart is a streamed message part update. type ChatStreamMessagePart struct { Role ChatMessageRole `json:"role,omitempty"` Part ChatMessagePart `json:"part"` HistoryVersion int64 `json:"history_version,omitempty"` GenerationAttempt int64 `json:"generation_attempt,omitempty"` Seq int64 `json:"seq,omitempty"` } // ChatStreamStatus represents an updated chat status. type ChatStreamStatus struct { Status ChatStatus `json:"status"` } // ChatErrorKind classifies chat errors for consistent client rendering. type ChatErrorKind string const ( ChatErrorKindGeneric ChatErrorKind = "generic" ChatErrorKindOverloaded ChatErrorKind = "overloaded" ChatErrorKindRateLimit ChatErrorKind = "rate_limit" ChatErrorKindTimeout ChatErrorKind = "timeout" ChatErrorKindStreamSilenceTimeout ChatErrorKind = "stream_silence_timeout" ChatErrorKindAuth ChatErrorKind = "auth" ChatErrorKindConfig ChatErrorKind = "config" ChatErrorKindUsageLimit ChatErrorKind = "usage_limit" ChatErrorKindMissingKey ChatErrorKind = "missing_key" ChatErrorKindProviderDisabled ChatErrorKind = "provider_disabled" ChatErrorKindContentFilter ChatErrorKind = "content_filter" ChatErrorKindHookDispatchFailed ChatErrorKind = "hook_dispatch_failed" ChatErrorKindHookDenied ChatErrorKind = "hook_denied" ) // AllChatErrorKinds contains every ChatErrorKind value. // Update this when adding new constants above. var AllChatErrorKinds = []ChatErrorKind{ ChatErrorKindGeneric, ChatErrorKindOverloaded, ChatErrorKindRateLimit, ChatErrorKindTimeout, ChatErrorKindStreamSilenceTimeout, ChatErrorKindAuth, ChatErrorKindConfig, ChatErrorKindUsageLimit, ChatErrorKindMissingKey, ChatErrorKindProviderDisabled, ChatErrorKindContentFilter, ChatErrorKindHookDispatchFailed, ChatErrorKindHookDenied, } // ChatError represents a terminal chat error in persisted chat state or the // live stream. type ChatError struct { // Message is the normalized, user-facing error message. Message string `json:"message"` // Detail is optional provider-specific context shown alongside the // normalized error message when available. Detail string `json:"detail,omitempty"` // Kind classifies the error for consistent client rendering. Kind ChatErrorKind `json:"kind,omitempty"` // Provider identifies the upstream model provider when known. Provider string `json:"provider,omitempty"` // Retryable reports whether the underlying error is transient. Retryable bool `json:"retryable"` // StatusCode is the best-effort upstream HTTP status code. StatusCode int `json:"status_code,omitempty"` } // ChatStreamRetry represents an auto-retry status event in the stream. // Published when the server automatically retries a failed LLM call. type ChatStreamRetry struct { // Attempt is the 1-indexed retry attempt number. Attempt int `json:"attempt"` // DelayMs is the backoff delay in milliseconds before the retry. DelayMs int64 `json:"delay_ms"` // Error is the normalized error message from the failed attempt. Error string `json:"error"` // Kind classifies the retry reason for consistent client rendering. Kind ChatErrorKind `json:"kind,omitempty"` // Provider identifies the upstream model provider when known. Provider string `json:"provider,omitempty"` // StatusCode is the best-effort upstream HTTP status code. StatusCode int `json:"status_code,omitempty"` // RetryingAt is the timestamp when the retry will be attempted. RetryingAt time.Time `json:"retrying_at" format:"date-time"` } // ChatStreamActionRequired is the payload of an action_required stream event. type ChatStreamActionRequired struct { ToolCalls []ChatStreamToolCall `json:"tool_calls"` } // ChatStreamToolCall describes a pending dynamic tool call that the client // must execute. type ChatStreamToolCall struct { ToolCallID string `json:"tool_call_id"` ToolName string `json:"tool_name"` Args string `json:"args"` } // DynamicToolCall represents a pending tool invocation from the // chat stream that the client must execute and submit back. type DynamicToolCall struct { ToolCallID string `json:"tool_call_id"` ToolName string `json:"tool_name"` Args string `json:"args"` } // DynamicToolResponse holds the output of a dynamic tool // execution. IsError indicates a tool-level error the LLM // should see, as opposed to an infrastructure failure // (returned as the error return value). type DynamicToolResponse struct { Content string `json:"content"` IsError bool `json:"is_error"` } // DynamicTool describes a client-declared tool definition. On the // client side, the Handler callback executes the tool when the LLM // invokes it. On the server side, only Name, Description, and // InputSchema are used (Handler is not serialized). type DynamicTool struct { Name string `json:"name"` Description string `json:"description,omitempty"` // InputSchema's JSON key "input_schema" uses snake_case for // SDK consistency, deviating from the camelCase "inputSchema" // convention used by MCP. InputSchema json.RawMessage `json:"input_schema"` // Handler executes the tool when the LLM invokes it. // Not serialized; this only exists on the client side. Handler func(ctx context.Context, call DynamicToolCall) (DynamicToolResponse, error) `json:"-"` } // NewDynamicTool creates a DynamicTool with a typed handler. // The JSON schema is derived from T using invopop/jsonschema. // The handler receives deserialized args and the DynamicToolCall metadata. func NewDynamicTool[T any]( name, description string, handler func(ctx context.Context, args T, call DynamicToolCall) (DynamicToolResponse, error), ) DynamicTool { reflector := jsonschema.Reflector{ DoNotReference: true, Anonymous: true, AllowAdditionalProperties: true, } schema := reflector.Reflect(new(T)) schema.Version = "" schemaJSON, err := json.Marshal(schema) if err != nil { panic(fmt.Sprintf("codersdk: failed to marshal schema for %q: %v", name, err)) } return DynamicTool{ Name: name, Description: description, InputSchema: schemaJSON, Handler: func(ctx context.Context, call DynamicToolCall) (DynamicToolResponse, error) { var parsed T if err := json.Unmarshal([]byte(call.Args), &parsed); err != nil { return DynamicToolResponse{ Content: fmt.Sprintf("invalid parameters: %s", err), IsError: true, }, nil } return handler(ctx, parsed, call) }, } } // ChatWatchEventKind represents the kind of event in the chat watch stream. type ChatWatchEventKind string const ( ChatWatchEventKindStatusChange ChatWatchEventKind = "status_change" ChatWatchEventKindSummaryChange ChatWatchEventKind = "summary_change" // ChatWatchEventKindChatSummaryChange carries the persisted whole-chat // summary. It is distinct from SummaryChange (bound to last_turn_summary) so // the frontend updates one field without disturbing the other. ChatWatchEventKindChatSummaryChange ChatWatchEventKind = "chat_summary_change" // ChatWatchEventKindTitleChange is published after each title write. // Take only the title fields from it, ordered by title_updated_at, // because a title write does not change updated_at. ChatWatchEventKindTitleChange ChatWatchEventKind = "title_change" ChatWatchEventKindCreated ChatWatchEventKind = "created" ChatWatchEventKindDeleted ChatWatchEventKind = "deleted" ChatWatchEventKindDiffStatusChange ChatWatchEventKind = "diff_status_change" ChatWatchEventKindActionRequired ChatWatchEventKind = "action_required" // ChatWatchEventKindContextDirty signals that the chat's pinned // workspace context changed: it drifted from the agent's latest // pushed snapshot, or hydration first populated it (a first-turn // pin or an agent push reaching a not-yet-pinned chat). The chat // stays usable; a refresh re-pins a drifted chat to the latest // snapshot. ChatWatchEventKindContextDirty ChatWatchEventKind = "context_dirty" // ChatWatchEventKindProjectChange is published for each chat of a // family after its root chat moves into, between or out of projects. // Take project_id only from this kind. ChatWatchEventKindProjectChange ChatWatchEventKind = "project_change" ) // ChatWatchEvent represents an event from the global chat watch stream. // It delivers lifecycle events (created, status change, summary change, // title change) for all of the authenticated user's chats. When Kind is // ActionRequired, ToolCalls contains the pending dynamic tool // invocations the client must execute and submit back. type ChatWatchEvent struct { Kind ChatWatchEventKind `json:"kind"` Chat Chat `json:"chat"` ToolCalls []ChatStreamToolCall `json:"tool_calls,omitempty"` // ChangedDiffStatus is set when Kind is // ChatWatchEventKindDiffStatusChange. It identifies the ref that // changed. ChangedDiffStatus *ChangedDiffStatus `json:"changed_diff_status,omitempty"` } // ChatStreamEvent represents a real-time update for chat streaming. type ChatStreamEvent struct { Type ChatStreamEventType `json:"type"` ChatID uuid.UUID `json:"chat_id" format:"uuid"` Message *ChatMessage `json:"message,omitempty"` MessagePart *ChatStreamMessagePart `json:"message_part,omitempty"` Status *ChatStreamStatus `json:"status,omitempty"` Error *ChatError `json:"error,omitempty"` Retry *ChatStreamRetry `json:"retry,omitempty"` // QueuedMessages lists the queued messages in delivery order: steer // messages first, then queue messages. QueuedMessages []ChatQueuedMessage `json:"queued_messages,omitempty"` ActionRequired *ChatStreamActionRequired `json:"action_required,omitempty"` } // ChatCost is the AI Gateway cost for the requested chat's whole tree. // Root and subagent chats report the same total. // RequestCount counts every finished request in the tree, including ones that // recorded no billable usage at all, such as a request that failed upstream. // UnpricedRequestCount counts requests with at least one usage record whose // model had no recorded price; RequestCount includes them and // TotalCostMicros omits only their unpriced usage. type ChatCost struct { ChatID uuid.UUID `json:"chat_id" format:"uuid"` TotalCostMicros int64 `json:"total_cost_micros"` RequestCount int64 `json:"request_count"` UnpricedRequestCount int64 `json:"unpriced_request_count"` } // ChatHookDispatchFailedResponse is the error body returned when a // lifecycle hook dispatch fails during a synchronous chat operation. // Kind lets clients classify the failure without parsing message text. type ChatHookDispatchFailedResponse struct { Response Kind ChatErrorKind `json:"kind"` } // ChatHookDeniedResponse is the error body returned when a lifecycle hook // denies a synchronous chat operation. Kind lets clients classify the denial // without parsing message text. type ChatHookDeniedResponse struct { Response Kind ChatErrorKind `json:"kind"` } type ChatRole string const ( ChatRoleRead ChatRole = "read" ChatRoleDeleted ChatRole = "" ) type ChatUser struct { MinimalUser Role ChatRole `json:"role" enums:"read"` } type ChatGroup struct { Group Role ChatRole `json:"role" enums:"read"` } type ChatACL struct { Users []ChatUser `json:"users"` Groups []ChatGroup `json:"groups"` } type UpdateChatACL struct { UserRoles map[string]ChatRole `json:"user_roles,omitempty"` GroupRoles map[string]ChatRole `json:"group_roles,omitempty"` } // ChatListSource controls which chats ListChats returns by ownership. type ChatListSource string const ( // ChatListSourceCreatedByMe returns chats owned by the caller. ChatListSourceCreatedByMe ChatListSource = "created_by_me" // ChatListSourceSharedWithMe returns chats shared with the caller. ChatListSourceSharedWithMe ChatListSource = "shared_with_me" ) // ListChatsOptions are optional parameters for ListChats. type ListChatsOptions struct { // Query supports raw chat search terms. If Query includes a source: term, // Source must be empty. Query string // Source adds a source: term to Query. Source ChatListSource Labels map[string]string ProjectID *uuid.UUID Pagination } func requestJSON[T any](ctx context.Context, c *Client, method, path string, wantStatus int, body any, opts ...RequestOption) (T, error) { var out T res, err := c.Request(ctx, method, path, body, opts...) if err != nil { return out, err } if res.StatusCode != wantStatus { // ReadBodyAsError closes the body. return out, ReadBodyAsError(res) } defer res.Body.Close() err = ReadBodyAsJSON(res, &out) return out, err } func requestNoContent(ctx context.Context, c *Client, method, path string, body any, opts ...RequestOption) error { res, err := c.Request(ctx, method, path, body, opts...) if err != nil { return err } if res.StatusCode != http.StatusNoContent { // ReadBodyAsError closes the body. return ReadBodyAsError(res) } defer res.Body.Close() return nil } // ListChats returns all chats for the authenticated user. func (c *Client) ListChats(ctx context.Context, opts *ListChatsOptions) ([]Chat, error) { var reqOpts []RequestOption if opts != nil { reqOpts = append(reqOpts, opts.asRequestOption()) query := opts.Query if opts.Source != "" { if query != "" { query += " " } query += "source:" + string(opts.Source) } if query != "" { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() q.Set("q", query) r.URL.RawQuery = q.Encode() }) } if opts.ProjectID != nil { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() q.Set("project_id", opts.ProjectID.String()) r.URL.RawQuery = q.Encode() }) } if len(opts.Labels) > 0 { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() for k, v := range opts.Labels { q.Add("label", k+":"+v) } r.URL.RawQuery = q.Encode() }) } } return requestJSON[[]Chat](ctx, c, http.MethodGet, "/api/v2/chats", http.StatusOK, nil, reqOpts...) } func chatProjectsPath(organizationID uuid.UUID) string { return fmt.Sprintf("/api/experimental/organizations/%s/chats/projects", organizationID) } func chatProjectPath(organizationID, projectID uuid.UUID) string { return fmt.Sprintf("%s/%s", chatProjectsPath(organizationID), projectID) } // ListChatProjects lists projects the user owns or that are shared with them, // across all organizations. func (c *ExperimentalClient) ListChatProjects(ctx context.Context) ([]ChatProject, error) { res, err := c.Request(ctx, http.MethodGet, "/api/experimental/chats/projects", nil) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, ReadBodyAsError(res) } var projects []ChatProject return projects, ReadBodyAsJSON(res, &projects) } // CreateChatProject creates a chat project in an organization. func (c *ExperimentalClient) CreateChatProject(ctx context.Context, organizationID uuid.UUID, req CreateChatProjectRequest) (ChatProject, error) { return requestJSON[ChatProject](ctx, c.Client, http.MethodPost, chatProjectsPath(organizationID), http.StatusCreated, req) } // GetChatProject gets a chat project. func (c *ExperimentalClient) GetChatProject(ctx context.Context, organizationID, projectID uuid.UUID) (ChatProject, error) { return requestJSON[ChatProject](ctx, c.Client, http.MethodGet, chatProjectPath(organizationID, projectID), http.StatusOK, nil) } // UpdateChatProject updates a chat project. func (c *ExperimentalClient) UpdateChatProject(ctx context.Context, organizationID, projectID uuid.UUID, req UpdateChatProjectRequest) (ChatProject, error) { res, err := c.Request(ctx, http.MethodPatch, chatProjectPath(organizationID, projectID), req) if err != nil { return ChatProject{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return ChatProject{}, ReadBodyAsError(res) } var project ChatProject return project, ReadBodyAsJSON(res, &project) } // DeleteChatProject deletes a chat project and detaches its chats. func (c *ExperimentalClient) DeleteChatProject(ctx context.Context, organizationID, projectID uuid.UUID) error { res, err := c.Request(ctx, http.MethodDelete, chatProjectPath(organizationID, projectID), nil) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusNoContent { return ReadBodyAsError(res) } return nil } // ChatProjectACL returns who a chat project is shared with. func (c *ExperimentalClient) ChatProjectACL(ctx context.Context, organizationID, projectID uuid.UUID) (ChatProjectACL, error) { res, err := c.Request(ctx, http.MethodGet, chatProjectPath(organizationID, projectID)+"/acl", nil) if err != nil { return ChatProjectACL{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return ChatProjectACL{}, ReadBodyAsError(res) } var acl ChatProjectACL return acl, ReadBodyAsJSON(res, &acl) } // UpdateChatProjectACL changes who a chat project is shared with. func (c *ExperimentalClient) UpdateChatProjectACL(ctx context.Context, organizationID, projectID uuid.UUID, req UpdateChatProjectACL) error { res, err := c.Request(ctx, http.MethodPatch, chatProjectPath(organizationID, projectID)+"/acl", req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusNoContent { return ReadBodyAsError(res) } return nil } // ChatProjectInstructions gets a chat project's instructions. func (c *ExperimentalClient) ChatProjectInstructions(ctx context.Context, organizationID, projectID uuid.UUID) (ChatProjectInstructions, error) { res, err := c.Request(ctx, http.MethodGet, chatProjectPath(organizationID, projectID)+"/instructions", nil) if err != nil { return ChatProjectInstructions{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return ChatProjectInstructions{}, ReadBodyAsError(res) } var instructions ChatProjectInstructions return instructions, ReadBodyAsJSON(res, &instructions) } // UpdateChatProjectInstructions creates or replaces a chat project's // instructions. func (c *ExperimentalClient) UpdateChatProjectInstructions(ctx context.Context, organizationID, projectID uuid.UUID, req UpdateChatProjectInstructionsRequest) (ChatProjectInstructions, error) { return requestJSON[ChatProjectInstructions](ctx, c.Client, http.MethodPut, chatProjectPath(organizationID, projectID)+"/instructions", http.StatusOK, req) } // DeleteChatProjectInstructions clears a chat project's instructions. It // succeeds when the project has no instructions. func (c *ExperimentalClient) DeleteChatProjectInstructions(ctx context.Context, organizationID, projectID uuid.UUID) error { return requestNoContent(ctx, c.Client, http.MethodDelete, chatProjectPath(organizationID, projectID)+"/instructions", nil) } // ListChatProjectMemories lists memories for a chat project. func (c *ExperimentalClient) ListChatProjectMemories(ctx context.Context, organizationID, projectID uuid.UUID) ([]ChatProjectMemory, error) { return requestJSON[[]ChatProjectMemory](ctx, c.Client, http.MethodGet, chatProjectPath(organizationID, projectID)+"/memories", http.StatusOK, nil) } // CreateChatProjectMemory creates a project memory. func (c *ExperimentalClient) CreateChatProjectMemory(ctx context.Context, organizationID, projectID uuid.UUID, req CreateChatProjectMemoryRequest) (ChatProjectMemory, error) { return requestJSON[ChatProjectMemory](ctx, c.Client, http.MethodPost, chatProjectPath(organizationID, projectID)+"/memories", http.StatusCreated, req) } // GetChatProjectMemory gets a project memory. func (c *ExperimentalClient) GetChatProjectMemory(ctx context.Context, organizationID, projectID, memoryID uuid.UUID) (ChatProjectMemory, error) { return requestJSON[ChatProjectMemory](ctx, c.Client, http.MethodGet, fmt.Sprintf("%s/memories/%s", chatProjectPath(organizationID, projectID), memoryID), http.StatusOK, nil) } // DeleteChatProjectMemory deletes a project memory. func (c *ExperimentalClient) DeleteChatProjectMemory(ctx context.Context, organizationID, projectID, memoryID uuid.UUID) error { return requestNoContent(ctx, c.Client, http.MethodDelete, fmt.Sprintf("%s/memories/%s", chatProjectPath(organizationID, projectID), memoryID), nil) } // ListUserAIProviderKeyConfigs returns user-scoped AI provider key configs. func (c *Client) ListUserAIProviderKeyConfigs(ctx context.Context, user string) ([]UserAIProviderKeyConfig, error) { res, err := c.Request(ctx, http.MethodGet, userAIProviderKeysPath(user), nil) if err != nil { return nil, xerrors.Errorf("list user AI provider key configs: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, ReadBodyAsError(res) } var configs []UserAIProviderKeyConfig return configs, ReadBodyAsJSON(res, &configs) } // UpsertUserAIProviderKey creates or replaces a user API key for an AI provider. func (c *Client) UpsertUserAIProviderKey(ctx context.Context, user string, providerID uuid.UUID, req CreateUserAIProviderKeyRequest) (UserAIProviderKeyConfig, error) { res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("%s/%s", userAIProviderKeysPath(user), providerID), req) if err != nil { return UserAIProviderKeyConfig{}, xerrors.Errorf("upsert user AI provider key: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return UserAIProviderKeyConfig{}, ReadBodyAsError(res) } var config UserAIProviderKeyConfig return config, ReadBodyAsJSON(res, &config) } // DeleteUserAIProviderKey deletes a user API key for an AI provider. func (c *Client) DeleteUserAIProviderKey(ctx context.Context, user string, providerID uuid.UUID) error { res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", userAIProviderKeysPath(user), providerID), nil) if err != nil { return xerrors.Errorf("delete user AI provider key: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusNoContent { return ReadBodyAsError(res) } return nil } func userAIProviderKeysPath(user string) string { return fmt.Sprintf("/api/v2/users/%s/ai-provider-keys", url.PathEscape(user)) } // ChatModels returns the chat model configs the caller can read in one // organization, plus the redacted provider descriptors the authoring page // needs, for org-scoped management and picker surfaces. func (c *Client) ChatModels(ctx context.Context, organizationID uuid.UUID) (OrganizationChatModelsResponse, error) { return requestJSON[OrganizationChatModelsResponse](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/chats/models", organizationID), http.StatusOK, nil) } // ChatModel fetches one chat model config by ID in an organization. func (c *Client) ChatModel(ctx context.Context, organizationID, modelConfigID uuid.UUID) (ChatModel, error) { return requestJSON[ChatModel](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s", organizationID, modelConfigID), http.StatusOK, nil) } // CreateChatModel creates a chat model config in the given organization. func (c *Client) CreateChatModel(ctx context.Context, organizationID uuid.UUID, req CreateChatModelRequest) (ChatModel, error) { return requestJSON[ChatModel](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/chats/models", organizationID), http.StatusCreated, req) } // UpdateChatModel updates a ChatModel in an organization. func (c *Client) UpdateChatModel(ctx context.Context, organizationID, modelID uuid.UUID, req UpdateChatModelRequest) (ChatModel, error) { return requestJSON[ChatModel](ctx, c, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s", organizationID, modelID), http.StatusOK, req) } // ChatModelACL returns the access control list for a chat model in an // organization. func (c *Client) ChatModelACL(ctx context.Context, organizationID, modelID uuid.UUID) (ChatModelACL, error) { return requestJSON[ChatModelACL](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s/acl", organizationID, modelID), http.StatusOK, nil) } // ChatModelACLAvailable returns available users and groups that can be assigned // chat model permissions. The optional request applies q/limit/offset/after_id // to users. Groups reuse the user search query and q/limit semantics. Pass // codersdk.UsersRequest{} when no filtering is desired. func (c *Client) ChatModelACLAvailable(ctx context.Context, organizationID, modelID uuid.UUID, req UsersRequest) (ACLAvailable, error) { return requestJSON[ACLAvailable]( ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s/acl/available", organizationID, modelID), http.StatusOK, nil, req.Pagination.asRequestOption(), req.asRequestOption(), ) } // UpdateChatModelACL applies a sparse access control list update to a chat // model in an organization. func (c *Client) UpdateChatModelACL(ctx context.Context, organizationID, modelID uuid.UUID, req UpdateChatModelACLRequest) error { return requestNoContent(ctx, c, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s/acl", organizationID, modelID), req) } // DeleteChatModel deletes a ChatModel in an organization. func (c *Client) DeleteChatModel(ctx context.Context, organizationID, modelID uuid.UUID) error { return requestNoContent(ctx, c, http.MethodDelete, fmt.Sprintf("/api/v2/organizations/%s/chats/models/%s", organizationID, modelID), nil) } // ChatModelProviderDescriptor is the redacted view of an AI provider carried // on the org model collection response. It carries only the capability // metadata the Models UI needs; key material, base URLs, and headers are // never exposed. The fields mirror the provider descriptors returned by the // organization-scoped chat models collection. type ChatModelProviderDescriptor struct { ID uuid.UUID `json:"id" format:"uuid"` Type string `json:"type"` DisplayName string `json:"display_name"` Icon string `json:"icon"` Enabled bool `json:"enabled"` HasAPIKey bool `json:"has_api_key"` HasUserAPIKey bool `json:"has_user_api_key"` HasEffectiveAPIKey bool `json:"has_effective_api_key"` AllowUserAPIKey bool `json:"allow_user_api_key"` Available bool `json:"available"` UnavailableReason ChatModelProviderUnavailableReason `json:"unavailable_reason,omitempty"` } // OrganizationChatModelsResponse is the org chat model config collection: // the caller-readable configs plus the redacted provider descriptors the // authoring page needs. type OrganizationChatModelsResponse struct { Models []ChatModel `json:"models"` Providers []ChatModelProviderDescriptor `json:"providers"` UnsupportedProviders []ChatUnsupportedProvider `json:"unsupported_providers"` } // GetChatCost returns the AI Gateway cost for the whole chat tree that // contains chatID. func (c *Client) GetChatCost(ctx context.Context, chatID uuid.UUID) (ChatCost, error) { return requestJSON[ChatCost](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/cost", chatID), http.StatusOK, nil) } // GetChatSystemPrompt returns the deployment-wide chat system prompt. func (c *Client) GetChatSystemPrompt(ctx context.Context) (ChatSystemPromptResponse, error) { return requestJSON[ChatSystemPromptResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/system-prompt", http.StatusOK, nil) } // UpdateChatSystemPrompt updates the deployment-wide chat system prompt. func (c *Client) UpdateChatSystemPrompt(ctx context.Context, req UpdateChatSystemPromptRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/system-prompt", req) } // GetChatPlanModeInstructions returns the deployment-wide plan mode instructions. func (c *Client) GetChatPlanModeInstructions(ctx context.Context) (ChatPlanModeInstructionsResponse, error) { return requestJSON[ChatPlanModeInstructionsResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/plan-mode-instructions", http.StatusOK, nil) } // UpdateChatPlanModeInstructions updates the deployment-wide plan mode instructions. func (c *Client) UpdateChatPlanModeInstructions(ctx context.Context, req UpdateChatPlanModeInstructionsRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/plan-mode-instructions", req) } // OrganizationChatModelOverrides returns the configured chat model overrides for an organization. func (c *Client) OrganizationChatModelOverrides(ctx context.Context, organizationID uuid.UUID) (ChatModelOverridesResponse, error) { path := fmt.Sprintf("/api/v2/organizations/%s/chats/model-overrides", organizationID) return requestJSON[ChatModelOverridesResponse](ctx, c, http.MethodGet, path, http.StatusOK, nil) } // UpdateOrganizationChatModelOverride updates or clears a chat model override for an organization. func (c *Client) UpdateOrganizationChatModelOverride(ctx context.Context, organizationID uuid.UUID, override ChatModelOverrideContext, req UpdateChatModelOverrideRequest) (ChatModelOverrideResponse, error) { path := fmt.Sprintf( "/api/v2/organizations/%s/chats/model-overrides/%s", organizationID, url.PathEscape(string(override)), ) return requestJSON[ChatModelOverrideResponse](ctx, c, http.MethodPut, path, http.StatusOK, req) } // OrganizationChatSystemPrompt returns the organization's chat system // prompt. New chats in the organization receive it after the deployment // system prompt. func (c *Client) OrganizationChatSystemPrompt(ctx context.Context, organizationID uuid.UUID) (OrganizationChatSystemPromptResponse, error) { path := fmt.Sprintf("/api/v2/organizations/%s/chats/config/system-prompt", organizationID) return requestJSON[OrganizationChatSystemPromptResponse](ctx, c, http.MethodGet, path, http.StatusOK, nil) } // UpdateOrganizationChatSystemPrompt replaces the organization's chat system // prompt. An empty prompt clears it. func (c *Client) UpdateOrganizationChatSystemPrompt(ctx context.Context, organizationID uuid.UUID, req UpdateOrganizationChatSystemPromptRequest) error { path := fmt.Sprintf("/api/v2/organizations/%s/chats/config/system-prompt", organizationID) return requestNoContent(ctx, c, http.MethodPut, path, req) } // GetChatPersonalModelOverridesAdminSettings returns the deployment-wide // personal model override admin settings. func (c *Client) GetChatPersonalModelOverridesAdminSettings(ctx context.Context) (ChatPersonalModelOverridesAdminSettings, error) { return requestJSON[ChatPersonalModelOverridesAdminSettings](ctx, c, http.MethodGet, "/api/v2/chats/config/personal-model-overrides", http.StatusOK, nil) } // UpdateChatPersonalModelOverridesAdminSettings updates the deployment-wide // personal model override admin settings. func (c *Client) UpdateChatPersonalModelOverridesAdminSettings(ctx context.Context, req UpdateChatPersonalModelOverridesAdminSettingsRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/personal-model-overrides", req) } // UserChatPersonalModelOverrides returns a user's personal model overrides in an organization. func (c *Client) UserChatPersonalModelOverrides(ctx context.Context, organizationID uuid.UUID, user string) (UserChatPersonalModelOverridesResponse, error) { path := fmt.Sprintf( "/api/v2/organizations/%s/members/%s/chats/model-overrides", organizationID, url.PathEscape(user), ) return requestJSON[UserChatPersonalModelOverridesResponse](ctx, c, http.MethodGet, path, http.StatusOK, nil) } // UpdateUserChatPersonalModelOverride updates a user's personal model override in an organization. func (c *Client) UpdateUserChatPersonalModelOverride(ctx context.Context, organizationID uuid.UUID, user string, override ChatPersonalModelOverrideContext, req UpdateUserChatPersonalModelOverrideRequest) error { path := fmt.Sprintf( "/api/v2/organizations/%s/members/%s/chats/model-overrides/%s", organizationID, url.PathEscape(user), url.PathEscape(string(override)), ) return requestNoContent(ctx, c, http.MethodPut, path, req) } // GetUserChatCustomPrompt fetches the user's custom chat prompt. func (c *Client) GetUserChatCustomPrompt(ctx context.Context) (UserChatCustomPrompt, error) { return requestJSON[UserChatCustomPrompt](ctx, c, http.MethodGet, "/api/v2/chats/config/user-prompt", http.StatusOK, nil) } // GetChatAdvisorConfig returns the deployment-wide advisor configuration. func (c *Client) GetChatAdvisorConfig(ctx context.Context) (AdvisorConfig, error) { return requestJSON[AdvisorConfig](ctx, c, http.MethodGet, "/api/v2/chats/config/advisor", http.StatusOK, nil) } // UpdateChatAdvisorConfig updates the deployment-wide advisor configuration. func (c *Client) UpdateChatAdvisorConfig(ctx context.Context, req UpdateAdvisorConfigRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/advisor", req) } // GetChatWorkspaceTTL returns the configured chat workspace TTL. func (c *Client) GetChatWorkspaceTTL(ctx context.Context) (ChatWorkspaceTTLResponse, error) { return requestJSON[ChatWorkspaceTTLResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/workspace-ttl", http.StatusOK, nil) } // UpdateChatWorkspaceTTL updates the chat workspace TTL setting. func (c *Client) UpdateChatWorkspaceTTL(ctx context.Context, req UpdateChatWorkspaceTTLRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/workspace-ttl", req) } // GetChatRetentionDays returns the configured chat retention period. func (c *Client) GetChatRetentionDays(ctx context.Context) (ChatRetentionDaysResponse, error) { return requestJSON[ChatRetentionDaysResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/retention-days", http.StatusOK, nil) } // UpdateChatRetentionDays updates the chat retention period. func (c *Client) UpdateChatRetentionDays(ctx context.Context, req UpdateChatRetentionDaysRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/retention-days", req) } // GetChatDebugRetentionDays returns the configured chat debug run // retention period. func (c *Client) GetChatDebugRetentionDays(ctx context.Context) (ChatDebugRetentionDaysResponse, error) { return requestJSON[ChatDebugRetentionDaysResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/debug-retention-days", http.StatusOK, nil) } // UpdateChatDebugRetentionDays updates the chat debug run retention period. func (c *Client) UpdateChatDebugRetentionDays(ctx context.Context, req UpdateChatDebugRetentionDaysRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/debug-retention-days", req) } // GetChatAutoArchiveDays returns the configured chat auto-archive period. func (c *Client) GetChatAutoArchiveDays(ctx context.Context) (ChatAutoArchiveDaysResponse, error) { return requestJSON[ChatAutoArchiveDaysResponse](ctx, c, http.MethodGet, "/api/v2/chats/config/auto-archive-days", http.StatusOK, nil) } // UpdateChatAutoArchiveDays updates the chat auto-archive period. func (c *Client) UpdateChatAutoArchiveDays(ctx context.Context, req UpdateChatAutoArchiveDaysRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/auto-archive-days", req) } // UpdateUserChatCustomPrompt updates the user's custom chat prompt. func (c *Client) UpdateUserChatCustomPrompt(ctx context.Context, req UserChatCustomPrompt) (UserChatCustomPrompt, error) { return requestJSON[UserChatCustomPrompt](ctx, c, http.MethodPut, "/api/v2/chats/config/user-prompt", http.StatusOK, req) } // GetUserChatCompactionThresholds fetches the user's per-model chat // compaction thresholds. func (c *Client) GetUserChatCompactionThresholds(ctx context.Context) (UserChatCompactionThresholds, error) { return requestJSON[UserChatCompactionThresholds](ctx, c, http.MethodGet, "/api/v2/chats/config/user-compaction-thresholds", http.StatusOK, nil) } // UpdateUserChatCompactionThreshold updates the user's per-model chat // compaction threshold. func (c *Client) UpdateUserChatCompactionThreshold(ctx context.Context, modelID uuid.UUID, req UpdateUserChatCompactionThresholdRequest) (UserChatCompactionThreshold, error) { return requestJSON[UserChatCompactionThreshold](ctx, c, http.MethodPut, fmt.Sprintf("/api/v2/chats/config/user-compaction-thresholds/%s", modelID), http.StatusOK, req) } // DeleteUserChatCompactionThreshold deletes the user's per-model chat // compaction threshold override. func (c *Client) DeleteUserChatCompactionThreshold(ctx context.Context, modelID uuid.UUID) error { return requestNoContent(ctx, c, http.MethodDelete, fmt.Sprintf("/api/v2/chats/config/user-compaction-thresholds/%s", modelID), nil) } // CreateChat creates a new chat. func (c *Client) CreateChat(ctx context.Context, req CreateChatRequest) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodPost, "/api/v2/chats", http.StatusCreated, req) } // StreamChatOptions are optional parameters for StreamChat. type StreamChatOptions struct { // AfterID limits the initial snapshot to messages created // after the given ID. This is useful for relay connections // that only need live message_part events and can skip the // full message history. AfterID *int64 } // StreamChat streams chat updates in real time. // // The returned channel includes initial snapshot events first, followed by // live updates. Callers must close the returned io.Closer to release the // websocket connection when done. func (c *Client) StreamChat(ctx context.Context, chatID uuid.UUID, opts *StreamChatOptions) (<-chan ChatStreamEvent, io.Closer, error) { path := fmt.Sprintf("/api/v2/chats/%s/stream", chatID) if opts != nil && opts.AfterID != nil { path += fmt.Sprintf("?after_id=%d", *opts.AfterID) } conn, err := c.Dial( ctx, path, &websocket.DialOptions{CompressionMode: websocket.CompressionDisabled}, ) if err != nil { return nil, nil, err } conn.SetReadLimit(1 << 22) // 4MiB streamCtx, streamCancel := context.WithCancel(ctx) events := make(chan ChatStreamEvent, 128) send := func(event ChatStreamEvent) bool { if event.ChatID == uuid.Nil { event.ChatID = chatID } select { case <-streamCtx.Done(): return false case events <- event: return true } } go func() { defer close(events) defer streamCancel() defer func() { _ = conn.Close(websocket.StatusNormalClosure, "") }() for { var batch []ChatStreamEvent if err := wsjson.Read(streamCtx, conn, &batch); err != nil { if streamCtx.Err() != nil { return } switch websocket.CloseStatus(err) { case websocket.StatusNormalClosure, websocket.StatusGoingAway: return } _ = send(ChatStreamEvent{ Type: ChatStreamEventTypeError, Error: &ChatError{ Message: fmt.Sprintf("read chat stream: %v", err), }, }) return } for _, event := range batch { if !send(event) { return } } } }() return events, closeFunc(func() error { streamCancel() return nil }), nil } // WatchChats streams lifecycle events for all of the authenticated // user's chats in real time. The returned channel emits // ChatWatchEvent values for status changes, title changes, creation, // deletion, diff-status changes, and action-required notifications. // Callers must close the returned io.Closer to release the websocket // connection when done. func (c *Client) WatchChats(ctx context.Context) (<-chan ChatWatchEvent, io.Closer, error) { conn, err := c.Dial( ctx, "/api/v2/chats/watch", &websocket.DialOptions{CompressionMode: websocket.CompressionDisabled}, ) if err != nil { return nil, nil, err } conn.SetReadLimit(1 << 22) // 4MiB streamCtx, streamCancel := context.WithCancel(ctx) events := make(chan ChatWatchEvent, 128) go func() { defer close(events) defer streamCancel() defer func() { _ = conn.Close(websocket.StatusNormalClosure, "") }() for { var event ChatWatchEvent if err := wsjson.Read(streamCtx, conn, &event); err != nil { if streamCtx.Err() != nil { return } switch websocket.CloseStatus(err) { case websocket.StatusNormalClosure, websocket.StatusGoingAway: return } return } select { case <-streamCtx.Done(): return case events <- event: } } }() return events, closeFunc(func() error { streamCancel() return nil }), nil } // GetChatDebugLogging returns the runtime admin setting that allows // users to opt into chat debug logging. func (c *Client) GetChatDebugLogging(ctx context.Context) (ChatDebugLoggingAdminSettings, error) { return requestJSON[ChatDebugLoggingAdminSettings](ctx, c, http.MethodGet, "/api/v2/chats/config/debug-logging", http.StatusOK, nil) } // UpdateChatDebugLogging updates the runtime admin setting that allows // users to opt into chat debug logging. func (c *Client) UpdateChatDebugLogging(ctx context.Context, req UpdateChatDebugLoggingAllowUsersRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/debug-logging", req) } // GetUserChatDebugLogging returns whether chat debug logging is active // for the current user and whether the user may change it. func (c *Client) GetUserChatDebugLogging(ctx context.Context) (UserChatDebugLoggingSettings, error) { return requestJSON[UserChatDebugLoggingSettings](ctx, c, http.MethodGet, "/api/v2/chats/config/user-debug-logging", http.StatusOK, nil) } // UpdateUserChatDebugLogging updates the current user's chat debug // logging preference. func (c *Client) UpdateUserChatDebugLogging(ctx context.Context, req UpdateUserChatDebugLoggingRequest) error { return requestNoContent(ctx, c, http.MethodPut, "/api/v2/chats/config/user-debug-logging", req) } // GetChatDebugRuns returns the debug runs for a chat. func (c *ExperimentalClient) GetChatDebugRuns(ctx context.Context, chatID uuid.UUID) ([]ChatDebugRunSummary, error) { return requestJSON[[]ChatDebugRunSummary](ctx, c.Client, http.MethodGet, fmt.Sprintf("/api/experimental/chats/%s/debug/runs", chatID), http.StatusOK, nil) } // GetChatDebugRun returns a single debug run along with its full step // history. Use GetChatDebugRuns when only the run summary list is needed. func (c *ExperimentalClient) GetChatDebugRun(ctx context.Context, chatID uuid.UUID, runID uuid.UUID) (ChatDebugRun, error) { return requestJSON[ChatDebugRun](ctx, c.Client, http.MethodGet, fmt.Sprintf("/api/experimental/chats/%s/debug/runs/%s", chatID, runID), http.StatusOK, nil) } // GetChat returns a chat by ID. func (c *Client) GetChat(ctx context.Context, chatID uuid.UUID) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s", chatID), http.StatusOK, nil) } // RefreshChatContext re-pins the chat to its agent's latest context snapshot // and clears the dirty marker. The request takes no body. func (c *Client) RefreshChatContext(ctx context.Context, chatID uuid.UUID) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodPut, fmt.Sprintf("/api/v2/chats/%s/context", chatID), http.StatusOK, nil) } func (c *Client) GetChatACL(ctx context.Context, chatID uuid.UUID) (ChatACL, error) { return requestJSON[ChatACL](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/acl", chatID), http.StatusOK, nil) } func (c *Client) UpdateChatACL(ctx context.Context, chatID uuid.UUID, req UpdateChatACL) error { return requestNoContent(ctx, c, http.MethodPatch, fmt.Sprintf("/api/v2/chats/%s/acl", chatID), req) } // GetChatMessages returns the messages and queued messages for a chat. // ChatMessagesPaginationOptions are optional pagination params for // GetChatMessages. type ChatMessagesPaginationOptions struct { BeforeID int64 // AfterID, when > 0, restricts results to messages with id strictly // greater than AfterID. When set without BeforeID, results come back // in ASCENDING id order so a polling caller can advance its cursor // to max(returned_ids) without gaps. When combined with BeforeID, // results come back in DESC order over the open range // (AfterID, BeforeID). AfterID int64 Limit int } // GetChatMessages returns the messages and queued messages for a chat. func (c *Client) GetChatMessages(ctx context.Context, chatID uuid.UUID, opts *ChatMessagesPaginationOptions) (ChatMessagesResponse, error) { reqOpts := []RequestOption{} if opts != nil { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() if opts.BeforeID > 0 { q.Set("before_id", strconv.FormatInt(opts.BeforeID, 10)) } if opts.AfterID > 0 { q.Set("after_id", strconv.FormatInt(opts.AfterID, 10)) } if opts.Limit > 0 { q.Set("limit", strconv.Itoa(opts.Limit)) } r.URL.RawQuery = q.Encode() }) } return requestJSON[ChatMessagesResponse](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/messages", chatID), http.StatusOK, nil, reqOpts...) } // ChatPromptsOptions are optional query parameters for GetChatPrompts. type ChatPromptsOptions struct { // Limit caps the number of prompts returned. The server enforces a // minimum of 1 and a maximum of 2000; passing 0 (or negative) // applies the server-side default of 500. Limit int } // GetChatPrompts returns the user prompts for a chat in newest-first // order. It is a thin endpoint dedicated to the composer's prompt // history cycle: only user-visible user messages are included, and // only their text parts (concatenated in the original order) are // returned. Whitespace-only prompts are filtered server-side so the // caller never has to skip blank entries while cycling. func (c *Client) GetChatPrompts(ctx context.Context, chatID uuid.UUID, opts *ChatPromptsOptions) (ChatPromptsResponse, error) { reqOpts := []RequestOption{} if opts != nil && opts.Limit > 0 { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() q.Set("limit", strconv.Itoa(opts.Limit)) r.URL.RawQuery = q.Encode() }) } return requestJSON[ChatPromptsResponse](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/prompts", chatID), http.StatusOK, nil, reqOpts...) } // ChatAttachmentsOptions are optional query parameters for // GetChatAttachments. // @typescript-ignore ChatAttachmentsOptions type ChatAttachmentsOptions struct { // BeforeID, when > 0, restricts results to messages with id strictly // less than BeforeID. 0 returns every attachment in the chat. BeforeID int64 } // GetChatAttachments returns the file parts of a chat's user-visible user // and assistant messages, newest first. func (c *Client) GetChatAttachments(ctx context.Context, chatID uuid.UUID, opts *ChatAttachmentsOptions) (ChatAttachmentsResponse, error) { reqOpts := []RequestOption{} if opts != nil && opts.BeforeID > 0 { reqOpts = append(reqOpts, func(r *http.Request) { q := r.URL.Query() q.Set("before_id", strconv.FormatInt(opts.BeforeID, 10)) r.URL.RawQuery = q.Encode() }) } res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/attachments", chatID), nil, reqOpts...) if err != nil { return ChatAttachmentsResponse{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return ChatAttachmentsResponse{}, ReadBodyAsError(res) } var resp ChatAttachmentsResponse return resp, ReadBodyAsJSON(res, &resp) } // UpdateChat patches a chat resource. func (c *Client) UpdateChat(ctx context.Context, chatID uuid.UUID, req UpdateChatRequest) error { return requestNoContent(ctx, c, http.MethodPatch, fmt.Sprintf("/api/v2/chats/%s", chatID), req) } // CreateChatMessage adds a message to a chat. func (c *Client) CreateChatMessage(ctx context.Context, chatID uuid.UUID, req CreateChatMessageRequest) (CreateChatMessageResponse, error) { return requestJSON[CreateChatMessageResponse](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/messages", chatID), http.StatusOK, req) } // EditChatMessage edits an existing user message in a chat and re-runs from there. func (c *Client) EditChatMessage( ctx context.Context, chatID uuid.UUID, messageID int64, req EditChatMessageRequest, ) (EditChatMessageResponse, error) { res, err := c.Request( ctx, http.MethodPatch, fmt.Sprintf("/api/v2/chats/%s/messages/%d", chatID, messageID), req, ) if err != nil { return EditChatMessageResponse{}, err } if res.StatusCode != http.StatusOK { return EditChatMessageResponse{}, ReadBodyAsError(res) } defer res.Body.Close() var resp EditChatMessageResponse return resp, ReadBodyAsJSON(res, &resp) } // EditChatQueuedMessage edits a queued message's content or edit marker. func (c *Client) EditChatQueuedMessage( ctx context.Context, chatID uuid.UUID, queuedMessageID int64, req EditChatQueuedMessageRequest, ) error { res, err := c.Request( ctx, http.MethodPatch, fmt.Sprintf("/api/v2/chats/%s/queue/%d", chatID, queuedMessageID), req, ) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusNoContent { return ReadBodyAsError(res) } return nil } // InterruptChat cancels an in-flight chat run and leaves it waiting. func (c *Client) InterruptChat(ctx context.Context, chatID uuid.UUID) (Chat, error) { res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/interrupt", chatID), nil) if err != nil { return Chat{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return Chat{}, ReadBodyAsError(res) } var chat Chat return chat, ReadBodyAsJSON(res, &chat) } // CompactChat requests a manual context compaction on an idle or // errored chat, clearing any stored error. The compaction runs // asynchronously through the chat worker and bypasses the automatic // usage threshold. func (c *Client) CompactChat(ctx context.Context, chatID uuid.UUID) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/compact", chatID), http.StatusOK, nil) } // ClearChat resets the model context of an idle or errored chat, // clearing any stored error. The reset commits synchronously with no // model call: the transcript is preserved and the next prompt starts // from a fresh context. func (c *Client) ClearChat(ctx context.Context, chatID uuid.UUID) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/clear", chatID), http.StatusOK, nil) } // ReconcileInvalidChatState recovers a chat stuck in an invalid // execution state, moving it into an error state from which the caller // can send a new message or edit history to continue. func (c *Client) ReconcileInvalidChatState(ctx context.Context, chatID uuid.UUID) (Chat, error) { return requestJSON[Chat](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/reconcile-invalid", chatID), http.StatusOK, nil) } // ProposeChatTitleResponse is returned by the propose-title endpoint. type ProposeChatTitleResponse struct { Title string `json:"title"` } // ProposeChatTitle requests the server to generate a suggested chat title without persisting it. func (c *Client) ProposeChatTitle(ctx context.Context, chatID uuid.UUID) (ProposeChatTitleResponse, error) { return requestJSON[ProposeChatTitleResponse](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/title/propose", chatID), http.StatusOK, nil) } // GetChatDiffContents returns resolved diff contents for a chat. // Without options, it returns the primary ref's diff. Pass // WithChatDiffStatusRef to select another ref the chat tracks. func (c *Client) GetChatDiffContents( ctx context.Context, chatID uuid.UUID, opts ...RequestOption, ) (ChatDiffContents, error) { return requestJSON[ChatDiffContents](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/diff", chatID), http.StatusOK, nil, opts...) } // WithChatDiffStatusRef selects the ref for GetChatDiffContents. // The ref must match a ref that the chat tracks. func WithChatDiffStatusRef(ref DiffStatusRef) RequestOption { return func(r *http.Request) { WithQueryParam("origin", ref.RemoteOrigin)(r) WithQueryParam("branch", ref.GitBranch)(r) } } // UploadChatFile uploads a file for use in chat messages. func (c *Client) UploadChatFile(ctx context.Context, organizationID uuid.UUID, contentType string, filename string, rd io.Reader) (UploadChatFileResponse, error) { return requestJSON[UploadChatFileResponse](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/files?organization=%s", organizationID), http.StatusCreated, rd, func(r *http.Request) { r.Header.Set("Content-Type", contentType) if filename != "" { r.Header.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename})) } }) } // ChatFileDownloadURL creates a short-lived download URL for a chat file. func (c *Client) ChatFileDownloadURL(ctx context.Context, fileID uuid.UUID) (ChatFileDownloadURLResponse, error) { return requestJSON[ChatFileDownloadURLResponse](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/files/%s/download-url", fileID), http.StatusOK, nil) } // UploadChatWorkspaceFile streams a file to the chat's workspace // filesystem via the workspace agent. There is no server-imposed size // cap; canceling ctx aborts the stream and no partial target file is // left behind (the agent removes the target when the write fails). func (c *Client) UploadChatWorkspaceFile(ctx context.Context, chatID uuid.UUID, contentType, filename string, rd io.Reader) (UploadChatWorkspaceFileResponse, error) { return requestJSON[UploadChatWorkspaceFileResponse](ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/workspace-files", chatID), http.StatusCreated, rd, func(r *http.Request) { if contentType != "" { r.Header.Set("Content-Type", contentType) } else { // Drop the SDK's default application/json so the server // can sniff-default an undeclared type. r.Header.Del("Content-Type") } if filename != "" { r.Header.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename})) } }) } // GetChatFile retrieves a previously uploaded chat file by ID. func (c *Client) GetChatFile(ctx context.Context, fileID uuid.UUID) ([]byte, string, error) { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/chats/files/%s", fileID), nil) if err != nil { return nil, "", err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, "", ReadBodyAsError(res) } data, err := io.ReadAll(res.Body) if err != nil { return nil, "", err } return data, res.Header.Get("Content-Type"), nil } // SubmitToolResults submits the results of dynamic tool calls for a chat // that is in requires_action status. func (c *Client) SubmitToolResults(ctx context.Context, chatID uuid.UUID, req SubmitToolResultsRequest) error { return requestNoContent(ctx, c, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/tool-results", chatID), req) } // GetChatsByWorkspace returns a mapping of workspace ID to the latest // non-archived chat ID for each requested workspace. Workspaces with // no chats are omitted from the response. func (c *Client) GetChatsByWorkspace(ctx context.Context, workspaceIDs []uuid.UUID) (map[uuid.UUID]uuid.UUID, error) { ids := make([]string, 0, len(workspaceIDs)) for _, id := range workspaceIDs { ids = append(ids, id.String()) } return requestJSON[map[uuid.UUID]uuid.UUID](ctx, c, http.MethodGet, fmt.Sprintf("/api/v2/chats/by-workspace?workspace_ids=%s", strings.Join(ids, ",")), http.StatusOK, nil) }