Documentation
¶
Overview ¶
Package session provides RFB (VNC) client-stream parsing for the sandbox desktop relay.
The relay is transparent — it forwards bytes without rewriting them — but it still has to know where client messages start and end, because idle detection is by message opcode, not by byte count. Byte counting was rejected: the XFCE panel clock, websockify's --heartbeat 30, and ContinuousUpdates all keep bytes flowing forever, so a sandbox would never be judged idle and its TTL would be refreshed indefinitely.
Everything here is a pure function or a small state machine with no I/O, so the parts that are expensive to debug in production are cheap to test.
Package session provides the sandbox desktop WebSocket relay.
Browser (noVNC, no credentials)
⇄ this handler ticket auth, 14-byte handshake skip, opcode relay ⇄ sandbox gateway e2b-traffic-access-token + Authorization: Basic ⇄ websockify :6080 ⇄ x11vnc :5900 (127.0.0.1, -nopw) ⇄ Xvfb ⇄ XFCE
Both credentials exist only on the backend→sandbox hop. The browser holds neither, which is the whole reason traffic is relayed rather than sent direct.
Package session: terminal bridge between the WebSocket and the sandbox PTY (see sandbox_terminal_ws.go for the protocol contract).
Package session: sandbox terminal WebSocket.
GET /api/v1/sessions/:session_id/sandbox/terminal?ticket=<short-lived JWT>
The route is registered before the global auth middleware because a browser WebSocket handshake cannot carry the Authorization header. The browser first POSTs (with a normal Bearer token) for a 2-minute ticket bound to this session and to that access-token's id, then presents the ticket on the handshake. After upgrade the bridge rechecks the bound token, user, workspace membership, and session ownership about once a minute and tears the PTY down on failure.
binary frame client→server raw keystrokes into the PTY binary frame server→client raw PTY output text frame both ways JSON control frames (resize/ping/ready/…)
Index ¶
- func DecodeBase64Attachment(data string) ([]byte, error)
- func MatchApprovedDir(raw string, approved []string) (string, bool)
- type AgentStreamHandler
- type AttachmentProcessor
- type AttachmentUpload
- type CreateKnowledgeQARequest
- type CreateSessionRequest
- type ForkSessionRequest
- type GenerateTitleRequest
- type Handler
- func (h *Handler) AgentQA(c *gin.Context)
- func (h *Handler) BatchDeleteSessions(c *gin.Context)
- func (h *Handler) BrowserSkillAccount(c *gin.Context)
- func (h *Handler) BrowserSkillAuthorize(c *gin.Context)
- func (h *Handler) BrowserSkillConnection(c *gin.Context)
- func (h *Handler) BrowserSkillDownload(c *gin.Context)
- func (h *Handler) BrowserSkillExtension(c *gin.Context)
- func (h *Handler) BrowserSkillInternal(c *gin.Context)
- func (h *Handler) ClearSessionMessages(c *gin.Context)
- func (h *Handler) ContinueStream(c *gin.Context)
- func (h *Handler) CreateSession(c *gin.Context)
- func (h *Handler) DeleteLibraryArtifact(c *gin.Context)
- func (h *Handler) DeleteMessageArtifact(c *gin.Context)
- func (h *Handler) DeleteSession(c *gin.Context)
- func (h *Handler) DeleteSteerMessage(c *gin.Context)
- func (h *Handler) DeleteTemporaryDocument(c *gin.Context)
- func (h *Handler) DownloadMessageArtifact(c *gin.Context)
- func (h *Handler) ForkSession(c *gin.Context)
- func (h *Handler) GenerateTitle(c *gin.Context)
- func (h *Handler) GetSession(c *gin.Context)
- func (h *Handler) GetSessionsByTenant(c *gin.Context)
- func (h *Handler) GetTemporaryDocument(c *gin.Context)
- func (h *Handler) IssueSandboxDesktopTicket(c *gin.Context)
- func (h *Handler) IssueSandboxTerminalTicket(c *gin.Context)
- func (h *Handler) KnowledgeQA(c *gin.Context)
- func (h *Handler) ListArtifactLibrary(c *gin.Context)
- func (h *Handler) ListMessageArtifacts(c *gin.Context)
- func (h *Handler) ListSessionArtifacts(c *gin.Context)
- func (h *Handler) ListSteerMessages(c *gin.Context)
- func (h *Handler) ListTemporaryDocuments(c *gin.Context)
- func (h *Handler) PinSession(c *gin.Context)
- func (h *Handler) PreviewTemporaryDocument(c *gin.Context)
- func (h *Handler) PromoteSteerMessage(c *gin.Context)
- func (h *Handler) ReportSandboxDesktopActivity(c *gin.Context)
- func (h *Handler) RewindSession(c *gin.Context)
- func (h *Handler) SandboxDesktopWS(c *gin.Context)
- func (h *Handler) SandboxTerminalWS(c *gin.Context)
- func (h *Handler) SearchKnowledge(c *gin.Context)
- func (h *Handler) SteerMessage(c *gin.Context)
- func (h *Handler) StopSession(c *gin.Context)
- func (h *Handler) UnpinSession(c *gin.Context)
- func (h *Handler) UpdateSession(c *gin.Context)
- func (h *Handler) UploadTemporaryDocument(c *gin.Context)
- type HostProjectDirsLoader
- type ImageAttachment
- type MentionedItemRequest
- type RewindSessionRequest
- type SandboxIDLookup
- type SearchKnowledgeRequest
- type SteerMessageRequest
- type StopSessionRequest
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DecodeBase64Attachment ¶
DecodeBase64Attachment decodes a base64 attachment payload, stripping any data URI prefix. Tries Std, URL, RawStd, and RawURL encodings in order.
func MatchApprovedDir ¶
MatchApprovedDir reports whether raw names a directory the user approved through the native picker, returning it cleaned.
This is the authorization boundary for host workspaces, so it lives here rather than in localsandbox: session create runs in every edition, and the desktop-only sandbox must not be imported by cmd/server.
Matching is exact after Clean. A subdirectory of an approved directory is not approved: the user picked a root, and honouring children would let a stored value reach deeper than the picker ever offered.
Types ¶
type AgentStreamHandler ¶
type AgentStreamHandler struct {
// contains filtered or unexported fields
}
AgentStreamHandler handles agent events for SSE streaming It uses a dedicated EventBus per request to avoid SessionID filtering Events are appended to StreamManager without accumulation
func NewAgentStreamHandler ¶
func NewAgentStreamHandler( ctx context.Context, sessionID, assistantMessageID, requestID string, tenantID uint64, receivedAt time.Time, assistantMessage *types.Message, streamManager interfaces.StreamManager, eventBus *event.EventBus, artifactCollector *service.ArtifactCollector, checkpointer *service.WorkspaceCheckpointer, sandboxIDLookup SandboxIDLookup, ) *AgentStreamHandler
NewAgentStreamHandler creates a new handler for agent SSE streaming
func (*AgentStreamHandler) Subscribe ¶
func (h *AgentStreamHandler) Subscribe()
Subscribe subscribes to all agent streaming events on the dedicated EventBus No SessionID filtering needed since we have a dedicated EventBus per request
type AttachmentProcessor ¶
type AttachmentProcessor struct {
// contains filtered or unexported fields
}
AttachmentProcessor saves uploaded file attachments and extracts their text content for injection into the LLM prompt.
func NewAttachmentProcessor ¶
func NewAttachmentProcessor( fileService interfaces.FileService, documentReader interfaces.DocumentReader, imageResolver *docparser.ImageResolver, modelService interfaces.ModelService, ) *AttachmentProcessor
NewAttachmentProcessor creates an AttachmentProcessor with the given dependencies.
func (*AttachmentProcessor) ProcessAttachment ¶
func (p *AttachmentProcessor) ProcessAttachment( ctx context.Context, data []byte, fileName string, fileSize int64, tenantID uint64, asrModelID string, ) (*types.MessageAttachment, error)
ProcessAttachment validates, saves, and extracts content from a single uploaded file. Content extraction is attempted for all supported types; errors are non-fatal (logged as warnings).
type AttachmentUpload ¶
type AttachmentUpload struct {
Data string `json:"data"` // Base64-encoded file content
FileName string `json:"file_name"` // Original filename
FileSize int64 `json:"file_size"` // File size in bytes
}
AttachmentUpload represents a file attachment upload from the client
type CreateKnowledgeQARequest ¶
type CreateKnowledgeQARequest struct {
// Query text; may be empty only when an image or file is attached
Query string `json:"query"`
// Selected knowledge base ID for this request
KnowledgeBaseIDs []string `json:"knowledge_base_ids"`
// Selected knowledge ID for this request
KnowledgeIDs []string `json:"knowledge_ids"`
// Whether agent mode is enabled for this request
AgentEnabled bool `json:"agent_enabled"`
// Selected custom agent ID (backend resolves shared agent and its workspace from share relation)
AgentID string `json:"agent_id"`
// Optional disambiguator; backend still verifies the share relation
AgentSourceTenantID uint64 `json:"agent_source_tenant_id,omitempty"`
// Browser source
LocalBrowserEnabled bool `json:"local_browser_enabled"`
// Whether web search is enabled for this request
WebSearchEnabled bool `json:"web_search_enabled"`
// Optional summary model ID for this request (overrides session default)
SummaryModelID string `json:"summary_model_id"`
// Per-request MCP services selected via @mention
MCPServiceIDs []string `json:"mcp_service_ids"`
// Per-request Skills selected via @mention
SkillNames []string `json:"skill_names"`
// @mentioned tag IDs (display/debug; scoped via MentionedItems)
TagIDs []string `json:"tag_ids"`
// @mentioned knowledge bases and files
MentionedItems []MentionedItemRequest `json:"mentioned_items"`
// Whether to disable auto title generation
DisableTitle bool `json:"disable_title"`
// Attached images for multimodal chat
Images []ImageAttachment `json:"images"`
// Attached files (documents, audio, etc.)
AttachmentUploads []AttachmentUpload `json:"attachment_uploads,omitempty"`
// Pre-uploaded session-scoped document IDs
AttachmentIDs []string `json:"attachment_ids,omitempty"`
// Source channel: "web", "api", "im", etc.
Channel string `json:"channel"`
SuggestionAttribution *types.SuggestionAttribution `json:"suggestion_attribution,omitempty"`
// QuestionOrigin is the knowledge source of a picked suggested question.
QuestionOrigin *types.QuestionOrigin `json:"question_origin,omitempty"`
// ReasoningEffort overrides thinking for this request; empty inherits the agent configuration.
ReasoningEffort string `json:"reasoning_effort,omitempty"`
}
CreateKnowledgeQARequest defines the request structure for knowledge QA
type CreateSessionRequest ¶
type CreateSessionRequest struct {
// Title for the session (optional)
Title string `json:"title"`
// Description for the session (optional)
Description string `json:"description"`
// ProjectDir is an optional Lite host-sandbox binding. When set it must
// be an absolute path already present in the user-approved ProjectDirs
// list. Empty means the session gets an auto-allocated workspace.
ProjectDir string `json:"project_dir,omitempty"`
}
CreateSessionRequest represents a request to create a new session Sessions are now knowledge-base-independent and serve as conversation containers. All configuration (knowledge bases, model settings, etc.) comes from custom agent at query time.
type ForkSessionRequest ¶
type ForkSessionRequest struct {
// MessageID is the message to branch at. A user message copies history
// strictly before it (the client prefills that question). An assistant
// message copies history through that answer so the branch continues after it.
MessageID string `json:"message_id" binding:"required"`
// Title is optional. Empty falls back to the source title plus a suffix.
Title string `json:"title"`
}
ForkSessionRequest is the fork endpoint's body.
type GenerateTitleRequest ¶
type GenerateTitleRequest struct {
Messages []types.Message `json:"messages" binding:"required"` // Messages to use as context for title generation
}
GenerateTitleRequest defines the request structure for generating a session title
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler handles all HTTP requests related to conversation sessions
func NewHandler ¶
func NewHandler( sessionService interfaces.SessionService, messageService interfaces.MessageService, suggestionService interfaces.MessageSuggestionService, streamManager interfaces.StreamManager, config *config.Config, knowledgebaseService interfaces.KnowledgeBaseService, customAgentService interfaces.CustomAgentService, tenantService interfaces.TenantService, agentShareService interfaces.AgentShareService, kbShareService interfaces.KBShareService, fileService interfaces.FileService, resourceCatalog interfaces.ResourceCatalog, storageResolver interfaces.StorageBackendResolver, modelService interfaces.ModelService, documentReader interfaces.DocumentReader, imageResolver *docparser.ImageResolver, temporaryDocuments interfaces.TemporaryDocumentService, artifactCollector *service.ArtifactCollector, workspaceCheckpointer *service.WorkspaceCheckpointer, sandboxIDLookup SandboxIDLookup, memoryService interfaces.MemoryService, userService interfaces.UserService, memberService interfaces.TenantMemberService, terminalService *service.SandboxTerminalService, browserSkill *browserskill.Manager, desktopService *service.SandboxDesktopService, desktopTickets service.SandboxDesktopTicketStore, desktopLast service.SandboxDesktopLastStore, rdb *redis.Client, forkService *service.SessionForkService, rewindService *service.SessionRewindService, approvedProjectDirs HostProjectDirsLoader, ) *Handler
NewHandler creates a new instance of Handler with all necessary dependencies
func (*Handler) AgentQA ¶
AgentQA godoc @Summary Agent问答 @Description 基于Agent的智能问答,支持多轮对话和SSE流式响应 @Tags 问答 @Accept json @Produce text/event-stream @Param session_id path string true "会话ID" @Param request body CreateKnowledgeQARequest true "问答请求" @Param resource_urls query string false "文件引用形式,public 返回可加载直链" Enums(handle, public) default(handle) @Success 200 {object} map[string]interface{} "问答结果(SSE流)" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /agent-chat/{session_id} [post]
func (*Handler) BatchDeleteSessions ¶
BatchDeleteSessions godoc @Summary 批量删除会话 @Description 根据ID列表批量删除对话会话,或设置 delete_all=true 删除当前空间的所有会话 @Tags 会话 @Accept json @Produce json @Param request body batchDeleteRequest true "批量删除请求" @Success 200 {object} map[string]interface{} "删除结果" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /sessions/batch [delete]
func (*Handler) BrowserSkillAccount ¶
BrowserSkillAccount manages this member's shared browser connection independently of any conversation. Normal authentication and tenant membership apply upstream.
func (*Handler) BrowserSkillAuthorize ¶
BrowserSkillAuthorize exchanges one-use pairing and renewable device credentials.
func (*Handler) BrowserSkillConnection ¶
BrowserSkillConnection exposes task status and user controls for an owned conversation.
func (*Handler) BrowserSkillDownload ¶
BrowserSkillDownload serves the extension archive for installation.
func (*Handler) BrowserSkillExtension ¶
BrowserSkillExtension upgrades a device-authenticated extension connection.
func (*Handler) BrowserSkillInternal ¶
BrowserSkillInternal forwards signed requests to the owning node's browser gateway.
func (*Handler) ClearSessionMessages ¶
ClearSessionMessages godoc @Summary 清空会话消息 @Description 删除会话中的所有消息,同时清除 LLM 上下文和聊天历史知识库条目。会话本身保留。 @Tags 会话 @Accept json @Produce json @Param id path string true "会话ID" @Success 200 {object} map[string]interface{} "清空成功" @Failure 400 {object} errors.AppError "请求参数错误" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id}/messages [delete]
func (*Handler) ContinueStream ¶
ContinueStream godoc @Summary 继续流式响应 @Description 继续获取正在进行的流式响应 @Tags 问答 @Accept json @Produce text/event-stream @Param session_id path string true "会话ID" @Param message_id query string true "消息ID" @Param resource_urls query string false "文件引用形式,public 返回可加载直链" Enums(handle, public) default(handle) @Success 200 {object} map[string]interface{} "流式响应" @Failure 404 {object} errors.AppError "会话或消息不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/continue-stream/{session_id} [get]
func (*Handler) CreateSession ¶
CreateSession godoc @Summary 创建会话 @Description 创建新的对话会话 @Tags 会话 @Accept json @Produce json @Param request body CreateSessionRequest true "会话创建请求" @Success 201 {object} map[string]interface{} "创建的会话" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /sessions [post]
func (*Handler) DeleteLibraryArtifact ¶
DeleteLibraryArtifact godoc @Summary 删除产物库中的文件 @Description 删除产物库中的一个文件。产物库一行代表一个文件的最新版本,默认连同该文件在本会话中的所有历史版本一并删除 @Tags 会话 @Produce json @Param session_id query string true "会话ID" @Param message_id query string true "消息ID" @Param index query int true "产物在消息中的位置" @Param all_versions query bool false "是否删除所有版本,默认 true" @Success 200 {object} map[string]interface{} @Failure 404 {object} errors.AppError @Security Bearer @Router /artifacts [delete]
Versions default to being deleted together here: the library row IS the file, with a version count rather than a row per regeneration, so deleting one version would leave the entry in place showing the previous one.
func (*Handler) DeleteMessageArtifact ¶
DeleteMessageArtifact godoc @Summary 删除会话中的产物文件 @Description 删除某条 assistant 消息生成的单个文件;all_versions=true 时连同本会话中该文件的所有历史版本一并删除 @Tags 会话 @Produce json @Param session_id path string true "会话ID" @Param message_id path string true "消息ID" @Param index path int true "产物在消息中的位置" @Param all_versions query bool false "是否删除该文件的所有版本" @Success 200 {object} map[string]interface{} @Failure 404 {object} errors.AppError @Security Bearer @Router /sessions/{session_id}/messages/{message_id}/artifacts/{index} [delete]
Addressed by the same (message, index) coordinates as the download endpoint, so a client deletes exactly the row it was showing. The in-chat panel lists every version separately and omits all_versions; the artifact library folds versions into one entry and sets it.
func (*Handler) DeleteSession ¶
DeleteSession godoc @Summary 删除会话 @Description 删除指定的会话 @Tags 会话 @Accept json @Produce json @Param id path string true "会话ID" @Success 200 {object} map[string]interface{} "删除成功" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id} [delete]
func (*Handler) DeleteSteerMessage ¶
DeleteSteerMessage godoc @Summary 删除一条排队中的消息 @Description 从当前运行的排队列表里去掉一条,不再注入也不再作为 follow-up 发出。 @Tags 问答 @Produce json @Param id path string true "会话 ID" @Param steer_id path string true "排队消息 ID" @Success 200 {object} map[string]interface{} @Failure 404 {object} errors.AppError @Failure 503 {object} errors.AppError "活 turn 查询失败,可重试" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id}/steer/{steer_id} [delete]
func (*Handler) DeleteTemporaryDocument ¶
func (*Handler) DownloadMessageArtifact ¶
DownloadMessageArtifact streams a single skill-generated file to the client. Clients reference the artifact by its position (:index) in the assistant message's Artifacts array; the storage URL never leaves the server so callers cannot pivot to arbitrary blobs.
@Router /sessions/{session_id}/messages/{message_id}/artifacts/{index}/download [get]
func (*Handler) ForkSession ¶
ForkSession godoc @Summary 分叉会话 @Description 从指定的用户或助手消息处分叉出一个新会话。用户消息:复制其之前的历史并预填该问题;助手消息:复制含该回答在内的历史,从该轮沙箱状态继续。 @Tags 会话 @Accept json @Produce json @Param session_id path string true "源会话 ID" @Param request body ForkSessionRequest true "分叉请求" @Success 200 {object} map[string]interface{} "新会话" @Failure 400 {object} errors.AppError "请求参数错误 / 分叉点角色不支持" @Failure 404 {object} errors.AppError "会话或消息不存在" @Failure 409 {object} errors.AppError "源会话正在生成中" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/fork [post]
func (*Handler) GenerateTitle ¶
GenerateTitle godoc @Summary 生成会话标题 @Description 根据消息内容自动生成会话标题 @Tags 会话 @Accept json @Produce json @Param session_id path string true "会话ID" @Param request body GenerateTitleRequest true "生成请求" @Success 200 {object} map[string]interface{} "生成的标题" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/title [post]
func (*Handler) GetSession ¶
GetSession godoc @Summary 获取会话详情 @Description 根据ID获取会话详情 @Tags 会话 @Accept json @Produce json @Param id path string true "会话ID" @Success 200 {object} map[string]interface{} "会话详情" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id} [get]
func (*Handler) GetSessionsByTenant ¶
GetSessionsByTenant godoc @Summary 获取会话列表 @Description 获取当前空间的会话列表,支持分页、关键字搜索、按来源/Agent 筛选 @Tags 会话 @Accept json @Produce json @Param page query int false "页码" @Param page_size query int false "每页数量" @Param keyword query string false "标题模糊搜索" @Param source query string false "来源过滤:web / embed / api / feishu / wechat / slack / ...(api、embed、IM 渠道需 Admin+)" @Param agent_id query string false "按 Agent 过滤(仅对 IM 会话生效)" @Success 200 {object} map[string]interface{} "会话列表" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /sessions [get]
func (*Handler) GetTemporaryDocument ¶
func (*Handler) IssueSandboxDesktopTicket ¶
IssueSandboxDesktopTicket mints a one-shot handshake ticket. The access JWT stays on this authenticated POST and never reaches the WS URL.
func (*Handler) IssueSandboxTerminalTicket ¶
IssueSandboxTerminalTicket mints a short-lived handshake ticket for the WebSocket. The access JWT stays on this authenticated POST and never appears in the WS URL (or nginx access logs of that handshake).
func (*Handler) KnowledgeQA ¶
KnowledgeQA godoc @Summary 知识问答 @Description 基于知识库的问答(使用LLM总结),支持SSE流式响应 @Tags 问答 @Accept json @Produce text/event-stream @Param session_id path string true "会话ID" @Param request body CreateKnowledgeQARequest true "问答请求" @Param resource_urls query string false "文件引用形式,public 返回可加载直链" Enums(handle, public) default(handle) @Success 200 {object} map[string]interface{} "问答结果(SSE流)" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /knowledge-chat/{session_id} [post]
func (*Handler) ListArtifactLibrary ¶
ListArtifactLibrary godoc @Summary 列出我的产物 @Description 跨会话列出当前用户可见会话中技能生成的文件,每个文件只返回最新版本(不含存储 URL) @Tags 会话 @Produce json @Param keyword query string false "按文件名过滤" @Param file_types query string false "逗号分隔的扩展名,如 .pdf,.pptx" @Param page query int false "页码" @Param page_size query int false "每页数量(最大 100)" @Success 200 {object} map[string]interface{} @Security Bearer @Security ApiKeyAuth @Router /artifacts [get]
Items carry session_id, message_id and index so the client downloads through the per-session endpoint, which re-runs the ownership check.
func (*Handler) ListMessageArtifacts ¶
ListMessageArtifacts returns just the artifacts attached to a single assistant message. Used by the "download files from this reply" button on each bot message.
Same-tenant/same-owner check flows through h.sessionService.GetSession exactly like ListSessionArtifacts.
@Router /sessions/{session_id}/messages/{message_id}/artifacts [get]
func (*Handler) ListSessionArtifacts ¶
ListSessionArtifacts godoc @Summary 列出会话生成的产物文件 @Description 返回本会话中所有 assistant 消息产生的技能产物元数据(不含 URL) @Tags 会话 @Produce json @Param session_id path string true "会话ID" @Success 200 {object} map[string]interface{} @Failure 404 {object} errors.AppError @Security Bearer @Router /sessions/{session_id}/artifacts [get]
The endpoint powers the drawer that lists every file generated in the session; it does NOT return the storage URL (only names/sizes/mtimes), so clients cannot reach around the download endpoint by reading a provider:// path from the API response.
Deleted artifacts are skipped but still consume their index: the index IS the download address, so renumbering around a tombstone would point old links at the wrong file.
func (*Handler) ListSteerMessages ¶
ListSteerMessages godoc @Summary 列出当前运行中尚未消费的排队消息 @Description 刷新页面后用来恢复输入框上方的队列。没有正在运行的 turn 时返回空列表。 @Tags 问答 @Produce json @Param id path string true "会话 ID" @Success 200 {object} map[string]interface{} @Failure 404 {object} errors.AppError @Failure 503 {object} errors.AppError "活 turn 查询失败,可重试" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id}/steer [get]
func (*Handler) ListTemporaryDocuments ¶
func (*Handler) PinSession ¶
PinSession godoc @Summary 置顶会话 @Description 将指定会话置顶(用户维度) @Tags 会话 @Produce json @Param session_id path string true "会话ID" @Success 200 {object} map[string]interface{} "置顶成功" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/pin [post]
func (*Handler) PreviewTemporaryDocument ¶
func (*Handler) PromoteSteerMessage ¶
PromoteSteerMessage godoc @Summary 将排队消息改为立即注入 @Description 把一条 delivery=after 的排队消息改为 inject,运行中的 agent 会在下一轮边界读到它。 @Tags 问答 @Produce json @Param session_id path string true "会话 ID" @Param steer_id path string true "排队消息 ID" @Success 200 {object} map[string]interface{} "queued | new_run" @Failure 400 {object} errors.AppError @Failure 404 {object} errors.AppError @Failure 503 {object} errors.AppError "活 turn 查询失败,可重试" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/steer/{steer_id}/inject [post]
func (*Handler) ReportSandboxDesktopActivity ¶
ReportSandboxDesktopActivity records browser-side key/mouse activity.
It is the fallback the RFB parser degrades to on an unknown opcode, and a no-op when the parser is healthy. It is deliberately not the primary signal: a client that simply stops posting would keep the sandbox alive for free.
func (*Handler) RewindSession ¶
RewindSession godoc @Summary 回滚会话 @Description 回滚当前会话到指定用户或助手消息:删除其后消息,可到达时 git reset 工作区。 @Tags 会话 @Accept json @Produce json @Param session_id path string true "会话 ID" @Param request body RewindSessionRequest true "回滚请求" @Success 200 {object} map[string]interface{} "回滚结果" @Failure 400 {object} errors.AppError "请求参数错误 / 回滚点角色不支持" @Failure 404 {object} errors.AppError "会话或消息不存在" @Failure 409 {object} errors.AppError "会话正在生成中 / 无检查点 / 沙箱已更换" @Failure 500 {object} errors.AppError "工作区回滚失败" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/rewind [post]
func (*Handler) SandboxDesktopWS ¶
SandboxDesktopWS relays the browser's RFB stream to the session sandbox.
Ticket, auth, and the exclusive slot stay HTTP because those run before Ensure. Overlay states from Ensure (SANDBOX_NOT_BOUND / SANDBOX_PAUSED / DESKTOP_UNSUPPORTED / DESKTOP_START_FAILED) must upgrade and then close with a reason: the JS WebSocket API never exposes a failed handshake's HTTP status, so a 409 here becomes a generic error overlay instead of the confirm card. Handshake prelude failures stay HTTP — by then the sandbox is already live and the frontend must not show "create sandbox".
func (*Handler) SandboxTerminalWS ¶
SandboxTerminalWS upgrades a browser connection into a session sandbox terminal. See the file header for the protocol.
func (*Handler) SearchKnowledge ¶
SearchKnowledge godoc @Summary 知识搜索 @Description 在知识库中搜索(不使用LLM总结)。与产品内问答使用同一检索流程(召回、rerank、合并),外部检索首选;可覆盖召回参数与 rerank @Tags 问答 @Accept json @Produce json @Param request body SearchKnowledgeRequest true "搜索请求" @Param resource_urls query string false "文件引用形式,public 返回可加载直链" Enums(handle, public) default(handle) @Success 200 {object} map[string]interface{} "搜索结果" @Failure 400 {object} errors.AppError "请求参数错误" @Security Bearer @Security ApiKeyAuth @Router /knowledge-search [post]
func (*Handler) SteerMessage ¶
SteerMessage godoc @Summary 向运行中的对话追加消息 @Description 向运行中的 agent turn 追加用户消息(after 排队 / inject 注入)。无活 turn 时返回 new_run。 @Tags 问答 @Accept json @Produce json @Param session_id path string true "会话 ID" @Param request body SteerMessageRequest true "追加消息" @Success 200 {object} map[string]interface{} "queued | new_run" @Failure 400 {object} errors.AppError "请求参数错误" @Failure 404 {object} errors.AppError "会话不存在" @Failure 503 {object} errors.AppError "活 turn 查询失败,可重试" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/steer [post]
func (*Handler) StopSession ¶
StopSession godoc @Summary 停止生成 @Description 停止当前正在进行的生成任务 @Tags 问答 @Accept json @Produce json @Param session_id path string true "会话ID" @Param request body StopSessionRequest true "停止请求" @Success 200 {object} map[string]interface{} "停止成功" @Failure 404 {object} errors.AppError "会话或消息不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{session_id}/stop [post]
func (*Handler) UnpinSession ¶
UnpinSession godoc @Summary 取消置顶会话 @Description 取消指定会话的置顶 @Tags 会话 @Produce json @Param id path string true "会话ID" @Success 200 {object} map[string]interface{} "取消置顶成功" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id}/pin [delete]
func (*Handler) UpdateSession ¶
UpdateSession godoc @Summary 更新会话 @Description 更新会话属性 @Tags 会话 @Accept json @Produce json @Param id path string true "会话ID" @Param request body types.Session true "会话信息" @Success 200 {object} map[string]interface{} "更新后的会话" @Failure 404 {object} errors.AppError "会话不存在" @Security Bearer @Security ApiKeyAuth @Router /sessions/{id} [put]
func (*Handler) UploadTemporaryDocument ¶
UploadTemporaryDocument accepts one multipart file and immediately returns a session-scoped document ID. Parsing continues in the document worker.
type HostProjectDirsLoader ¶
type HostProjectDirsLoader func() []string
HostProjectDirsLoader returns the user-approved project directories from desktop prefs. Nil means no directory is approved.
type ImageAttachment ¶
type ImageAttachment struct {
Data string `json:"data,omitempty"` // base64 data URI from frontend (data:image/png;base64,...)
URL string `json:"url,omitempty"` // serving URL after saving to storage
Caption string `json:"caption,omitempty"` // VLM analysis result (context-aware, single call)
}
ImageAttachment represents an image in a chat request. Frontend sends base64 data in the Data field; the backend saves, runs VLM analysis, and populates URL/Caption before proceeding with the chat pipeline.
type MentionedItemRequest ¶
type MentionedItemRequest struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"` // "kb", "file", "tag", "mcp", "skill"
KBType string `json:"kb_type"` // "document" or "faq" (only for kb type)
KBID string `json:"kb_id"` // Parent knowledge base for file/tag mentions
KBName string `json:"kb_name"` // Display name for parent KB
ServiceID string `json:"service_id"` // Parent MCP service for MCP tool mentions
SkillName string `json:"skill_name"` // Preloaded agent skill name
}
MentionedItemRequest represents a mentioned item in the request
type RewindSessionRequest ¶
type RewindSessionRequest struct {
// MessageID is the message to rewind to. A user message deletes itself and
// everything after (the client prefills that question). An assistant
// message keeps itself and deletes everything after.
MessageID string `json:"message_id" binding:"required"`
}
RewindSessionRequest is the rewind endpoint's body.
type SandboxIDLookup ¶
type SandboxIDLookup interface {
BoundSandboxID(ctx context.Context, sessionID string) (string, bool)
}
SandboxIDLookup reports the sandbox currently bound to a session without provisioning one. A session with no live sandbox reports ok=false, which the completion path treats as "nothing to check point".
type SearchKnowledgeRequest ¶
type SearchKnowledgeRequest struct {
Query string `json:"query" binding:"required"` // Query text to search for
KnowledgeBaseID string `json:"knowledge_base_id"` // Single knowledge base ID (for backward compatibility)
KnowledgeBaseIDs []string `json:"knowledge_base_ids"` // IDs of knowledge bases to search (multi-KB support)
KnowledgeIDs []string `json:"knowledge_ids"` // IDs of specific knowledge (files) to search
TagIDs []string `json:"tag_ids"` // Tag IDs for filtering within a single KB
MentionedItems []MentionedItemRequest `json:"mentioned_items"` // Optional scoped tag mentions
// Optional overrides of the tenant retrieval config. Omitted fields keep it.
VectorThreshold *float64 `json:"vector_threshold,omitempty"` // Minimum vector similarity
KeywordThreshold *float64 `json:"keyword_threshold,omitempty"` // Minimum keyword score
MatchCount int `json:"match_count,omitempty"` // Number of results to return
DisableKeywordsMatch bool `json:"disable_keywords_match,omitempty"` // Vector recall only
DisableVectorMatch bool `json:"disable_vector_match,omitempty"` // Keyword recall only
Rerank *types.RerankOptions `json:"rerank,omitempty"` // Rerank override
}
SearchKnowledgeRequest defines the request structure for searching knowledge without LLM summarization
type SteerMessageRequest ¶
type SteerMessageRequest struct {
// Optional for older clients. New clients pin delivery to the run they see
// and supply a stable ID so a consume event may precede the HTTP response.
ExpectedAssistantMessageID string `json:"expected_assistant_message_id,omitempty"`
SteerID string `json:"steer_id,omitempty"`
Query string `json:"query" binding:"required"`
MentionedItems []MentionedItemRequest `json:"mentioned_items,omitempty"`
Channel string `json:"channel,omitempty"`
// Delivery is "after" (default) or "inject". See the constants above.
Delivery string `json:"delivery,omitempty"`
}
SteerMessageRequest is the payload of POST /sessions/:session_id/steer.
type StopSessionRequest ¶
type StopSessionRequest struct {
MessageID string `json:"message_id" binding:"required"`
}
StopSessionRequest represents the stop session request
Source Files
¶
- agent_stream_handler.go
- artifact_delete.go
- artifact_download.go
- artifact_library.go
- artifact_reference.go
- artifact_storage.go
- attachment_processor.go
- browserskill.go
- continue_stream_coalesce.go
- fork.go
- handler.go
- helpers.go
- image_upload.go
- project_dir.go
- qa.go
- quick_answer_timeline.go
- resource_urls.go
- rewind.go
- sandbox_desktop_rfb.go
- sandbox_desktop_ws.go
- sandbox_terminal_bridge.go
- sandbox_terminal_ws.go
- steer.go
- stream.go
- temporary_document.go
- title.go
- types.go
- wiki_fixer_scope.go