package acp
import "encoding/json"
// ProtocolVersion is the ACP revision this package speaks. T3 Code sends 1 in
// its initialize request and every other client we know of does too; a client
// that asks for something else still gets 1 back, because answering with a
// version we do not implement would be a lie the handshake cannot recover
// from.
const ProtocolVersion = 1
// The method names, spelled once. A typo in a string literal is a method the
// peer never hears about and an error nobody can grep for.
const (
MethodInitialize = "initialize"
MethodAuthenticate = "authenticate"
MethodNewSession = "session/new"
MethodPrompt = "session/prompt"
MethodCancel = "session/cancel"
MethodSessionUpdate = "session/update"
MethodRequestPermission = "session/request_permission"
)
// Implementation names a peer: the agent to the client, or the client to the
// agent.
type Implementation struct {
Name string `json:"name"`
Title string `json:"title,omitempty"`
Version string `json:"version"`
}
// InitializeRequest is the first thing a client sends.
//
// ClientCapabilities is kept raw. We advertise no filesystem or terminal
// capability of our own, so there is nothing in there we act on, and decoding
// a structure we ignore is a decode that can fail for no reason.
type InitializeRequest struct {
ProtocolVersion int `json:"protocolVersion"`
ClientCapabilities json.RawMessage `json:"clientCapabilities,omitempty"`
ClientInfo *Implementation `json:"clientInfo,omitempty"`
}
// InitializeResponse answers it. Only ProtocolVersion is required by the
// protocol; everything else is how the client decides what to ask us for
// later, so leaving a field out is the way to say "do not ask".
type InitializeResponse struct {
ProtocolVersion int `json:"protocolVersion"`
AgentInfo *Implementation `json:"agentInfo,omitempty"`
AgentCapabilities *AgentCapabilities `json:"agentCapabilities,omitempty"`
// AuthMethods is serialized even when empty: a client that sees the key
// knows we answered the question, where a missing key means "unstated".
AuthMethods []AuthMethod `json:"authMethods"`
// Meta is the protocol's escape hatch, and the one thing we put in it is
// ModelStateMeta. A client probing which models an agent has does not want
// to open a session to find out, and answering here is what lets its model
// picker be filled from the endpoint rather than from parsing our own
// command-line output.
Meta map[string]any `json:"_meta,omitempty"`
}
// ModelStateMeta is the key under InitializeResponse.Meta that carries a
// [SessionModelState].
const ModelStateMeta = "modelState"
// AgentCapabilities is what the agent can be asked to do beyond one prompt.
type AgentCapabilities struct {
LoadSession bool `json:"loadSession,omitempty"`
PromptCapabilities *PromptCapabilities `json:"promptCapabilities,omitempty"`
}
// PromptCapabilities is what a prompt may contain.
type PromptCapabilities struct {
Image bool `json:"image,omitempty"`
Audio bool `json:"audio,omitempty"`
EmbeddedContext bool `json:"embeddedContext,omitempty"`
}
// AuthMethod is one way of logging in that the agent offers. We offer none —
// Codey takes its credential from the environment — so this exists to make the
// empty list well typed rather than to be constructed.
type AuthMethod struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
// AuthenticateRequest asks the agent to log in with one of the methods it
// advertised. Clients send it unconditionally, including a method id we never
// offered, so the id is informational.
type AuthenticateRequest struct {
MethodID string `json:"methodId"`
}
// NewSessionRequest opens a conversation rooted at a directory.
//
// AdditionalDirectories is not in every published copy of the schema but T3
// Code sends it, and McpServers is accepted and ignored: an agent that refuses
// to decode a field it does not use is an agent that breaks on the client's
// next release.
type NewSessionRequest struct {
Cwd string `json:"cwd"`
McpServers []json.RawMessage `json:"mcpServers,omitempty"`
AdditionalDirectories []string `json:"additionalDirectories,omitempty"`
}
// NewSessionResponse hands back the id every later message carries.
type NewSessionResponse struct {
SessionID string `json:"sessionId"`
Models *SessionModelState `json:"models,omitempty"`
}
// SessionModelState is the model picker: what is selected, and what else there
// is. A client that gets this uses it in preference to anything the agent
// printed on a command line.
type SessionModelState struct {
CurrentModelID string `json:"currentModelId"`
AvailableModels []ModelInfo `json:"availableModels"`
}
// ModelInfo is one entry in that picker.
type ModelInfo struct {
ModelID string `json:"modelId"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
// SetModelRequest selects a model mid-session.
type SetModelRequest struct {
SessionID string `json:"sessionId"`
ModelID string `json:"modelId"`
}
// PromptRequest is one user turn.
type PromptRequest struct {
SessionID string `json:"sessionId"`
Prompt []ContentBlock `json:"prompt"`
}
// ContentBlock is a piece of a message. The union is decoded into one struct
// because Go has no sum types and the alternative — a custom unmarshaller per
// variant — buys nothing when the only variant we read is text.
type ContentBlock struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Data string `json:"data,omitempty"`
MimeType string `json:"mimeType,omitempty"`
URI string `json:"uri,omitempty"`
Name string `json:"name,omitempty"`
}
// The stop reasons a prompt can end with.
const (
StopEndTurn = "end_turn"
StopCancelled = "cancelled"
StopRefusal = "refusal"
StopMaxTokens = "max_tokens"
StopMaxTurnRequests = "max_turn_requests"
)
// PromptResponse ends the turn. StopReason is the only field the clients we
// know of read.
type PromptResponse struct {
StopReason string `json:"stopReason"`
}
// CancelParams asks for the turn in flight to stop. It is a notification, so
// there is nothing to answer: the acknowledgement is the prompt coming back
// with StopCancelled.
type CancelParams struct {
SessionID string `json:"sessionId"`
}
// SessionNotification is everything the agent says while a turn runs. One
// struct with a tagged Update, because that is how the wire is shaped.
type SessionNotification struct {
SessionID string `json:"sessionId"`
Update Update `json:"update"`
}
// The sessionUpdate discriminators we emit.
const (
UpdateAgentMessageChunk = "agent_message_chunk"
UpdateAgentThoughtChunk = "agent_thought_chunk"
UpdateToolCall = "tool_call"
UpdateToolCallUpdate = "tool_call_update"
)
// Update is the tagged body of a session notification. Every field beyond
// SessionUpdate is optional, and which ones are read depends on the tag.
//
// Content is `any` because the wire reuses one key for two shapes: on a chunk
// it is a single content block, on a tool call it is a list of tool call
// content. Updates are only ever written, never decoded, so carrying the union
// as an interface costs nothing and spells out that the shape follows the tag.
type Update struct {
SessionUpdate string `json:"sessionUpdate"`
Content any `json:"content,omitempty"`
// Set on the tool-call variants. ToolCallID is what pairs an update with
// the call it belongs to, so it is the one field that is never optional
// there.
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"`
}
// The tool kinds ACP defines. A client uses them to pick an icon and to decide
// what to fold away, so a wrong one is cosmetic and a missing one is fine.
const (
KindRead = "read"
KindEdit = "edit"
KindDelete = "delete"
KindMove = "move"
KindSearch = "search"
KindExecute = "execute"
KindThink = "think"
KindFetch = "fetch"
KindOther = "other"
)
// The tool call statuses.
const (
StatusPending = "pending"
StatusInProgress = "in_progress"
StatusCompleted = "completed"
StatusFailed = "failed"
)
// ToolCallContent is what a tool produced: text, or a diff a client can render
// side by side.
type ToolCallContent struct {
Type string `json:"type"`
Content *ContentBlock `json:"content,omitempty"`
// Set when Type is "diff".
Path string `json:"path,omitempty"`
OldText string `json:"oldText,omitempty"`
NewText string `json:"newText,omitempty"`
}
// TextContent is the content block a chunk carries, which is always text in
// what Codey emits.
func TextContent(text string) *ContentBlock {
return &ContentBlock{Type: "text", Text: text}
}
// ToolCallText wraps tool output as tool call content.
func ToolCallText(text string) ToolCallContent {
return ToolCallContent{Type: "content", Content: TextContent(text)}
}
// RequestPermissionParams asks the client to decide about one tool call.
type RequestPermissionParams struct {
SessionID string `json:"sessionId"`
ToolCall PermissionToolCall `json:"toolCall"`
Options []PermissionOption `json:"options"`
}
// PermissionToolCall describes what is waiting on the answer.
type PermissionToolCall struct {
ToolCallID string `json:"toolCallId"`
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 client may give.
type PermissionOption struct {
OptionID string `json:"optionId"`
Name string `json:"name"`
Kind string `json:"kind"`
}
// The option kinds, and the option ids we use.
//
// The ids are not ours to invent freely. Cursor's adapter in T3 Code replies
// with these three literals regardless of what the agent offered, so choosing
// them here is what lets the same binary sit in a Cursor-shaped slot as well
// as a Grok-shaped one.
const (
KindAllowOnce = "allow_once"
KindAllowAlways = "allow_always"
KindRejectOnce = "reject_once"
OptAllowOnce = "allow-once"
OptAllowAlways = "allow-always"
OptRejectOnce = "reject-once"
)
// RequestPermissionResult is the answer.
type RequestPermissionResult struct {
Outcome PermissionOutcome `json:"outcome"`
}
// PermissionOutcome is either a selection or a cancellation.
type PermissionOutcome struct {
Outcome string `json:"outcome"`
OptionID string `json:"optionId,omitempty"`
}
// The outcome tags.
const (
OutcomeSelected = "selected"
OutcomeCancelled = "cancelled"
)