返回 DeepSeek-Reasonix
protocol.go
根目录 / internal / acp / protocol.go
1 // Package acp implements the Agent Client Protocol (https://agentclientprotocol.com)
2 // transport: a stdio JSON-RPC 2.0 agent that editors and other host clients speak
3 // to drive Reasonix. Many tools integrated with the v1 (main-branch) agent over
4 // ACP, so v2 keeps the wire contract identical — the wire types in this file are a
5 // faithful port of main's src/acp/protocol.ts (ACP protocol version 1).
6 //
7 // The package is an adapter layer over the v2 kernel and depends only on stable
8 // contracts: it maps the agent's typed event.Event stream onto session/update
9 // notifications (see dispatch.go), bridges permission.Approver onto
10 // session/request_permission round-trips (see permission.go), and exposes the
11 // whole thing over NDJSON JSON-RPC (see server.go). How a per-session agent is
12 // actually assembled — provider, tools rooted at the session cwd, per-session MCP
13 // — is left to a Factory the composition root supplies (see service.go), so this
14 // package stays independent of the cli wiring.
15 package acp
16
17 import (
18 "encoding/json"
19 "fmt"
20 "strings"
21 )
22
23 // ProtocolVersion is the ACP version this agent implements. Matches main.
24 const ProtocolVersion = 1
25
26 // JSON-RPC 2.0 error codes (subset used on the wire). Mirrors protocol.ts.
27 const (
28 ErrParse = -32700
29 ErrInvalidRequest = -32600
30 ErrMethodNotFound = -32601
31 ErrInvalidParams = -32602
32 ErrInternal = -32603
33 )
34
35 // --- initialize ---
36
37 // InitializeParams is the client's handshake. The agent records the client's
38 // capabilities — fs read/write proxying and host terminals are used when
39 // offered — and advertises its own fixed capability set in reply.
40 type InitializeParams struct {
41 ProtocolVersion int `json:"protocolVersion"`
42 ClientInfo *Implementation `json:"clientInfo,omitempty"`
43 ClientCapabilities ClientCapabilities `json:"clientCapabilities,omitempty"`
44 }
45
46 // ClientCapabilities is what the client offers the agent: filesystem proxy
47 // methods (fs/read_text_file, fs/write_text_file) that see unsaved editor
48 // buffers, and host-owned terminals (terminal/*). Meta carries vendor
49 // capability blocks (e.g. _meta["reasonix.io"]) for tolerant parse — unknown
50 // or malformed entries simply mean the vendor feature stays off.
51 type ClientCapabilities struct {
52 FS FSCapabilities `json:"fs,omitempty"`
53 Terminal bool `json:"terminal,omitempty"`
54 Meta map[string]any `json:"_meta,omitempty"`
55 }
56
57 // FSCapabilities reports which client filesystem methods are available.
58 type FSCapabilities struct {
59 ReadTextFile bool `json:"readTextFile,omitempty"`
60 WriteTextFile bool `json:"writeTextFile,omitempty"`
61 }
62
63 // Implementation names a participant (client or agent) on the wire.
64 type Implementation struct {
65 Name string `json:"name"`
66 Title string `json:"title,omitempty"`
67 Version string `json:"version,omitempty"`
68 }
69
70 // InitializeResult advertises what this agent supports: persisted session load,
71 // ACP v1 session lifecycle helpers, inline resource text (embeddedContext) but
72 // not image/audio, and stdio / Streamable HTTP MCP (no legacy sse).
73 type InitializeResult struct {
74 ProtocolVersion int `json:"protocolVersion"`
75 AgentCapabilities AgentCapabilities `json:"agentCapabilities"`
76 AgentInfo Implementation `json:"agentInfo"`
77 AuthMethods []AuthMethod `json:"authMethods"`
78 }
79
80 // AgentCapabilities is the agentCapabilities object in InitializeResult.
81 type AgentCapabilities struct {
82 LoadSession bool `json:"loadSession"`
83 SessionCapabilities SessionCapabilities `json:"sessionCapabilities,omitempty"`
84 PromptCapabilities PromptCapabilities `json:"promptCapabilities"`
85 MCPCapabilities MCPCapabilities `json:"mcpCapabilities"`
86 Meta map[string]any `json:"_meta,omitempty"`
87 }
88
89 // ReasonixExtensionCapabilities advertises Reasonix-specific ACP extensions.
90 // ACP v1 reserves agentCapabilities._meta for vendor capability discovery.
91 type ReasonixExtensionCapabilities struct {
92 SessionSteer *SessionSteerCapability `json:"sessionSteer,omitempty"`
93 // SessionReloadExtensions advertises the vendor runtime-reload method.
94 SessionReloadExtensions *SessionReloadExtensionsCapability `json:"sessionReloadExtensions,omitempty"`
95 // ExtensionSurface advertises structured extension-UI surface support:
96 // the agent publishes surfaces as vendor session/update payloads.
97 ExtensionSurface *ExtensionSurfaceCapability `json:"extensionSurface,omitempty"`
98 }
99
100 // SessionSteerCapability identifies the vendor-namespaced steering method.
101 type SessionSteerCapability struct {
102 Method string `json:"method"`
103 }
104
105 // SessionReloadExtensionsCapability identifies the vendor-namespaced runtime
106 // reload method.
107 type SessionReloadExtensionsCapability struct {
108 Method string `json:"method"`
109 }
110
111 const (
112 // reasonixExtensionSurfaceSchemaVersion versions the extension-surface DTO
113 // carried by the vendor session/update variant.
114 reasonixExtensionSurfaceSchemaVersion = 1
115 // extensionSurfaceUpdateKind discriminates the vendor session/update
116 // variant that carries a structured extension-UI surface.
117 extensionSurfaceUpdateKind = "_reasonix.io/extension_surface"
118 )
119
120 // ExtensionSurfaceCapability advertises that a participant renders structured
121 // extension-UI surfaces (Extension Protocol v1) natively.
122 type ExtensionSurfaceCapability struct {
123 Supported bool `json:"supported"`
124 SchemaVersion int `json:"schemaVersion"`
125 }
126
127 // EmptyCapability serializes to {} for ACP capability flags.
128 type EmptyCapability struct{}
129
130 // SessionCapabilities advertises optional session lifecycle methods.
131 type SessionCapabilities struct {
132 List *EmptyCapability `json:"list,omitempty"`
133 Resume *EmptyCapability `json:"resume,omitempty"`
134 Close *EmptyCapability `json:"close,omitempty"`
135 Delete *EmptyCapability `json:"delete,omitempty"`
136 }
137
138 // PromptCapabilities reports which content-block kinds prompts may carry.
139 type PromptCapabilities struct {
140 Image bool `json:"image"`
141 Audio bool `json:"audio"`
142 EmbeddedContext bool `json:"embeddedContext"`
143 }
144
145 // MCPCapabilities reports which MCP transports session/new accepts.
146 type MCPCapabilities struct {
147 HTTP bool `json:"http"`
148 SSE bool `json:"sse"`
149 }
150
151 // AuthMethod advertises how a client can prepare credentials for the agent.
152 type AuthMethod struct {
153 ID string `json:"id"`
154 Name string `json:"name"`
155 Description string `json:"description,omitempty"`
156 Type string `json:"type,omitempty"`
157 Args []string `json:"args,omitempty"`
158 Env map[string]string `json:"env,omitempty"`
159 }
160
161 // AuthenticateParams selects one advertised auth method. Terminal methods are
162 // normally handled by the client by launching the agent with the method's args;
163 // accepting this request keeps clients that call authenticate directly working.
164 type AuthenticateParams struct {
165 MethodID string `json:"methodId"`
166 }
167
168 // AuthenticateResult is the empty authentication ack.
169 type AuthenticateResult struct{}
170
171 // --- session/new ---
172
173 // SessionNewParams opens a session rooted at cwd, optionally with MCP servers
174 // the agent should connect for the session's lifetime.
175 type SessionNewParams struct {
176 Cwd string `json:"cwd,omitempty"`
177 MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
178 }
179
180 // MCPServerSpec describes one MCP server the client asks the agent to connect.
181 type MCPServerSpec struct {
182 Name string `json:"name"`
183 Type string `json:"type,omitempty"`
184 Command string `json:"command,omitempty"`
185 Args []string `json:"args,omitempty"`
186 Env MCPEnv `json:"env,omitempty"`
187 URL string `json:"url,omitempty"`
188 Headers MCPHeaders `json:"headers,omitempty"`
189 }
190
191 // MCPEnv accepts ACP's official EnvVariable[] shape while still accepting the
192 // older map shape that Reasonix v1 clients used.
193 type MCPEnv map[string]string
194
195 // MCPHeaders accepts ACP's official HTTPHeader[] shape while still accepting
196 // the older map shape that Reasonix v1 clients used. The official spec
197 // (https://agentclientprotocol.com) ships HTTP/SSE MCP headers as an array of
198 // {name,value} objects, even when empty.
199 type MCPHeaders map[string]string
200
201 // EnvVariable is one official ACP MCP environment variable entry. The same
202 // {name,value} shape is also used by HTTP/SSE headers in the ACP spec, so we
203 // reuse it as the parse target for [MCPHeaders] too.
204 type EnvVariable struct {
205 Name string `json:"name"`
206 Value string `json:"value"`
207 }
208
209 func (e *MCPEnv) UnmarshalJSON(raw []byte) error {
210 out, err := unmarshalNameValueMap(raw, "env")
211 if err != nil {
212 return err
213 }
214 *e = out
215 return nil
216 }
217
218 func (h *MCPHeaders) UnmarshalJSON(raw []byte) error {
219 out, err := unmarshalNameValueMap(raw, "headers")
220 if err != nil {
221 return err
222 }
223 *h = out
224 return nil
225 }
226
227 // unmarshalNameValueMap parses ACP's official [{name,value}, ...] array shape
228 // or the legacy {name: value, ...} map shape into a map. field names the JSON
229 // field for error messages.
230 func unmarshalNameValueMap(raw []byte, field string) (map[string]string, error) {
231 if s := strings.TrimSpace(string(raw)); s == "" || s == "null" {
232 return nil, nil
233 }
234
235 var vars []EnvVariable
236 if err := json.Unmarshal(raw, &vars); err == nil {
237 out := make(map[string]string, len(vars))
238 for i, v := range vars {
239 if strings.TrimSpace(v.Name) == "" {
240 return nil, fmt.Errorf("%s[%d].name is required", field, i)
241 }
242 out[v.Name] = v.Value
243 }
244 return out, nil
245 }
246
247 var legacy map[string]string
248 if err := json.Unmarshal(raw, &legacy); err == nil {
249 return legacy, nil
250 }
251 return nil, fmt.Errorf("%s must be an array of {name,value} objects", field)
252 }
253
254 // SessionNewResult returns the opaque id used to address the session thereafter.
255 type SessionNewResult struct {
256 SessionID string `json:"sessionId"`
257 Models *SessionModelState `json:"models,omitempty"`
258 Modes *SessionModeState `json:"modes,omitempty"`
259 ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
260 }
261
262 // --- session modes ---
263
264 // SessionMode is one operating mode the client can switch the session into.
265 type SessionMode struct {
266 ID string `json:"id"`
267 Name string `json:"name"`
268 Description string `json:"description,omitempty"`
269 }
270
271 // SessionModeState reports the current mode and the full mode list.
272 type SessionModeState struct {
273 CurrentModeID string `json:"currentModeId"`
274 AvailableModes []SessionMode `json:"availableModes"`
275 }
276
277 // SessionSetModeParams switches a session's operating mode.
278 type SessionSetModeParams struct {
279 SessionID string `json:"sessionId"`
280 ModeID string `json:"modeId"`
281 }
282
283 // SessionSetModeResult is the empty ack.
284 type SessionSetModeResult struct{}
285
286 // ModelInfo describes one selectable model in ACP's legacy model selector.
287 type ModelInfo struct {
288 ModelID string `json:"modelId"`
289 Name string `json:"name"`
290 Description string `json:"description,omitempty"`
291 }
292
293 // SessionModelState is ACP's legacy model selector state. New clients should
294 // prefer the category:"model" config option, but some hosts still probe this.
295 type SessionModelState struct {
296 AvailableModels []ModelInfo `json:"availableModels"`
297 CurrentModelID string `json:"currentModelId"`
298 }
299
300 // --- session/load ---
301
302 // SessionLoadParams resumes a session saved under sessionId (the id a prior
303 // session/new returned), optionally re-rooting it at cwd with fresh MCP servers.
304 // The agent replays the stored conversation as session/update notifications
305 // before the request returns.
306 type SessionLoadParams struct {
307 SessionID string `json:"sessionId"`
308 Cwd string `json:"cwd,omitempty"`
309 MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
310 }
311
312 // SessionLoadResult is the empty ack; the conversation has already arrived as a
313 // burst of session/update notifications by the time it is sent.
314 type SessionLoadResult struct {
315 Models *SessionModelState `json:"models,omitempty"`
316 Modes *SessionModeState `json:"modes,omitempty"`
317 ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
318 }
319
320 // --- session/resume ---
321
322 // SessionResumeParams resumes a session without replaying its transcript.
323 type SessionResumeParams struct {
324 SessionID string `json:"sessionId"`
325 Cwd string `json:"cwd,omitempty"`
326 MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
327 }
328
329 // SessionResumeResult is the empty ack returned once the session is ready.
330 type SessionResumeResult struct {
331 Models *SessionModelState `json:"models,omitempty"`
332 Modes *SessionModeState `json:"modes,omitempty"`
333 ConfigOptions []SessionConfigOption `json:"configOptions,omitempty"`
334 }
335
336 // --- session/set_config_option ---
337
338 // SetSessionConfigOptionParams changes one advertised session config option.
339 type SetSessionConfigOptionParams struct {
340 SessionID string `json:"sessionId"`
341 ConfigID string `json:"configId"`
342 Value string `json:"value"`
343 }
344
345 // SetSessionConfigOptionResult returns the full refreshed config state.
346 type SetSessionConfigOptionResult struct {
347 ConfigOptions []SessionConfigOption `json:"configOptions"`
348 }
349
350 // SessionConfigOption is a single-value ACP session selector.
351 type SessionConfigOption struct {
352 ID string `json:"id"`
353 Name string `json:"name"`
354 Description string `json:"description,omitempty"`
355 Category string `json:"category,omitempty"`
356 Type string `json:"type"`
357 CurrentValue string `json:"currentValue"`
358 Options []SessionConfigSelectOption `json:"options"`
359 }
360
361 // SessionConfigSelectOption is one selectable value for a config option.
362 type SessionConfigSelectOption struct {
363 Value string `json:"value"`
364 Name string `json:"name"`
365 Description string `json:"description,omitempty"`
366 }
367
368 // --- session/set_model ---
369
370 // SetSessionModelParams is ACP's legacy model-switching request.
371 type SetSessionModelParams struct {
372 SessionID string `json:"sessionId"`
373 ModelID string `json:"modelId"`
374 }
375
376 // SetSessionModelResult is the empty ack for legacy model switching.
377 type SetSessionModelResult struct{}
378
379 // --- session/list ---
380
381 // SessionListParams lists known sessions, optionally filtered by cwd.
382 type SessionListParams struct {
383 Cwd string `json:"cwd,omitempty"`
384 Cursor string `json:"cursor,omitempty"`
385 }
386
387 // SessionListResult is the first and only page of sessions Reasonix currently
388 // returns. NextCursor is omitted because the in-process list is unpaged.
389 type SessionListResult struct {
390 Sessions []SessionInfo `json:"sessions"`
391 NextCursor string `json:"nextCursor,omitempty"`
392 }
393
394 // SessionInfo is the ACP session/list item shape.
395 type SessionInfo struct {
396 SessionID string `json:"sessionId"`
397 Cwd string `json:"cwd"`
398 Title string `json:"title,omitempty"`
399 UpdatedAt string `json:"updatedAt,omitempty"`
400 Meta map[string]any `json:"_meta,omitempty"`
401 }
402
403 // --- session/close ---
404
405 // SessionCloseParams closes an active session and releases its resources.
406 type SessionCloseParams struct {
407 SessionID string `json:"sessionId"`
408 }
409
410 // SessionCloseResult is the empty close ack.
411 type SessionCloseResult struct{}
412
413 // --- session/delete ---
414
415 // SessionDeleteParams removes a session from future session/list results.
416 type SessionDeleteParams struct {
417 SessionID string `json:"sessionId"`
418 }
419
420 // SessionDeleteResult is the empty delete ack.
421 type SessionDeleteResult struct{}
422
423 // --- content blocks (inbound prompt) ---
424
425 // ContentBlock is one piece of a prompt. The agent reads text blocks and the
426 // inline text of resource blocks (embeddedContext); image/audio are accepted on
427 // the wire but ignored, matching the advertised capabilities.
428 type ContentBlock struct {
429 Type string `json:"type"`
430 Text string `json:"text,omitempty"`
431 Resource *ResourceContents `json:"resource,omitempty"`
432 MimeType string `json:"mimeType,omitempty"`
433 Data string `json:"data,omitempty"`
434 }
435
436 // ResourceContents is the embedded resource of a "resource" content block.
437 type ResourceContents struct {
438 URI string `json:"uri"`
439 MimeType string `json:"mimeType,omitempty"`
440 Text string `json:"text,omitempty"`
441 }
442
443 // FlattenPrompt extracts the user-visible prompt text out of ACP content blocks.
444 // Text blocks contribute their text; resource blocks contribute their inline
445 // text when present (embeddedContext). Other block kinds are dropped. Ported from
446 // protocol.ts flattenPrompt.
447 func FlattenPrompt(blocks []ContentBlock) string {
448 parts := make([]string, 0, len(blocks))
449 for _, b := range blocks {
450 switch b.Type {
451 case "text":
452 if b.Text != "" {
453 parts = append(parts, b.Text)
454 }
455 case "resource":
456 if b.Resource != nil && b.Resource.Text != "" {
457 parts = append(parts, b.Resource.Text)
458 }
459 }
460 }
461 return strings.TrimSpace(strings.Join(parts, "\n\n"))
462 }
463
464 // --- session/prompt ---
465
466 // SessionPromptParams sends a turn's prompt to a session.
467 type SessionPromptParams struct {
468 SessionID string `json:"sessionId"`
469 Prompt []ContentBlock `json:"prompt"`
470 }
471
472 // SessionSteerParams is the Reasonix ACP v1 extension for injecting user
473 // guidance into an active prompt without cancelling it.
474 type SessionSteerParams struct {
475 SessionID string `json:"sessionId"`
476 Prompt []ContentBlock `json:"prompt"`
477 }
478
479 // SessionSteerResult acknowledges that the active turn accepted the guidance.
480 type SessionSteerResult struct{}
481
482 // sessionSteerMethod follows ACP v1's reserved vendor-extension namespace.
483 const sessionSteerMethod = "_reasonix.io/session/steer"
484
485 // SessionReloadExtensionsParams addresses one live ACP session.
486 type SessionReloadExtensionsParams struct {
487 SessionID string `json:"sessionId"`
488 }
489
490 // SessionReloadExtensionsResult reports whether the runtime reload ran
491 // immediately (Queued false) or was coalesced behind a turn/rebuild in flight
492 // to run when the session goes idle (Queued true).
493 type SessionReloadExtensionsResult struct {
494 Queued bool `json:"queued,omitempty"`
495 }
496
497 // sessionReloadExtensionsMethod follows ACP v1's reserved vendor-extension
498 // namespace, like sessionSteerMethod: only the "_<vendor>/" prefix is reserved
499 // for vendor methods, so the bare "reasonix/session/reloadExtensions" form
500 // could collide with a future official ACP method and must not be used.
501 const sessionReloadExtensionsMethod = "_reasonix.io/session/reloadExtensions"
502
503 // StopReason tells the client why a turn ended. Values match main's wire.
504 type StopReason string
505
506 const (
507 StopEndTurn StopReason = "end_turn"
508 StopCancelled StopReason = "cancelled"
509 StopError StopReason = "error"
510 )
511
512 // SessionPromptResult ends a session/prompt. TranscriptPath is reserved for a
513 // future on-disk transcript pointer; omitted (null) for now.
514 type SessionPromptResult struct {
515 StopReason StopReason `json:"stopReason"`
516 TranscriptPath *string `json:"transcriptPath,omitempty"`
517 }
518
519 // --- session/update (agent → client notifications) ---
520 //
521 // SessionUpdate is a tagged union discriminated by sessionUpdate. The variants
522 // reuse the JSON key "content" with two incompatible shapes (a single block for
523 // message chunks, an array for tool results), so we model each variant as its own
524 // struct rather than one struct with conflicting tags, and carry it through
525 // SessionUpdateParams.Update as an interface value.
526
527 // SessionUpdateParams wraps one update for a session.
528 type SessionUpdateParams struct {
529 SessionID string `json:"sessionId"`
530 Update any `json:"update"`
531 }
532
533 // messageChunk is agent_message_chunk / agent_thought_chunk.
534 type messageChunk struct {
535 SessionUpdate string `json:"sessionUpdate"`
536 Content ContentBlock `json:"content"`
537 Metadata *updateMeta `json:"metadata,omitempty"`
538 }
539
540 // extensionSurfaceUpdate is the vendor session/update variant that carries one
541 // structured extension-UI surface to clients that negotiated
542 // reasonix.extensionSurface in initialize. ACP has no standard notification for
543 // extension surfaces, so the DTO (the shared eventwire JSON contract) rides
544 // _meta["reasonix.io"]["extensionSurface"], mirroring how the initialize
545 // handshake namespaces vendor data under "reasonix.io". The sink always pairs
546 // it with a flattened agent_message_chunk text fallback (belt and suspenders):
547 // a client that ignores the vendor variant still shows the content.
548 type extensionSurfaceUpdate struct {
549 SessionUpdate string `json:"sessionUpdate"`
550 Meta map[string]any `json:"_meta"`
551 }
552
553 // updateMeta carries optional error detail on an agent_message_chunk.
554 type updateMeta struct {
555 Error *updateError `json:"error,omitempty"`
556 }
557
558 type updateError struct {
559 Name string `json:"name"`
560 Message string `json:"message"`
561 }
562
563 // toolCall is a "tool_call" update (announces a call, with title/kind/rawInput).
564 type toolCall struct {
565 SessionUpdate string `json:"sessionUpdate"`
566 ToolCallID string `json:"toolCallId"`
567 Title string `json:"title,omitempty"`
568 Kind string `json:"kind,omitempty"`
569 Status string `json:"status,omitempty"`
570 RawInput json.RawMessage `json:"rawInput,omitempty"`
571 Locations []ToolCallLocation `json:"locations,omitempty"`
572 }
573
574 // ToolCallLocation names a file (and optionally a line) a tool call touches, so
575 // the client can follow along in the editor.
576 type ToolCallLocation struct {
577 Path string `json:"path"`
578 Line *int `json:"line,omitempty"`
579 }
580
581 // toolCallUpdateMsg is a "tool_call_update" update (status + result content).
582 type toolCallUpdateMsg struct {
583 SessionUpdate string `json:"sessionUpdate"`
584 ToolCallID string `json:"toolCallId"`
585 Status string `json:"status,omitempty"`
586 Content []toolContent `json:"content,omitempty"`
587 }
588
589 // toolContent wraps a tool result's text, per the ACP tool_call_update shape.
590 type toolContent struct {
591 Type string `json:"type"`
592 Content ContentBlock `json:"content"`
593 }
594
595 // availableCommandsUpdate advertises slash commands that the ACP client may
596 // surface in its composer. The client sends invocations back as normal
597 // session/prompt text such as "/review diff".
598 type availableCommandsUpdate struct {
599 SessionUpdate string `json:"sessionUpdate"`
600 AvailableCommands []AvailableCommand `json:"availableCommands"`
601 }
602
603 // AvailableCommand is one slash command available in a session.
604 type AvailableCommand struct {
605 Name string `json:"name"`
606 Description string `json:"description"`
607 Input *AvailableCommandInput `json:"input,omitempty"`
608 }
609
610 // AvailableCommandInput describes a command's free-form text argument.
611 type AvailableCommandInput struct {
612 Hint string `json:"hint"`
613 }
614
615 // configOptionUpdate reports a complete refreshed session config state.
616 type configOptionUpdate struct {
617 SessionUpdate string `json:"sessionUpdate"`
618 ConfigOptions []SessionConfigOption `json:"configOptions"`
619 }
620
621 // planUpdate is a "plan" update: the agent's current task list. Each update
622 // carries the complete plan and replaces the previous one, mirroring the
623 // todo_write contract it is derived from.
624 type planUpdate struct {
625 SessionUpdate string `json:"sessionUpdate"`
626 Entries []PlanEntry `json:"entries"`
627 }
628
629 // PlanEntry is one task in a plan update.
630 type PlanEntry struct {
631 Content string `json:"content"`
632 Priority string `json:"priority"`
633 Status string `json:"status"`
634 }
635
636 // currentModeUpdate reports that the session switched operating modes.
637 type currentModeUpdate struct {
638 SessionUpdate string `json:"sessionUpdate"`
639 CurrentModeID string `json:"currentModeId"`
640 }
641
642 // --- fs/* (agent → client requests) ---
643
644 // FSReadTextFileParams asks the client for a file's current text, including
645 // unsaved editor state. Line (1-based) and Limit page the content; Reasonix
646 // always reads whole files and pages locally, so it sends neither.
647 type FSReadTextFileParams struct {
648 SessionID string `json:"sessionId"`
649 Path string `json:"path"`
650 Line *int `json:"line,omitempty"`
651 Limit *int `json:"limit,omitempty"`
652 }
653
654 // FSReadTextFileResult carries the file content.
655 type FSReadTextFileResult struct {
656 Content string `json:"content"`
657 }
658
659 // FSWriteTextFileParams asks the client to write content to path, updating any
660 // open buffer as well as the file on disk.
661 type FSWriteTextFileParams struct {
662 SessionID string `json:"sessionId"`
663 Path string `json:"path"`
664 Content string `json:"content"`
665 }
666
667 // --- terminal/* (agent → client requests) ---
668
669 // TerminalCreateParams starts a command in a client-owned terminal.
670 // Env follows ACP v1's official EnvVariable[] shape (same as MCP env): only
671 // the overrides Reasonix owns (typically TMPDIR/TMP/TEMP) are sent — never a
672 // full host environment dump.
673 type TerminalCreateParams struct {
674 SessionID string `json:"sessionId"`
675 Command string `json:"command"`
676 Args []string `json:"args,omitempty"`
677 Cwd string `json:"cwd,omitempty"`
678 Env []EnvVariable `json:"env,omitempty"`
679 OutputByteLimit int `json:"outputByteLimit,omitempty"`
680 }
681
682 // TerminalCreateResult returns the id used by the other terminal methods.
683 type TerminalCreateResult struct {
684 TerminalID string `json:"terminalId"`
685 }
686
687 // TerminalIDParams addresses one terminal (output / kill / wait / release).
688 type TerminalIDParams struct {
689 SessionID string `json:"sessionId"`
690 TerminalID string `json:"terminalId"`
691 }
692
693 // TerminalOutputResult is the terminal's captured output so far.
694 type TerminalOutputResult struct {
695 Output string `json:"output"`
696 Truncated bool `json:"truncated"`
697 ExitStatus *TerminalExitStatus `json:"exitStatus,omitempty"`
698 }
699
700 // TerminalWaitResult reports how the command exited.
701 type TerminalWaitResult struct {
702 ExitCode *int `json:"exitCode,omitempty"`
703 Signal *string `json:"signal,omitempty"`
704 }
705
706 // TerminalExitStatus mirrors TerminalWaitResult inside terminal/output.
707 type TerminalExitStatus struct {
708 ExitCode *int `json:"exitCode,omitempty"`
709 Signal *string `json:"signal,omitempty"`
710 }
711
712 // --- session/cancel (client → agent notification) ---
713
714 // SessionCancelParams cancels an in-progress turn.
715 type SessionCancelParams struct {
716 SessionID string `json:"sessionId"`
717 }
718
719 // --- session/request_permission (agent → client request) ---
720
721 // PermissionOptionKind classifies an option for host UI styling. It is an ACP v1
722 // wire enum, so host-visible permission choices must stay within the official
723 // protocol values.
724 type PermissionOptionKind string
725
726 const (
727 OptAllowOnce PermissionOptionKind = "allow_once"
728 OptAllowAlways PermissionOptionKind = "allow_always"
729 OptRejectOnce PermissionOptionKind = "reject_once"
730 OptRejectAlways PermissionOptionKind = "reject_always"
731 )
732
733 // PermissionOption is one choice offered to the user for a permission request.
734 type PermissionOption struct {
735 OptionID string `json:"optionId"`
736 Name string `json:"name"`
737 Kind PermissionOptionKind `json:"kind"`
738 }
739
740 // PermissionRequestParams asks the client to approve a pending tool call.
741 type PermissionRequestParams struct {
742 SessionID string `json:"sessionId"`
743 ToolCall PermissionToolCall `json:"toolCall"`
744 Options []PermissionOption `json:"options"`
745 }
746
747 // PermissionToolCall describes the call awaiting approval.
748 type PermissionToolCall struct {
749 ToolCallID string `json:"toolCallId"`
750 Title string `json:"title,omitempty"`
751 Kind string `json:"kind,omitempty"`
752 Status string `json:"status,omitempty"`
753 Content []toolContent `json:"content,omitempty"`
754 RawInput json.RawMessage `json:"rawInput,omitempty"`
755 Locations []ToolCallLocation `json:"locations,omitempty"`
756 Meta map[string]any `json:"_meta,omitempty"`
757 }
758
759 // PermissionRequestResult is the client's reply to a permission request.
760 type PermissionRequestResult struct {
761 Outcome PermissionOutcome `json:"outcome"`
762 }
763
764 // PermissionOutcome is "selected" (with optionId) or "cancelled".
765 type PermissionOutcome struct {
766 Outcome string `json:"outcome"`
767 OptionID string `json:"optionId,omitempty"`
768 }
769
769 lines GO