Skip to main content

recursive/acp/
protocol.rs

1//! ACP v1 protocol types — pure data re-exports from `agent-client-protocol-schema`.
2//!
3//! This module is a thin re-export layer. All wire types come from the upstream
4//! `agent-client-protocol-schema` crate (fixed version). No runtime dependencies,
5//! no async, no I/O — just `serde`-backed structs and enums.
6//!
7//! Covers the full ACP v1 spec: initialize, session lifecycle, prompt turns,
8//! tool calls, permissions, content blocks, capabilities, and protocol-level
9//! notifications.
10//!
11//! See: <https://agentclientprotocol.com/protocol/v1/>
12
13use agent_client_protocol_schema::v1;
14
15// ── Requests ────────────────────────────────────────────────────────────
16
17/// `authenticate` request from client to agent.
18pub use v1::AuthenticateRequest;
19/// `session/close` request from client to agent.
20pub use v1::CloseSessionRequest;
21/// `session/delete` request from client to agent.
22pub use v1::DeleteSessionRequest;
23/// `initialize` request from client to agent.
24pub use v1::InitializeRequest;
25/// `session/list` request from client to agent.
26pub use v1::ListSessionsRequest;
27/// `session/load` request from client to agent.
28pub use v1::LoadSessionRequest;
29/// `logout` request from client to agent.
30pub use v1::LogoutRequest;
31/// `session/new` request from client to agent.
32pub use v1::NewSessionRequest;
33/// `session/prompt` request from client to agent.
34pub use v1::PromptRequest;
35/// `session/resume` request from client to agent.
36pub use v1::ResumeSessionRequest;
37/// `session/setConfigOption` request from client to agent.
38pub use v1::SetSessionConfigOptionRequest;
39/// `session/setMode` request from client to agent.
40pub use v1::SetSessionModeRequest;
41
42// ── Responses ───────────────────────────────────────────────────────────
43
44/// `authenticate` response from agent to client.
45pub use v1::AuthenticateResponse;
46/// `session/close` response from agent to client.
47pub use v1::CloseSessionResponse;
48/// `session/delete` response from agent to client.
49pub use v1::DeleteSessionResponse;
50/// `initialize` response from agent to client.
51pub use v1::InitializeResponse;
52/// `session/list` response from agent to client.
53pub use v1::ListSessionsResponse;
54/// `session/load` response from agent to client.
55pub use v1::LoadSessionResponse;
56/// `logout` response from agent to client.
57pub use v1::LogoutResponse;
58/// `session/new` response from agent to client.
59pub use v1::NewSessionResponse;
60/// `session/prompt` response from agent to client.
61pub use v1::PromptResponse;
62/// `session/resume` response from agent to client.
63pub use v1::ResumeSessionResponse;
64/// `session/setConfigOption` response from agent to client.
65pub use v1::SetSessionConfigOptionResponse;
66/// `session/setMode` response from agent to client.
67pub use v1::SetSessionModeResponse;
68
69// ── Notifications ───────────────────────────────────────────────────────
70
71/// Notification sent by the agent.
72pub use v1::AgentNotification;
73/// `notifications/cancel` notification.
74pub use v1::CancelNotification;
75/// `notifications/cancelled` protocol-level notification.
76pub use v1::CancelRequestNotification;
77/// Notification sent by the client.
78pub use v1::ClientNotification;
79/// Extension notification.
80pub use v1::ExtNotification;
81/// Protocol-level notification enum (cancel, etc.).
82pub use v1::ProtocolLevelNotification;
83/// Session-scoped notification from agent to client.
84pub use v1::SessionNotification;
85
86// ── Enums (request/response/notification routing) ───────────────────────
87
88/// All requests an agent can receive.
89pub use v1::AgentRequest;
90/// All responses an agent can send.
91pub use v1::AgentResponse;
92/// All requests a client can receive.
93pub use v1::ClientRequest;
94/// All responses a client can send.
95pub use v1::ClientResponse;
96
97// ── Tool calls ──────────────────────────────────────────────────────────
98
99/// A tool call the agent is requested to make.
100pub use v1::ToolCall;
101/// Content produced by a tool call (content block / diff / terminal).
102pub use v1::ToolCallContent;
103/// Unique identifier for a tool call within a session.
104pub use v1::ToolCallId;
105/// A file location accessed or modified by a tool.
106pub use v1::ToolCallLocation;
107/// Execution status of a tool call.
108pub use v1::ToolCallStatus;
109/// An update to an existing tool call (status, content, etc.).
110pub use v1::ToolCallUpdate;
111/// Fields that can be updated in a tool call update.
112pub use v1::ToolCallUpdateFields;
113// `ToolKind` is defined locally in `src/acp/tool_kind.rs` (Sprint 3).
114// The upstream `v1::ToolKind` is missing `Write` and `WebSearch` variants.
115
116// ── Content blocks ──────────────────────────────────────────────────────
117
118/// Audio content.
119pub use v1::AudioContent;
120/// Binary resource contents.
121pub use v1::BlobResourceContents;
122/// A block of content (text, image, audio, resource).
123pub use v1::ContentBlock;
124/// A chunk of streamed content.
125pub use v1::ContentChunk;
126/// An embedded resource with full content.
127pub use v1::EmbeddedResource;
128/// Embedded resource variants.
129pub use v1::EmbeddedResourceResource;
130/// Base64-encoded image content.
131pub use v1::ImageContent;
132/// A link to an external resource.
133pub use v1::ResourceLink;
134/// The role of a message (user / agent).
135pub use v1::Role;
136/// Plain text content.
137pub use v1::TextContent;
138/// Text resource contents.
139pub use v1::TextResourceContents;
140
141// ── Metadata / annotations ──────────────────────────────────────────────
142
143/// Annotations attached to content blocks.
144pub use v1::Annotations;
145/// Cost information for a turn.
146pub use v1::Cost;
147/// Arbitrary metadata (opaque JSON object).
148pub use v1::Meta;
149/// A usage update during a session.
150pub use v1::UsageUpdate;
151
152// ── Capabilities ────────────────────────────────────────────────────────
153
154/// Capabilities declared by the agent.
155pub use v1::AgentCapabilities;
156/// Boolean config option capabilities.
157pub use v1::BooleanConfigOptionCapabilities;
158/// Capabilities declared by the client.
159pub use v1::ClientCapabilities;
160/// Client-specific session capabilities.
161pub use v1::ClientSessionCapabilities;
162/// File-system capabilities.
163pub use v1::FileSystemCapabilities;
164/// MCP-related capabilities.
165pub use v1::McpCapabilities;
166/// Prompt-related capabilities.
167pub use v1::PromptCapabilities;
168/// Session additional directories capabilities.
169pub use v1::SessionAdditionalDirectoriesCapabilities;
170/// Capabilities for a specific session.
171pub use v1::SessionCapabilities;
172/// Session close capabilities.
173pub use v1::SessionCloseCapabilities;
174/// Session config options capabilities.
175pub use v1::SessionConfigOptionsCapabilities;
176/// Session delete capabilities.
177pub use v1::SessionDeleteCapabilities;
178/// Session list capabilities.
179pub use v1::SessionListCapabilities;
180/// Session resume capabilities.
181pub use v1::SessionResumeCapabilities;
182
183// ── Permissions ─────────────────────────────────────────────────────────
184
185/// A permission option presented to the user.
186pub use v1::PermissionOption;
187/// A permission option ID.
188pub use v1::PermissionOptionId;
189/// The kind of permission option.
190pub use v1::PermissionOptionKind;
191/// The outcome the client selected for a permission request.
192pub use v1::RequestPermissionOutcome;
193/// A permission request to the client.
194pub use v1::RequestPermissionRequest;
195/// The client's response to a permission request.
196pub use v1::RequestPermissionResponse;
197/// The outcome selected by the user.
198pub use v1::SelectedPermissionOutcome;
199
200// ── Session types ───────────────────────────────────────────────────────
201
202/// A boolean config option.
203pub use v1::SessionConfigBoolean;
204/// A config group ID.
205pub use v1::SessionConfigGroupId;
206/// A config ID.
207pub use v1::SessionConfigId;
208/// A config option kind.
209pub use v1::SessionConfigKind;
210/// A config option for a session.
211pub use v1::SessionConfigOption;
212/// Config option category.
213pub use v1::SessionConfigOptionCategory;
214/// A config option value.
215pub use v1::SessionConfigOptionValue;
216/// A select config option.
217pub use v1::SessionConfigSelect;
218/// A select config group.
219pub use v1::SessionConfigSelectGroup;
220/// A select config option item.
221pub use v1::SessionConfigSelectOption;
222/// Select options grouping.
223pub use v1::SessionConfigSelectOptions;
224/// A config value ID.
225pub use v1::SessionConfigValueId;
226/// A session identifier.
227pub use v1::SessionId;
228/// Information about a session.
229pub use v1::SessionInfo;
230/// A session mode.
231pub use v1::SessionMode;
232/// A session mode identifier.
233pub use v1::SessionModeId;
234/// Current mode state.
235pub use v1::SessionModeState;
236
237// ── Session update types ────────────────────────────────────────────────
238
239/// An available commands update.
240pub use v1::AvailableCommandsUpdate;
241/// A config option update.
242pub use v1::ConfigOptionUpdate;
243/// A current mode update.
244pub use v1::CurrentModeUpdate;
245/// A session info update.
246pub use v1::SessionInfoUpdate;
247/// Types of updates that can be sent during session processing.
248pub use v1::SessionUpdate;
249
250// ── Plan types ──────────────────────────────────────────────────────────
251
252/// A plan for execution.
253pub use v1::Plan;
254/// An entry in a plan.
255pub use v1::PlanEntry;
256/// The priority of a plan entry.
257pub use v1::PlanEntryPriority;
258/// The status of a plan entry.
259pub use v1::PlanEntryStatus;
260
261// ── Terminal types ──────────────────────────────────────────────────────
262
263/// Terminal exit status.
264pub use v1::TerminalExitStatus;
265/// A terminal identifier.
266pub use v1::TerminalId;
267
268// ── MCP server config ───────────────────────────────────────────────────
269
270/// An MCP server configuration.
271pub use v1::McpServer;
272/// HTTP-based MCP server config.
273pub use v1::McpServerHttp;
274/// SSE-based MCP server config.
275pub use v1::McpServerSse;
276/// Stdio-based MCP server config.
277pub use v1::McpServerStdio;
278
279// ── Auth ────────────────────────────────────────────────────────────────
280
281/// Agent auth capabilities.
282pub use v1::AgentAuthCapabilities;
283/// An authentication method.
284pub use v1::AuthMethod;
285/// Agent-side auth method.
286pub use v1::AuthMethodAgent;
287/// An auth method ID.
288pub use v1::AuthMethodId;
289/// Logout capabilities.
290pub use v1::LogoutCapabilities;
291
292// ── General ─────────────────────────────────────────────────────────────
293
294/// Agent method names.
295pub use v1::AgentMethodNames;
296/// Available command.
297pub use v1::AvailableCommand;
298/// Input type for an available command.
299pub use v1::AvailableCommandInput;
300/// Client method names.
301pub use v1::ClientMethodNames;
302/// A diff representing file modifications.
303pub use v1::Diff;
304/// An environment variable definition.
305pub use v1::EnvVariable;
306/// General method names.
307pub use v1::GeneralMethodNames;
308/// An HTTP header definition.
309pub use v1::HttpHeader;
310/// Information about an implementation (name, version).
311pub use v1::Implementation;
312/// A message identifier.
313pub use v1::MessageId;
314/// Reason the agent stopped a turn.
315pub use v1::StopReason;
316/// Unstructured command input.
317pub use v1::UnstructuredCommandInput;
318
319// ── Errors ──────────────────────────────────────────────────────────────
320
321/// An ACP protocol error.
322pub use v1::Error;
323/// ACP error codes.
324pub use v1::ErrorCode;
325
326// ── Extension types ─────────────────────────────────────────────────────
327
328/// Extension request.
329pub use v1::ExtRequest;
330/// Extension response.
331pub use v1::ExtResponse;
332
333// ── Version ─────────────────────────────────────────────────────────────
334
335/// Protocol version identifier (v0, v1, v2).
336pub use agent_client_protocol_schema::ProtocolVersion;
337
338// ── JSON-RPC primitives ─────────────────────────────────────────────────
339
340/// A JSON-RPC response envelope.
341pub use v1::Response;
342
343// ═══════════════════════════════════════════════════════════════════════════
344// Tests
345// ═══════════════════════════════════════════════════════════════════════════
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    // ── Helper ──────────────────────────────────────────────────────
352
353    /// Serialise round-trip: value → JSON → value', assert equality.
354    fn assert_roundtrip<
355        T: serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + PartialEq,
356    >(
357        val: &T,
358    ) {
359        let json = serde_json::to_string(val).expect("serialize");
360        let val2: T = serde_json::from_str(&json).expect("deserialize");
361        assert_eq!(val, &val2, "roundtrip failed for JSON: {json}");
362    }
363
364    /// Round-trip from a JSON literal: JSON → value → JSON' → value', compare value == value'.
365    fn assert_json_roundtrip<
366        T: serde::de::DeserializeOwned + serde::Serialize + std::fmt::Debug + PartialEq,
367    >(
368        json: &str,
369    ) {
370        let val: T = serde_json::from_str(json).expect("deserialize from literal");
371        let serialized = serde_json::to_string(&val).expect("serialize");
372        let val2: T = serde_json::from_str(&serialized).expect("deserialize from serialized");
373        assert_eq!(
374            val, val2,
375            "json roundtrip failed\n  input: {json}\n  serialized: {serialized}"
376        );
377    }
378
379    // ── C0-04 enum variant tests ────────────────────────────────────
380
381    #[test]
382    fn tool_kind_variants_roundtrip() {
383        for kind in [
384            crate::acp::ToolKind::Read,
385            crate::acp::ToolKind::Edit,
386            crate::acp::ToolKind::Write,
387            crate::acp::ToolKind::Execute,
388            crate::acp::ToolKind::Search,
389            crate::acp::ToolKind::Fetch,
390            crate::acp::ToolKind::WebSearch,
391            crate::acp::ToolKind::Other,
392        ] {
393            assert_roundtrip(&kind);
394        }
395    }
396
397    #[test]
398    fn tool_call_status_variants_roundtrip() {
399        for status in [
400            ToolCallStatus::Pending,
401            ToolCallStatus::InProgress,
402            ToolCallStatus::Completed,
403            ToolCallStatus::Failed,
404        ] {
405            assert_roundtrip(&status);
406        }
407    }
408
409    #[test]
410    fn stop_reason_variants_roundtrip() {
411        for reason in [
412            StopReason::EndTurn,
413            StopReason::Cancelled,
414            StopReason::MaxTokens,
415            StopReason::MaxTurnRequests,
416            StopReason::Refusal,
417        ] {
418            assert_roundtrip(&reason);
419        }
420    }
421
422    #[test]
423    fn permission_option_kind_variants_roundtrip() {
424        for kind in [
425            PermissionOptionKind::AllowOnce,
426            PermissionOptionKind::AllowAlways,
427            PermissionOptionKind::RejectOnce,
428            PermissionOptionKind::RejectAlways,
429        ] {
430            assert_roundtrip(&kind);
431        }
432    }
433
434    #[test]
435    fn plan_entry_status_variants_roundtrip() {
436        for status in [
437            PlanEntryStatus::Pending,
438            PlanEntryStatus::InProgress,
439            PlanEntryStatus::Completed,
440        ] {
441            assert_roundtrip(&status);
442        }
443    }
444
445    #[test]
446    fn plan_entry_priority_variants_roundtrip() {
447        for priority in [
448            PlanEntryPriority::Low,
449            PlanEntryPriority::Medium,
450            PlanEntryPriority::High,
451        ] {
452            assert_roundtrip(&priority);
453        }
454    }
455
456    #[test]
457    fn error_code_variants_roundtrip() {
458        for code in [
459            ErrorCode::ParseError,
460            ErrorCode::InvalidRequest,
461            ErrorCode::MethodNotFound,
462            ErrorCode::InvalidParams,
463            ErrorCode::InternalError,
464        ] {
465            assert_roundtrip(&code);
466        }
467    }
468
469    #[test]
470    fn role_variants_roundtrip() {
471        assert_roundtrip(&Role::User);
472        assert_roundtrip(&Role::Assistant);
473    }
474
475    #[test]
476    fn session_config_kind_variants_roundtrip() {
477        // SessionConfigKind is internally tagged on "type".
478        // Boolean variant: needs current_value (required).
479        assert_json_roundtrip::<SessionConfigKind>(r#"{"type":"boolean","currentValue":true}"#);
480        // Select variant: currentValue + options. SessionConfigSelectOptions is untagged;
481        // Ungrouped variant is just a Vec<SessionConfigSelectOption>, so JSON is an array.
482        assert_json_roundtrip::<SessionConfigKind>(
483            r#"{"type":"select","currentValue":"a","options":[{"value":"a","name":"A"}]}"#,
484        );
485    }
486
487    #[test]
488    fn content_block_variants_roundtrip() {
489        // Text
490        assert_json_roundtrip::<ContentBlock>(r#"{"type":"text","text":"hello"}"#);
491        // Image
492        assert_json_roundtrip::<ContentBlock>(
493            r#"{"type":"image","data":"AAAA","mimeType":"image/png"}"#,
494        );
495        // Audio
496        assert_json_roundtrip::<ContentBlock>(
497            r#"{"type":"audio","data":"AAAA","mimeType":"audio/wav"}"#,
498        );
499        // ResourceLink
500        assert_json_roundtrip::<ContentBlock>(
501            r#"{"type":"resource_link","uri":"file:///tmp/data.txt","name":"data.txt","mimeType":"text/plain"}"#,
502        );
503        // Resource (embedded)
504        assert_json_roundtrip::<ContentBlock>(
505            r#"{"type":"resource","resource":{"uri":"file:///tmp/x","mimeType":"text/plain","text":"content"}}"#,
506        );
507    }
508
509    #[test]
510    fn tool_call_content_variants_roundtrip() {
511        // Content variant
512        assert_json_roundtrip::<ToolCallContent>(
513            r#"{"type":"content","content":{"type":"text","text":"output"}}"#,
514        );
515        // Diff variant (fields flattened by internal tagging on "type")
516        assert_json_roundtrip::<ToolCallContent>(
517            r#"{"type":"diff","path":"/tmp/file.txt","newText":"hello"}"#,
518        );
519        // Terminal variant (fields flattened; terminalId required)
520        assert_json_roundtrip::<ToolCallContent>(r#"{"type":"terminal","terminalId":"t1"}"#);
521    }
522
523    #[test]
524    fn session_update_variants_roundtrip() {
525        // user_message_chunk
526        assert_json_roundtrip::<SessionUpdate>(
527            r#"{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"hello"}}"#,
528        );
529        // agent_message_chunk
530        assert_json_roundtrip::<SessionUpdate>(
531            r#"{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"hi"}}"#,
532        );
533        // tool_call (flattened)
534        assert_json_roundtrip::<SessionUpdate>(
535            r#"{"sessionUpdate":"tool_call","toolCallId":"tc1","title":"test","kind":"bash","status":"in_progress"}"#,
536        );
537        // tool_call_update
538        assert_json_roundtrip::<SessionUpdate>(
539            r#"{"sessionUpdate":"tool_call_update","toolCallId":"tc1","status":"completed"}"#,
540        );
541    }
542
543    #[test]
544    fn mcp_server_variants_roundtrip() {
545        // stdio (untagged fallback — no `type` field; McpServerStdio requires name/command/env)
546        assert_json_roundtrip::<McpServer>(
547            r#"{"name":"fs","command":"node","args":["server.js"],"env":[]}"#,
548        );
549        // http (tagged)
550        assert_json_roundtrip::<McpServer>(
551            r#"{"type":"http","name":"api","url":"http://localhost:8080","headers":[]}"#,
552        );
553        // sse (tagged)
554        assert_json_roundtrip::<McpServer>(
555            r#"{"type":"sse","name":"events","url":"http://localhost:8080/sse","headers":[]}"#,
556        );
557    }
558
559    #[test]
560    fn embedded_resource_resource_variants_roundtrip() {
561        // Text resource
562        assert_json_roundtrip::<EmbeddedResourceResource>(
563            r#"{"uri":"file:///tmp/x","mimeType":"text/plain","text":"hello"}"#,
564        );
565        // Blob resource
566        assert_json_roundtrip::<EmbeddedResourceResource>(
567            r#"{"uri":"file:///tmp/x","mimeType":"application/octet-stream","blob":"AAAA"}"#,
568        );
569    }
570
571    #[test]
572    fn auth_method_variants_roundtrip() {
573        // AuthMethod::Agent is the only stable variant (others are feature-gated). It is
574        // untagged, so JSON shape is just the inner struct's fields.
575        assert_json_roundtrip::<AuthMethod>(r#"{"id":"github","name":"GitHub"}"#);
576    }
577
578    #[test]
579    fn available_command_input_variants_roundtrip() {
580        // AvailableCommandInput is untagged; only the Unstructured variant is stable.
581        assert_json_roundtrip::<AvailableCommandInput>(r#"{"hint":"query"}"#);
582    }
583
584    // ── C0-05 struct roundtrip tests ────────────────────────────────
585
586    #[test]
587    fn initialize_request_roundtrip() {
588        assert_json_roundtrip::<InitializeRequest>(
589            r#"{"protocolVersion":1,"clientCapabilities":{"terminal":true},"clientInfo":{"name":"zed","version":"1.0"}}"#,
590        );
591    }
592
593    #[test]
594    fn initialize_response_roundtrip() {
595        assert_json_roundtrip::<InitializeResponse>(
596            r#"{"protocolVersion":1,"agentInfo":{"name":"recursive","version":"0.7.0"},"agentCapabilities":{"promptCapabilities":{"text":true}}}"#,
597        );
598    }
599
600    #[test]
601    fn new_session_request_roundtrip() {
602        assert_json_roundtrip::<NewSessionRequest>(r#"{"cwd":"/tmp","mcpServers":[]}"#);
603    }
604
605    #[test]
606    fn new_session_response_roundtrip() {
607        assert_json_roundtrip::<NewSessionResponse>(r#"{"sessionId":"s1","capabilities":{}}"#);
608    }
609
610    #[test]
611    fn prompt_request_roundtrip() {
612        assert_json_roundtrip::<PromptRequest>(
613            r#"{"sessionId":"s1","prompt":[{"type":"text","text":"hello"}]}"#,
614        );
615    }
616
617    #[test]
618    fn prompt_response_roundtrip() {
619        assert_json_roundtrip::<PromptResponse>(r#"{"stopReason":"end_turn"}"#);
620    }
621
622    #[test]
623    fn cancel_notification_roundtrip() {
624        assert_json_roundtrip::<CancelNotification>(
625            r#"{"sessionId":"s1","reason":"user request"}"#,
626        );
627    }
628
629    #[test]
630    fn session_notification_roundtrip() {
631        assert_json_roundtrip::<SessionNotification>(
632            r#"{"sessionId":"s1","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"hi"}}}"#,
633        );
634    }
635
636    #[test]
637    fn tool_call_roundtrip() {
638        assert_json_roundtrip::<ToolCall>(
639            r#"{"toolCallId":"tc1","title":"test","kind":"bash","status":"in_progress"}"#,
640        );
641    }
642
643    #[test]
644    fn tool_call_update_roundtrip() {
645        assert_json_roundtrip::<ToolCallUpdate>(r#"{"toolCallId":"tc1","status":"completed"}"#);
646    }
647
648    #[test]
649    fn tool_call_location_roundtrip() {
650        assert_json_roundtrip::<ToolCallLocation>(r#"{"path":"/tmp/file.txt","line":42}"#);
651    }
652
653    #[test]
654    fn diff_roundtrip() {
655        assert_json_roundtrip::<Diff>(r#"{"path":"/tmp/file.txt","newText":"hello"}"#);
656    }
657
658    #[test]
659    fn annotations_roundtrip() {
660        assert_json_roundtrip::<Annotations>(r#"{"audience":["user"],"priority":0.5}"#);
661    }
662
663    #[test]
664    fn cost_roundtrip() {
665        assert_json_roundtrip::<Cost>(r#"{"amount":0.05,"currency":"USD"}"#);
666    }
667
668    #[test]
669    fn content_chunk_roundtrip() {
670        assert_json_roundtrip::<ContentChunk>(
671            r#"{"type":"text","content":{"type":"text","text":"hello"}}"#,
672        );
673    }
674
675    #[test]
676    fn agent_capabilities_roundtrip() {
677        assert_json_roundtrip::<AgentCapabilities>(
678            r#"{"promptCapabilities":{"text":true},"mcpCapabilities":{"http":true}}"#,
679        );
680    }
681
682    #[test]
683    fn client_capabilities_roundtrip() {
684        assert_json_roundtrip::<ClientCapabilities>(r#"{"terminal":true}"#);
685    }
686
687    #[test]
688    fn session_capabilities_roundtrip() {
689        assert_json_roundtrip::<SessionCapabilities>(r#"{}"#);
690    }
691
692    #[test]
693    fn request_permission_request_roundtrip() {
694        // toolCall is required (a ToolCallUpdate), not toolCallId.
695        assert_json_roundtrip::<RequestPermissionRequest>(
696            r#"{"sessionId":"s1","toolCall":{"toolCallId":"tc1"},"options":[{"kind":"allow_once","optionId":"opt1","name":"Allow"}]}"#,
697        );
698    }
699
700    #[test]
701    fn request_permission_response_roundtrip() {
702        assert_json_roundtrip::<RequestPermissionResponse>(
703            r#"{"outcome":{"outcome":"selected","optionId":"opt1"}}"#,
704        );
705    }
706
707    #[test]
708    fn implementation_roundtrip() {
709        assert_json_roundtrip::<Implementation>(r#"{"name":"recursive","version":"0.7.0"}"#);
710    }
711
712    #[test]
713    fn load_session_request_roundtrip() {
714        assert_json_roundtrip::<LoadSessionRequest>(
715            r#"{"sessionId":"s1","cwd":"/tmp","mcpServers":[]}"#,
716        );
717    }
718
719    #[test]
720    fn load_session_response_roundtrip() {
721        assert_json_roundtrip::<LoadSessionResponse>(r#"{"sessionId":"s1","messages":[]}"#);
722    }
723
724    #[test]
725    fn close_session_request_roundtrip() {
726        assert_json_roundtrip::<CloseSessionRequest>(r#"{"sessionId":"s1"}"#);
727    }
728
729    #[test]
730    fn close_session_response_roundtrip() {
731        assert_json_roundtrip::<CloseSessionResponse>(r#"{}"#);
732    }
733
734    #[test]
735    fn session_id_roundtrip() {
736        assert_json_roundtrip::<SessionId>(r#""session-abc-123""#);
737    }
738
739    #[test]
740    fn tool_call_id_roundtrip() {
741        assert_json_roundtrip::<ToolCallId>(r#""tc-xyz""#);
742    }
743
744    #[test]
745    fn message_id_roundtrip() {
746        assert_json_roundtrip::<MessageId>(r#""msg-456""#);
747    }
748
749    #[test]
750    fn terminal_id_roundtrip() {
751        assert_json_roundtrip::<TerminalId>(r#""term-1""#);
752    }
753
754    #[test]
755    fn text_content_roundtrip() {
756        assert_json_roundtrip::<TextContent>(r#"{"type":"text","text":"hello"}"#);
757    }
758
759    #[test]
760    fn image_content_roundtrip() {
761        assert_json_roundtrip::<ImageContent>(
762            r#"{"type":"image","data":"AAAA","mimeType":"image/png"}"#,
763        );
764    }
765
766    #[test]
767    fn plan_roundtrip() {
768        assert_json_roundtrip::<Plan>(r#"{"entries":[]}"#);
769    }
770
771    #[test]
772    fn plan_entry_roundtrip() {
773        assert_json_roundtrip::<PlanEntry>(
774            r#"{"id":"p1","content":"Do X","status":"pending","priority":"medium"}"#,
775        );
776    }
777
778    #[test]
779    fn session_mode_roundtrip() {
780        assert_json_roundtrip::<SessionMode>(r#"{"id":"default","name":"Default"}"#);
781    }
782
783    #[test]
784    fn mcp_capabilities_roundtrip() {
785        assert_json_roundtrip::<McpCapabilities>(r#"{"http":true,"sse":false}"#);
786    }
787
788    #[test]
789    fn file_system_capabilities_roundtrip() {
790        assert_json_roundtrip::<FileSystemCapabilities>(
791            r#"{"readTextFile":true,"writeTextFile":false}"#,
792        );
793    }
794
795    #[test]
796    fn error_roundtrip() {
797        assert_json_roundtrip::<Error>(r#"{"code":-32600,"message":"Invalid Request"}"#);
798    }
799
800    #[test]
801    fn usage_update_roundtrip() {
802        assert_json_roundtrip::<UsageUpdate>(r#"{"used":1500,"size":200000}"#);
803    }
804
805    #[test]
806    fn auth_method_agent_roundtrip() {
807        assert_json_roundtrip::<AuthMethodAgent>(r#"{"id":"github","name":"GitHub"}"#);
808    }
809
810    #[test]
811    fn env_variable_roundtrip() {
812        assert_json_roundtrip::<EnvVariable>(r#"{"name":"API_KEY","value":"secret123"}"#);
813    }
814
815    #[test]
816    fn http_header_roundtrip() {
817        assert_json_roundtrip::<HttpHeader>(r#"{"name":"Authorization","value":"Bearer token"}"#);
818    }
819
820    // ── C0-06 boundary / edge-case tests ────────────────────────────
821
822    #[test]
823    fn option_field_none() {
824        // Implementation has optional `title` field
825        assert_json_roundtrip::<Implementation>(r#"{"name":"test","version":"1.0"}"#);
826        // With title
827        assert_json_roundtrip::<Implementation>(
828            r#"{"name":"test","version":"1.0","title":"Test App"}"#,
829        );
830        // Annotations with None audience
831        assert_json_roundtrip::<Annotations>(r#"{}"#);
832        // ToolCallLocation with None line
833        assert_json_roundtrip::<ToolCallLocation>(r#"{"path":"/tmp/f"}"#);
834        // Diff with None old_text
835        assert_json_roundtrip::<Diff>(r#"{"path":"/tmp/f","newText":"new"}"#);
836    }
837
838    #[test]
839    fn vec_field_empty() {
840        // Plan with empty entries
841        assert_json_roundtrip::<Plan>(r#"{"entries":[]}"#);
842        // PromptRequest with empty prompt (pathological but valid)
843        assert_json_roundtrip::<PromptRequest>(r#"{"sessionId":"s1","prompt":[]}"#);
844        // LoadSessionResponse with empty messages
845        assert_json_roundtrip::<LoadSessionResponse>(r#"{"sessionId":"s1","messages":[]}"#);
846        // ToolCall with empty content & locations
847        assert_json_roundtrip::<ToolCall>(
848            r#"{"toolCallId":"tc1","title":"t","content":[],"locations":[]}"#,
849        );
850    }
851
852    #[test]
853    fn nested_enum_coverage() {
854        // ContentBlock::Text (nested within ContentChunk, SessionUpdate, ToolCallContent, etc.)
855        assert_json_roundtrip::<ContentChunk>(
856            r#"{"type":"text","content":{"type":"text","text":"hello"}}"#,
857        );
858        // ContentBlock::Image
859        assert_json_roundtrip::<ContentChunk>(
860            r#"{"type":"image","content":{"type":"image","data":"AAAA","mimeType":"image/png"}}"#,
861        );
862        // ContentBlock::ResourceLink
863        assert_json_roundtrip::<ContentChunk>(
864            r#"{"type":"resource_link","content":{"type":"resource_link","uri":"file:///x","name":"x","mimeType":"text/plain"}}"#,
865        );
866
867        // SessionNotification wraps SessionUpdate
868        assert_json_roundtrip::<SessionNotification>(
869            r#"{"sessionId":"s1","update":{"sessionUpdate":"tool_call","toolCallId":"tc1","title":"Run","kind":"execute","status":"in_progress"}}"#,
870        );
871    }
872
873    #[test]
874    fn enum_non_default_variant_roundtrip() {
875        // ToolKind non-default (default is Other)
876        assert_roundtrip(&crate::acp::ToolKind::Read);
877        assert_roundtrip(&crate::acp::ToolKind::Edit);
878        // ToolCallStatus non-default (default is Pending)
879        assert_roundtrip(&ToolCallStatus::Completed);
880        assert_roundtrip(&ToolCallStatus::Failed);
881        // StopReason non-default
882        assert_roundtrip(&StopReason::Cancelled);
883        assert_roundtrip(&StopReason::Refusal);
884    }
885
886    #[test]
887    fn session_update_plan_variant_roundtrip() {
888        // Plan update (internally tagged — fields flattened alongside sessionUpdate)
889        assert_json_roundtrip::<SessionUpdate>(r#"{"sessionUpdate":"plan","entries":[]}"#);
890    }
891
892    #[test]
893    fn session_update_usage_update_variant_roundtrip() {
894        assert_json_roundtrip::<SessionUpdate>(
895            r#"{"sessionUpdate":"usage_update","used":1000,"size":200000}"#,
896        );
897    }
898
899    #[test]
900    fn permission_option_all_kinds() {
901        for (kind_str, kind) in [
902            ("allow_once", PermissionOptionKind::AllowOnce),
903            ("allow_always", PermissionOptionKind::AllowAlways),
904            ("reject_once", PermissionOptionKind::RejectOnce),
905            ("reject_always", PermissionOptionKind::RejectAlways),
906        ] {
907            let json = format!(r#"{{"kind":"{kind_str}","optionId":"o1","name":"L"}}"#);
908            assert_json_roundtrip::<PermissionOption>(&json);
909            assert_roundtrip(&kind);
910        }
911    }
912}