package acp import "encoding/json" // ProtocolVersion is the version of the Agent Client Protocol this client // speaks. It is a single integer, and both sides have to agree on one. const ProtocolVersion = 1 // The methods this client calls on an agent. const ( MethodInitialize = "initialize" MethodNewSession = "session/new" MethodPrompt = "session/prompt" MethodCancel = "session/cancel" ) // The methods an agent calls on this client. const ( MethodUpdate = "session/update" MethodRequestPermission = "session/request_permission" MethodReadTextFile = "fs/read_text_file" MethodWriteTextFile = "fs/write_text_file" ) // Implementation is what one side of the conversation calls itself. An agent // logs it, so it is worth being truthful in. type Implementation struct { Name string `json:"name"` Title string `json:"title,omitempty"` Version string `json:"version,omitempty"` } // FileSystemCapability says which file operations the editor will perform on // the agent's behalf. type FileSystemCapability struct { ReadTextFile bool `json:"readTextFile"` WriteTextFile bool `json:"writeTextFile"` } // ClientCapabilities is what this editor offers an agent. // // Terminal is deliberately absent rather than false-and-present: an agent can // already have a shell through its own toolsets, and advertising this one would // mean the editor running commands on the agent's behalf and owning the output. type ClientCapabilities struct { FS FileSystemCapability `json:"fs"` } // InitializeParams opens the conversation. type InitializeParams struct { ProtocolVersion int `json:"protocolVersion"` ClientCapabilities ClientCapabilities `json:"clientCapabilities"` ClientInfo Implementation `json:"clientInfo"` } // AuthMethod is one way an agent offers to be logged in to. type AuthMethod struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` } // InitializeResult is what the agent answers with. // // AgentCapabilities is kept encoded: the set grows with the protocol, this // client acts on one corner of it, and decoding the whole into a struct would // turn every new capability into a field nobody reads. The corner it does act // on is read out by PromptCapabilities. type InitializeResult struct { ProtocolVersion int `json:"protocolVersion"` AgentCapabilities json.RawMessage `json:"agentCapabilities,omitempty"` AgentInfo Implementation `json:"agentInfo"` AuthMethods []AuthMethod `json:"authMethods,omitempty"` } // PromptCapabilities is what an agent accepts inside a prompt beyond plain // text. EmbeddedContext is the one that matters here: it says a file named // with "@" may be sent with its text inside the prompt rather than as a link // the agent has to follow. type PromptCapabilities struct { EmbeddedContext bool `json:"embeddedContext"` Image bool `json:"image"` } // PromptCapabilities returns the prompt capabilities the agent declared, and // the zero value — nothing beyond text — when it declared none. func (r InitializeResult) PromptCapabilities() PromptCapabilities { var capabilities struct { Prompt PromptCapabilities `json:"promptCapabilities"` } if len(r.AgentCapabilities) > 0 { _ = json.Unmarshal(r.AgentCapabilities, &capabilities) } return capabilities.Prompt } // NewSessionParams starts a conversation in a directory. // // McpServers is always empty and is still sent: the field is required, and the // MCP servers an agent talks to are its own configuration's business. type NewSessionParams struct { Cwd string `json:"cwd"` McpServers []any `json:"mcpServers"` } // NewSessionResult carries the id every later message quotes. type NewSessionResult struct { SessionID string `json:"sessionId"` } // The kinds of content block this editor sends. An agent may send others // back — image, audio — and those are drawn as nothing rather than refused. const ( ContentText = "text" ContentResourceLink = "resource_link" ContentResource = "resource" ) // ContentBlock is one piece of a message. // // Three shapes share the struct, told apart by Type: text carries Text; a // resource_link carries URI, Name and MimeType and points at a file the agent // fetches for itself; a resource carries the file's text inside Resource, for // an agent that accepts embedded context. The last two are how a file named // with "@" in the box reaches the agent. type ContentBlock struct { Type string `json:"type"` Text string `json:"text,omitempty"` URI string `json:"uri,omitempty"` Name string `json:"name,omitempty"` MimeType string `json:"mimeType,omitempty"` Resource *EmbeddedResource `json:"resource,omitempty"` } // EmbeddedResource is a file's text carried inside the prompt. type EmbeddedResource struct { URI string `json:"uri"` MimeType string `json:"mimeType,omitempty"` Text string `json:"text"` } // Text returns the block's text, and "" for a block that carries none. func (c ContentBlock) String() string { return c.Text } // PromptParams sends one turn's worth of input. type PromptParams struct { SessionID string `json:"sessionId"` Prompt []ContentBlock `json:"prompt"` } // The reasons a turn ends. const ( StopEndTurn = "end_turn" StopMaxTokens = "max_tokens" StopMaxTurnRequests = "max_turn_requests" StopRefusal = "refusal" StopCancelled = "cancelled" ) // PromptResult says why the turn ended. type PromptResult struct { StopReason string `json:"stopReason"` } // CancelParams interrupts a turn. It is a notification: there is nothing to // answer. type CancelParams struct { SessionID string `json:"sessionId"` } // The kinds of session/update an agent sends. const ( UpdateAgentMessage = "agent_message_chunk" UpdateAgentThought = "agent_thought_chunk" UpdateUserMessage = "user_message_chunk" UpdateToolCall = "tool_call" UpdateToolCallDone = "tool_call_update" UpdatePlan = "plan" UpdateCommands = "available_commands_update" UpdateUsage = "usage_update" UpdateCurrentMode = "current_mode_update" ) // The statuses a tool call moves through. const ( StatusPending = "pending" StatusInProgress = "in_progress" StatusCompleted = "completed" StatusFailed = "failed" ) // PlanEntry is one line of a plan the agent published. type PlanEntry struct { Content string `json:"content"` Priority string `json:"priority,omitempty"` Status string `json:"status,omitempty"` } // Command is one thing the agent says it can be asked to do: a slash command, // typed as "/name" at the start of a prompt, the way Zed and the other clients // send it. The protocol has no method for it — a command is a text prompt the // agent recognises by its first word. type Command struct { Name string `json:"name"` Description string `json:"description,omitempty"` Input *CommandInput `json:"input,omitempty"` } // CommandInput says a command wants something after its name, and hints at // what: "query to search for", "description of what to plan". // // It is a pointer on Command because its absence is the fact that matters — // a command with no input is complete once its name is typed — and an empty // struct cannot be told from a missing one. type CommandInput struct { Hint string `json:"hint,omitempty"` } // TakesInput reports whether the command wants something typed after its name. // // acp.Command{Name: "test"}.TakesInput() // false // acp.Command{Name: "web", Input: &acp.CommandInput{}}.TakesInput() // true func (c Command) TakesInput() bool { return c.Input != nil } // Hint returns what the agent suggests typing after the name, or "" when the // command takes nothing, or the agent did not say. func (c Command) Hint() string { if c.Input == nil { return "" } return c.Input.Hint } // ToolCallContent is one piece of what a tool produced. // // The protocol gives it three shapes — a content block, a diff, and a // reference to a terminal — told apart by Type. Only the first is drawn; // the others are named so that an unknown one can be reported rather than // silently dropped. type ToolCallContent struct { Type string `json:"type"` Content ContentBlock `json:"content"` Path string `json:"path,omitempty"` OldText string `json:"oldText,omitempty"` NewText string `json:"newText,omitempty"` } // Update is one session/update, in every shape it comes in. // // Content is kept encoded because the protocol uses the same name for two // different things: a single block on a message chunk, and an array of // ToolCallContent on a tool call. Decoding it eagerly into either would break // on the other, which is a defect that only shows up once an agent uses a tool. type Update struct { SessionUpdate string `json:"sessionUpdate"` Content json.RawMessage `json:"content,omitempty"` ToolCallID string `json:"toolCallId,omitempty"` Title string `json:"title,omitempty"` Kind string `json:"kind,omitempty"` Status string `json:"status,omitempty"` RawInput json.RawMessage `json:"rawInput,omitempty"` Entries []PlanEntry `json:"entries,omitempty"` AvailableCommands []Command `json:"availableCommands,omitempty"` Used int64 `json:"used,omitempty"` Size int64 `json:"size,omitempty"` } // Block returns Content read as a single content block, for the message and // thought chunks that carry one. func (u Update) Block() ContentBlock { var block ContentBlock if len(u.Content) == 0 { return block } _ = json.Unmarshal(u.Content, &block) return block } // Blocks returns Content read as a tool call's output, which is an array. func (u Update) Blocks() []ToolCallContent { var blocks []ToolCallContent if len(u.Content) == 0 { return nil } _ = json.Unmarshal(u.Content, &blocks) return blocks } // SessionNotification wraps every update in the session it belongs to. type SessionNotification struct { SessionID string `json:"sessionId"` Update Update `json:"update"` } // ToolCallUpdate is the tool a permission request is about. type ToolCallUpdate struct { ToolCallID string `json:"toolCallId,omitempty"` Title string `json:"title,omitempty"` Kind string `json:"kind,omitempty"` Status string `json:"status,omitempty"` RawInput json.RawMessage `json:"rawInput,omitempty"` } // PermissionOption is one answer the agent will accept. // // Kind is a hint about what the option means — allow_once, allow_always, // reject_once, reject_always — and is what lets Escape be mapped onto the // agent's own idea of "no" instead of a guess. type PermissionOption struct { OptionID string `json:"optionId"` Name string `json:"name"` Kind string `json:"kind,omitempty"` } // The permission option kinds the protocol defines. const ( OptionAllowOnce = "allow_once" OptionAllowAlways = "allow_always" OptionRejectOnce = "reject_once" OptionRejectAlways = "reject_always" ) // RequestPermissionParams is the agent asking before it acts. type RequestPermissionParams struct { SessionID string `json:"sessionId"` ToolCall ToolCallUpdate `json:"toolCall"` Options []PermissionOption `json:"options"` } // The outcomes a permission request can end in. const ( OutcomeSelected = "selected" OutcomeCancelled = "cancelled" ) // PermissionOutcome is the answer: which option, or that the turn was // interrupted before anybody chose. type PermissionOutcome struct { Outcome string `json:"outcome"` OptionID string `json:"optionId,omitempty"` } // RequestPermissionResult wraps the outcome, as the protocol asks. type RequestPermissionResult struct { Outcome PermissionOutcome `json:"outcome"` } // ReadTextFileParams is the agent asking the editor for a file's text. type ReadTextFileParams struct { SessionID string `json:"sessionId"` Path string `json:"path"` Line *int `json:"line,omitempty"` Limit *int `json:"limit,omitempty"` } // ReadTextFileResult carries the text back. type ReadTextFileResult struct { Content string `json:"content"` } // WriteTextFileParams is the agent asking the editor to write a file. type WriteTextFileParams struct { SessionID string `json:"sessionId"` Path string `json:"path"` Content string `json:"content"` }