turbo-editors/turbo-corepublic Fork 0
v1.0.2
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.

🛟 Updated. 28d5985 · on v1.0.2 · k33g · 15h ago
protocol.go · 371 lines · 12.3 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
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"`
}