turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

protocol.go · 371 lines · 12.3 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 4h ago1package acp
2
3import "encoding/json"
4
5// ProtocolVersion is the version of the Agent Client Protocol this client
6// speaks. It is a single integer, and both sides have to agree on one.
7const ProtocolVersion = 1
8
9// The methods this client calls on an agent.
10const (
11 MethodInitialize = "initialize"
12 MethodNewSession = "session/new"
13 MethodPrompt = "session/prompt"
14 MethodCancel = "session/cancel"
15)
16
17// The methods an agent calls on this client.
18const (
19 MethodUpdate = "session/update"
20 MethodRequestPermission = "session/request_permission"
21 MethodReadTextFile = "fs/read_text_file"
22 MethodWriteTextFile = "fs/write_text_file"
23)
24
25// Implementation is what one side of the conversation calls itself. An agent
26// logs it, so it is worth being truthful in.
27type Implementation struct {
28 Name string `json:"name"`
29 Title string `json:"title,omitempty"`
30 Version string `json:"version,omitempty"`
31}
32
33// FileSystemCapability says which file operations the editor will perform on
34// the agent's behalf.
35type FileSystemCapability struct {
36 ReadTextFile bool `json:"readTextFile"`
37 WriteTextFile bool `json:"writeTextFile"`
38}
39
40// ClientCapabilities is what this editor offers an agent.
41//
42// Terminal is deliberately absent rather than false-and-present: an agent can
43// already have a shell through its own toolsets, and advertising this one would
44// mean the editor running commands on the agent's behalf and owning the output.
45type ClientCapabilities struct {
46 FS FileSystemCapability `json:"fs"`
47}
48
49// InitializeParams opens the conversation.
50type InitializeParams struct {
51 ProtocolVersion int `json:"protocolVersion"`
52 ClientCapabilities ClientCapabilities `json:"clientCapabilities"`
53 ClientInfo Implementation `json:"clientInfo"`
54}
55
56// AuthMethod is one way an agent offers to be logged in to.
57type AuthMethod struct {
58 ID string `json:"id"`
59 Name string `json:"name"`
60 Description string `json:"description,omitempty"`
61}
62
63// InitializeResult is what the agent answers with.
64//
65// AgentCapabilities is kept encoded: the set grows with the protocol, this
66// client acts on one corner of it, and decoding the whole into a struct would
67// turn every new capability into a field nobody reads. The corner it does act
68// on is read out by PromptCapabilities.
69type InitializeResult struct {
70 ProtocolVersion int `json:"protocolVersion"`
71 AgentCapabilities json.RawMessage `json:"agentCapabilities,omitempty"`
72 AgentInfo Implementation `json:"agentInfo"`
73 AuthMethods []AuthMethod `json:"authMethods,omitempty"`
74}
75
76// PromptCapabilities is what an agent accepts inside a prompt beyond plain
77// text. EmbeddedContext is the one that matters here: it says a file named
78// with "@" may be sent with its text inside the prompt rather than as a link
79// the agent has to follow.
80type PromptCapabilities struct {
81 EmbeddedContext bool `json:"embeddedContext"`
82 Image bool `json:"image"`
83}
84
85// PromptCapabilities returns the prompt capabilities the agent declared, and
86// the zero value — nothing beyond text — when it declared none.
87func (r InitializeResult) PromptCapabilities() PromptCapabilities {
88 var capabilities struct {
89 Prompt PromptCapabilities `json:"promptCapabilities"`
90 }
91 if len(r.AgentCapabilities) > 0 {
92 _ = json.Unmarshal(r.AgentCapabilities, &capabilities)
93 }
94 return capabilities.Prompt
95}
96
97// NewSessionParams starts a conversation in a directory.
98//
99// McpServers is always empty and is still sent: the field is required, and the
100// MCP servers an agent talks to are its own configuration's business.
101type NewSessionParams struct {
102 Cwd string `json:"cwd"`
103 McpServers []any `json:"mcpServers"`
104}
105
106// NewSessionResult carries the id every later message quotes.
107type NewSessionResult struct {
108 SessionID string `json:"sessionId"`
109}
110
111// The kinds of content block this editor sends. An agent may send others
112// back — image, audio — and those are drawn as nothing rather than refused.
113const (
114 ContentText = "text"
115 ContentResourceLink = "resource_link"
116 ContentResource = "resource"
117)
118
119// ContentBlock is one piece of a message.
120//
121// Three shapes share the struct, told apart by Type: text carries Text; a
122// resource_link carries URI, Name and MimeType and points at a file the agent
123// fetches for itself; a resource carries the file's text inside Resource, for
124// an agent that accepts embedded context. The last two are how a file named
125// with "@" in the box reaches the agent.
126type ContentBlock struct {
127 Type string `json:"type"`
128 Text string `json:"text,omitempty"`
129
130 URI string `json:"uri,omitempty"`
131 Name string `json:"name,omitempty"`
132 MimeType string `json:"mimeType,omitempty"`
133
134 Resource *EmbeddedResource `json:"resource,omitempty"`
135}
136
137// EmbeddedResource is a file's text carried inside the prompt.
138type EmbeddedResource struct {
139 URI string `json:"uri"`
140 MimeType string `json:"mimeType,omitempty"`
141 Text string `json:"text"`
142}
143
144// Text returns the block's text, and "" for a block that carries none.
145func (c ContentBlock) String() string { return c.Text }
146
147// PromptParams sends one turn's worth of input.
148type PromptParams struct {
149 SessionID string `json:"sessionId"`
150 Prompt []ContentBlock `json:"prompt"`
151}
152
153// The reasons a turn ends.
154const (
155 StopEndTurn = "end_turn"
156 StopMaxTokens = "max_tokens"
157 StopMaxTurnRequests = "max_turn_requests"
158 StopRefusal = "refusal"
159 StopCancelled = "cancelled"
160)
161
162// PromptResult says why the turn ended.
163type PromptResult struct {
164 StopReason string `json:"stopReason"`
165}
166
167// CancelParams interrupts a turn. It is a notification: there is nothing to
168// answer.
169type CancelParams struct {
170 SessionID string `json:"sessionId"`
171}
172
173// The kinds of session/update an agent sends.
174const (
175 UpdateAgentMessage = "agent_message_chunk"
176 UpdateAgentThought = "agent_thought_chunk"
177 UpdateUserMessage = "user_message_chunk"
178 UpdateToolCall = "tool_call"
179 UpdateToolCallDone = "tool_call_update"
180 UpdatePlan = "plan"
181 UpdateCommands = "available_commands_update"
182 UpdateUsage = "usage_update"
183 UpdateCurrentMode = "current_mode_update"
184)
185
186// The statuses a tool call moves through.
187const (
188 StatusPending = "pending"
189 StatusInProgress = "in_progress"
190 StatusCompleted = "completed"
191 StatusFailed = "failed"
192)
193
194// PlanEntry is one line of a plan the agent published.
195type PlanEntry struct {
196 Content string `json:"content"`
197 Priority string `json:"priority,omitempty"`
198 Status string `json:"status,omitempty"`
199}
200
201// Command is one thing the agent says it can be asked to do: a slash command,
202// typed as "/name" at the start of a prompt, the way Zed and the other clients
203// send it. The protocol has no method for it — a command is a text prompt the
204// agent recognises by its first word.
205type Command struct {
206 Name string `json:"name"`
207 Description string `json:"description,omitempty"`
208 Input *CommandInput `json:"input,omitempty"`
209}
210
211// CommandInput says a command wants something after its name, and hints at
212// what: "query to search for", "description of what to plan".
213//
214// It is a pointer on Command because its absence is the fact that matters —
215// a command with no input is complete once its name is typed — and an empty
216// struct cannot be told from a missing one.
217type CommandInput struct {
218 Hint string `json:"hint,omitempty"`
219}
220
221// TakesInput reports whether the command wants something typed after its name.
222//
223// acp.Command{Name: "test"}.TakesInput() // false
224// acp.Command{Name: "web", Input: &acp.CommandInput{}}.TakesInput() // true
225func (c Command) TakesInput() bool { return c.Input != nil }
226
227// Hint returns what the agent suggests typing after the name, or "" when the
228// command takes nothing, or the agent did not say.
229func (c Command) Hint() string {
230 if c.Input == nil {
231 return ""
232 }
233 return c.Input.Hint
234}
235
236// ToolCallContent is one piece of what a tool produced.
237//
238// The protocol gives it three shapes — a content block, a diff, and a
239// reference to a terminal — told apart by Type. Only the first is drawn;
240// the others are named so that an unknown one can be reported rather than
241// silently dropped.
242type ToolCallContent struct {
243 Type string `json:"type"`
244 Content ContentBlock `json:"content"`
245 Path string `json:"path,omitempty"`
246 OldText string `json:"oldText,omitempty"`
247 NewText string `json:"newText,omitempty"`
248}
249
250// Update is one session/update, in every shape it comes in.
251//
252// Content is kept encoded because the protocol uses the same name for two
253// different things: a single block on a message chunk, and an array of
254// ToolCallContent on a tool call. Decoding it eagerly into either would break
255// on the other, which is a defect that only shows up once an agent uses a tool.
256type Update struct {
257 SessionUpdate string `json:"sessionUpdate"`
258 Content json.RawMessage `json:"content,omitempty"`
259
260 ToolCallID string `json:"toolCallId,omitempty"`
261 Title string `json:"title,omitempty"`
262 Kind string `json:"kind,omitempty"`
263 Status string `json:"status,omitempty"`
264 RawInput json.RawMessage `json:"rawInput,omitempty"`
265
266 Entries []PlanEntry `json:"entries,omitempty"`
267 AvailableCommands []Command `json:"availableCommands,omitempty"`
268
269 Used int64 `json:"used,omitempty"`
270 Size int64 `json:"size,omitempty"`
271}
272
273// Block returns Content read as a single content block, for the message and
274// thought chunks that carry one.
275func (u Update) Block() ContentBlock {
276 var block ContentBlock
277 if len(u.Content) == 0 {
278 return block
279 }
280 _ = json.Unmarshal(u.Content, &block)
281 return block
282}
283
284// Blocks returns Content read as a tool call's output, which is an array.
285func (u Update) Blocks() []ToolCallContent {
286 var blocks []ToolCallContent
287 if len(u.Content) == 0 {
288 return nil
289 }
290 _ = json.Unmarshal(u.Content, &blocks)
291 return blocks
292}
293
294// SessionNotification wraps every update in the session it belongs to.
295type SessionNotification struct {
296 SessionID string `json:"sessionId"`
297 Update Update `json:"update"`
298}
299
300// ToolCallUpdate is the tool a permission request is about.
301type ToolCallUpdate struct {
302 ToolCallID string `json:"toolCallId,omitempty"`
303 Title string `json:"title,omitempty"`
304 Kind string `json:"kind,omitempty"`
305 Status string `json:"status,omitempty"`
306 RawInput json.RawMessage `json:"rawInput,omitempty"`
307}
308
309// PermissionOption is one answer the agent will accept.
310//
311// Kind is a hint about what the option means — allow_once, allow_always,
312// reject_once, reject_always — and is what lets Escape be mapped onto the
313// agent's own idea of "no" instead of a guess.
314type PermissionOption struct {
315 OptionID string `json:"optionId"`
316 Name string `json:"name"`
317 Kind string `json:"kind,omitempty"`
318}
319
320// The permission option kinds the protocol defines.
321const (
322 OptionAllowOnce = "allow_once"
323 OptionAllowAlways = "allow_always"
324 OptionRejectOnce = "reject_once"
325 OptionRejectAlways = "reject_always"
326)
327
328// RequestPermissionParams is the agent asking before it acts.
329type RequestPermissionParams struct {
330 SessionID string `json:"sessionId"`
331 ToolCall ToolCallUpdate `json:"toolCall"`
332 Options []PermissionOption `json:"options"`
333}
334
335// The outcomes a permission request can end in.
336const (
337 OutcomeSelected = "selected"
338 OutcomeCancelled = "cancelled"
339)
340
341// PermissionOutcome is the answer: which option, or that the turn was
342// interrupted before anybody chose.
343type PermissionOutcome struct {
344 Outcome string `json:"outcome"`
345 OptionID string `json:"optionId,omitempty"`
346}
347
348// RequestPermissionResult wraps the outcome, as the protocol asks.
349type RequestPermissionResult struct {
350 Outcome PermissionOutcome `json:"outcome"`
351}
352
353// ReadTextFileParams is the agent asking the editor for a file's text.
354type ReadTextFileParams struct {
355 SessionID string `json:"sessionId"`
356 Path string `json:"path"`
357 Line *int `json:"line,omitempty"`
358 Limit *int `json:"limit,omitempty"`
359}
360
361// ReadTextFileResult carries the text back.
362type ReadTextFileResult struct {
363 Content string `json:"content"`
364}
365
366// WriteTextFileParams is the agent asking the editor to write a file.
367type WriteTextFileParams struct {
368 SessionID string `json:"sessionId"`
369 Path string `json:"path"`
370 Content string `json:"content"`
371}