Skip to main content

agent_client_protocol_schema/v2/
nes.rs

1//! Next Edit Suggestions (NES) types and constants.
2//!
3//! NES allows agents to provide predictive code edits via capability negotiation,
4//! document events, and a suggestion request/response flow. NES sessions are
5//! independent of chat sessions and have their own lifecycle.
6
7use std::collections::BTreeMap;
8
9use schemars::{JsonSchema, Schema};
10use serde::{Deserialize, Serialize};
11use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
12
13use super::{Meta, SessionId};
14use crate::{IntoOption, SkipListener};
15
16// Method name constants
17
18/// Method name for starting an NES session.
19pub(crate) const NES_START_METHOD_NAME: &str = "nes/start";
20/// Method name for requesting a suggestion.
21pub(crate) const NES_SUGGEST_METHOD_NAME: &str = "nes/suggest";
22/// Method name for accepting a suggestion.
23pub(crate) const NES_ACCEPT_METHOD_NAME: &str = "nes/accept";
24/// Method name for rejecting a suggestion.
25pub(crate) const NES_REJECT_METHOD_NAME: &str = "nes/reject";
26/// Method name for closing an NES session.
27pub(crate) const NES_CLOSE_METHOD_NAME: &str = "nes/close";
28/// Notification name for document open events.
29pub(crate) const DOCUMENT_DID_OPEN_METHOD_NAME: &str = "document/didOpen";
30/// Notification name for document change events.
31pub(crate) const DOCUMENT_DID_CHANGE_METHOD_NAME: &str = "document/didChange";
32/// Notification name for document close events.
33pub(crate) const DOCUMENT_DID_CLOSE_METHOD_NAME: &str = "document/didClose";
34/// Notification name for document save events.
35pub(crate) const DOCUMENT_DID_SAVE_METHOD_NAME: &str = "document/didSave";
36/// Notification name for document focus events.
37pub(crate) const DOCUMENT_DID_FOCUS_METHOD_NAME: &str = "document/didFocus";
38
39// Position primitives
40
41/// The encoding used for character offsets in positions.
42///
43/// Follows the same conventions as LSP 3.17. The default is UTF-16.
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum PositionEncodingKind {
47    /// Character offsets count UTF-16 code units. This is the default.
48    #[serde(rename = "utf-16")]
49    Utf16,
50    /// Character offsets count Unicode code points.
51    #[serde(rename = "utf-32")]
52    Utf32,
53    /// Character offsets count UTF-8 code units (bytes).
54    #[serde(rename = "utf-8")]
55    Utf8,
56}
57
58/// A zero-based position in a text document.
59///
60/// The meaning of `character` depends on the negotiated position encoding.
61#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
62#[serde(rename_all = "camelCase")]
63#[non_exhaustive]
64pub struct Position {
65    /// Zero-based line number.
66    pub line: u32,
67    /// Zero-based character offset (encoding-dependent).
68    pub character: u32,
69}
70
71impl Position {
72    #[must_use]
73    pub fn new(line: u32, character: u32) -> Self {
74        Self { line, character }
75    }
76}
77
78/// A range in a text document, expressed as start and end positions.
79#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
80#[serde(rename_all = "camelCase")]
81#[non_exhaustive]
82pub struct Range {
83    /// The start position (inclusive).
84    pub start: Position,
85    /// The end position (exclusive).
86    pub end: Position,
87}
88
89impl Range {
90    #[must_use]
91    pub fn new(start: Position, end: Position) -> Self {
92        Self { start, end }
93    }
94}
95
96// Agent NES capabilities
97
98/// NES capabilities advertised by the agent during initialization.
99#[serde_as]
100#[skip_serializing_none]
101#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
102#[serde(rename_all = "camelCase")]
103#[non_exhaustive]
104pub struct NesCapabilities {
105    /// Events the agent wants to receive.
106    #[serde_as(deserialize_as = "DefaultOnError")]
107    #[schemars(extend("x-deserialize-default-on-error" = true))]
108    #[serde(default)]
109    pub events: Option<NesEventCapabilities>,
110    /// Context the agent wants attached to each suggestion request.
111    #[serde_as(deserialize_as = "DefaultOnError")]
112    #[schemars(extend("x-deserialize-default-on-error" = true))]
113    #[serde(default)]
114    pub context: Option<NesContextCapabilities>,
115    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
116    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
117    /// these keys.
118    ///
119    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
120    #[serde(rename = "_meta")]
121    pub meta: Option<Meta>,
122}
123
124impl NesCapabilities {
125    #[must_use]
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    #[must_use]
131    pub fn events(mut self, events: impl IntoOption<NesEventCapabilities>) -> Self {
132        self.events = events.into_option();
133        self
134    }
135
136    #[must_use]
137    pub fn context(mut self, context: impl IntoOption<NesContextCapabilities>) -> Self {
138        self.context = context.into_option();
139        self
140    }
141
142    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
143    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
144    /// these keys.
145    ///
146    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
147    #[must_use]
148    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
149        self.meta = meta.into_option();
150        self
151    }
152}
153
154/// Event capabilities the agent can consume.
155#[serde_as]
156#[skip_serializing_none]
157#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
158#[serde(rename_all = "camelCase")]
159#[non_exhaustive]
160pub struct NesEventCapabilities {
161    /// Document event capabilities.
162    #[serde_as(deserialize_as = "DefaultOnError")]
163    #[schemars(extend("x-deserialize-default-on-error" = true))]
164    #[serde(default)]
165    pub document: Option<NesDocumentEventCapabilities>,
166    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
167    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
168    /// these keys.
169    ///
170    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
171    #[serde(rename = "_meta")]
172    pub meta: Option<Meta>,
173}
174
175impl NesEventCapabilities {
176    #[must_use]
177    pub fn new() -> Self {
178        Self::default()
179    }
180
181    #[must_use]
182    pub fn document(mut self, document: impl IntoOption<NesDocumentEventCapabilities>) -> Self {
183        self.document = document.into_option();
184        self
185    }
186
187    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
188    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
189    /// these keys.
190    ///
191    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
192    #[must_use]
193    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
194        self.meta = meta.into_option();
195        self
196    }
197}
198
199/// Document event capabilities the agent wants to receive.
200#[serde_as]
201#[skip_serializing_none]
202#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
203#[serde(rename_all = "camelCase")]
204#[non_exhaustive]
205pub struct NesDocumentEventCapabilities {
206    /// Whether the agent wants `document/didOpen` events.
207    #[serde_as(deserialize_as = "DefaultOnError")]
208    #[schemars(extend("x-deserialize-default-on-error" = true))]
209    #[serde(default)]
210    pub did_open: Option<NesDocumentDidOpenCapabilities>,
211    /// Whether the agent wants `document/didChange` events, and the sync kind.
212    #[serde_as(deserialize_as = "DefaultOnError")]
213    #[schemars(extend("x-deserialize-default-on-error" = true))]
214    #[serde(default)]
215    pub did_change: Option<NesDocumentDidChangeCapabilities>,
216    /// Whether the agent wants `document/didClose` events.
217    #[serde_as(deserialize_as = "DefaultOnError")]
218    #[schemars(extend("x-deserialize-default-on-error" = true))]
219    #[serde(default)]
220    pub did_close: Option<NesDocumentDidCloseCapabilities>,
221    /// Whether the agent wants `document/didSave` events.
222    #[serde_as(deserialize_as = "DefaultOnError")]
223    #[schemars(extend("x-deserialize-default-on-error" = true))]
224    #[serde(default)]
225    pub did_save: Option<NesDocumentDidSaveCapabilities>,
226    /// Whether the agent wants `document/didFocus` events.
227    #[serde_as(deserialize_as = "DefaultOnError")]
228    #[schemars(extend("x-deserialize-default-on-error" = true))]
229    #[serde(default)]
230    pub did_focus: Option<NesDocumentDidFocusCapabilities>,
231    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
232    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
233    /// these keys.
234    ///
235    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
236    #[serde(rename = "_meta")]
237    pub meta: Option<Meta>,
238}
239
240impl NesDocumentEventCapabilities {
241    #[must_use]
242    pub fn new() -> Self {
243        Self::default()
244    }
245
246    #[must_use]
247    pub fn did_open(mut self, did_open: impl IntoOption<NesDocumentDidOpenCapabilities>) -> Self {
248        self.did_open = did_open.into_option();
249        self
250    }
251
252    #[must_use]
253    pub fn did_change(
254        mut self,
255        did_change: impl IntoOption<NesDocumentDidChangeCapabilities>,
256    ) -> Self {
257        self.did_change = did_change.into_option();
258        self
259    }
260
261    #[must_use]
262    pub fn did_close(
263        mut self,
264        did_close: impl IntoOption<NesDocumentDidCloseCapabilities>,
265    ) -> Self {
266        self.did_close = did_close.into_option();
267        self
268    }
269
270    #[must_use]
271    pub fn did_save(mut self, did_save: impl IntoOption<NesDocumentDidSaveCapabilities>) -> Self {
272        self.did_save = did_save.into_option();
273        self
274    }
275
276    #[must_use]
277    pub fn did_focus(
278        mut self,
279        did_focus: impl IntoOption<NesDocumentDidFocusCapabilities>,
280    ) -> Self {
281        self.did_focus = did_focus.into_option();
282        self
283    }
284
285    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
286    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
287    /// these keys.
288    ///
289    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
290    #[must_use]
291    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
292        self.meta = meta.into_option();
293        self
294    }
295}
296
297/// Marker for `document/didOpen` capability support.
298#[skip_serializing_none]
299#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
300#[serde(rename_all = "camelCase")]
301#[non_exhaustive]
302pub struct NesDocumentDidOpenCapabilities {
303    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
304    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
305    /// these keys.
306    ///
307    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
308    #[serde(rename = "_meta")]
309    pub meta: Option<Meta>,
310}
311
312impl NesDocumentDidOpenCapabilities {
313    #[must_use]
314    pub fn new() -> Self {
315        Self::default()
316    }
317}
318
319/// Capabilities for `document/didChange` events.
320#[skip_serializing_none]
321#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
322#[serde(rename_all = "camelCase")]
323#[non_exhaustive]
324pub struct NesDocumentDidChangeCapabilities {
325    /// The sync kind the agent wants: `"full"` or `"incremental"`.
326    pub sync_kind: TextDocumentSyncKind,
327    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
328    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
329    /// these keys.
330    ///
331    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
332    #[serde(rename = "_meta")]
333    pub meta: Option<Meta>,
334}
335
336impl NesDocumentDidChangeCapabilities {
337    #[must_use]
338    pub fn new(sync_kind: TextDocumentSyncKind) -> Self {
339        Self {
340            sync_kind,
341            meta: None,
342        }
343    }
344
345    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
346    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
347    /// these keys.
348    ///
349    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
350    #[must_use]
351    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
352        self.meta = meta.into_option();
353        self
354    }
355}
356
357/// How the agent wants document changes delivered.
358#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
359#[non_exhaustive]
360pub enum TextDocumentSyncKind {
361    /// Client sends the entire file content on each change.
362    #[serde(rename = "full")]
363    Full,
364    /// Client sends only the changed ranges.
365    #[serde(rename = "incremental")]
366    Incremental,
367}
368
369/// Marker for `document/didClose` capability support.
370#[skip_serializing_none]
371#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
372#[serde(rename_all = "camelCase")]
373#[non_exhaustive]
374pub struct NesDocumentDidCloseCapabilities {
375    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
376    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
377    /// these keys.
378    ///
379    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
380    #[serde(rename = "_meta")]
381    pub meta: Option<Meta>,
382}
383
384impl NesDocumentDidCloseCapabilities {
385    #[must_use]
386    pub fn new() -> Self {
387        Self::default()
388    }
389}
390
391/// Marker for `document/didSave` capability support.
392#[skip_serializing_none]
393#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
394#[serde(rename_all = "camelCase")]
395#[non_exhaustive]
396pub struct NesDocumentDidSaveCapabilities {
397    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
398    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
399    /// these keys.
400    ///
401    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
402    #[serde(rename = "_meta")]
403    pub meta: Option<Meta>,
404}
405
406impl NesDocumentDidSaveCapabilities {
407    #[must_use]
408    pub fn new() -> Self {
409        Self::default()
410    }
411}
412
413/// Marker for `document/didFocus` capability support.
414#[skip_serializing_none]
415#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
416#[serde(rename_all = "camelCase")]
417#[non_exhaustive]
418pub struct NesDocumentDidFocusCapabilities {
419    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
420    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
421    /// these keys.
422    ///
423    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
424    #[serde(rename = "_meta")]
425    pub meta: Option<Meta>,
426}
427
428impl NesDocumentDidFocusCapabilities {
429    #[must_use]
430    pub fn new() -> Self {
431        Self::default()
432    }
433}
434
435/// Context capabilities the agent wants attached to each suggestion request.
436#[serde_as]
437#[skip_serializing_none]
438#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
439#[serde(rename_all = "camelCase")]
440#[non_exhaustive]
441pub struct NesContextCapabilities {
442    /// Whether the agent wants recent files context.
443    #[serde_as(deserialize_as = "DefaultOnError")]
444    #[schemars(extend("x-deserialize-default-on-error" = true))]
445    #[serde(default)]
446    pub recent_files: Option<NesRecentFilesCapabilities>,
447    /// Whether the agent wants related snippets context.
448    #[serde_as(deserialize_as = "DefaultOnError")]
449    #[schemars(extend("x-deserialize-default-on-error" = true))]
450    #[serde(default)]
451    pub related_snippets: Option<NesRelatedSnippetsCapabilities>,
452    /// Whether the agent wants edit history context.
453    #[serde_as(deserialize_as = "DefaultOnError")]
454    #[schemars(extend("x-deserialize-default-on-error" = true))]
455    #[serde(default)]
456    pub edit_history: Option<NesEditHistoryCapabilities>,
457    /// Whether the agent wants user actions context.
458    #[serde_as(deserialize_as = "DefaultOnError")]
459    #[schemars(extend("x-deserialize-default-on-error" = true))]
460    #[serde(default)]
461    pub user_actions: Option<NesUserActionsCapabilities>,
462    /// Whether the agent wants open files context.
463    #[serde_as(deserialize_as = "DefaultOnError")]
464    #[schemars(extend("x-deserialize-default-on-error" = true))]
465    #[serde(default)]
466    pub open_files: Option<NesOpenFilesCapabilities>,
467    /// Whether the agent wants diagnostics context.
468    #[serde_as(deserialize_as = "DefaultOnError")]
469    #[schemars(extend("x-deserialize-default-on-error" = true))]
470    #[serde(default)]
471    pub diagnostics: Option<NesDiagnosticsCapabilities>,
472    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
473    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
474    /// these keys.
475    ///
476    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
477    #[serde(rename = "_meta")]
478    pub meta: Option<Meta>,
479}
480
481impl NesContextCapabilities {
482    #[must_use]
483    pub fn new() -> Self {
484        Self::default()
485    }
486
487    #[must_use]
488    pub fn recent_files(
489        mut self,
490        recent_files: impl IntoOption<NesRecentFilesCapabilities>,
491    ) -> Self {
492        self.recent_files = recent_files.into_option();
493        self
494    }
495
496    #[must_use]
497    pub fn related_snippets(
498        mut self,
499        related_snippets: impl IntoOption<NesRelatedSnippetsCapabilities>,
500    ) -> Self {
501        self.related_snippets = related_snippets.into_option();
502        self
503    }
504
505    #[must_use]
506    pub fn edit_history(
507        mut self,
508        edit_history: impl IntoOption<NesEditHistoryCapabilities>,
509    ) -> Self {
510        self.edit_history = edit_history.into_option();
511        self
512    }
513
514    #[must_use]
515    pub fn user_actions(
516        mut self,
517        user_actions: impl IntoOption<NesUserActionsCapabilities>,
518    ) -> Self {
519        self.user_actions = user_actions.into_option();
520        self
521    }
522
523    #[must_use]
524    pub fn open_files(mut self, open_files: impl IntoOption<NesOpenFilesCapabilities>) -> Self {
525        self.open_files = open_files.into_option();
526        self
527    }
528
529    #[must_use]
530    pub fn diagnostics(mut self, diagnostics: impl IntoOption<NesDiagnosticsCapabilities>) -> Self {
531        self.diagnostics = diagnostics.into_option();
532        self
533    }
534
535    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
536    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
537    /// these keys.
538    ///
539    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
540    #[must_use]
541    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
542        self.meta = meta.into_option();
543        self
544    }
545}
546
547/// Capabilities for recent files context.
548#[skip_serializing_none]
549#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
550#[serde(rename_all = "camelCase")]
551#[non_exhaustive]
552pub struct NesRecentFilesCapabilities {
553    /// Maximum number of recent files the agent can use.
554    pub max_count: Option<u32>,
555    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
556    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
557    /// these keys.
558    ///
559    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
560    #[serde(rename = "_meta")]
561    pub meta: Option<Meta>,
562}
563
564impl NesRecentFilesCapabilities {
565    #[must_use]
566    pub fn new() -> Self {
567        Self::default()
568    }
569}
570
571/// Capabilities for related snippets context.
572#[skip_serializing_none]
573#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
574#[serde(rename_all = "camelCase")]
575#[non_exhaustive]
576pub struct NesRelatedSnippetsCapabilities {
577    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
578    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
579    /// these keys.
580    ///
581    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
582    #[serde(rename = "_meta")]
583    pub meta: Option<Meta>,
584}
585
586impl NesRelatedSnippetsCapabilities {
587    #[must_use]
588    pub fn new() -> Self {
589        Self::default()
590    }
591}
592
593/// Capabilities for edit history context.
594#[skip_serializing_none]
595#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
596#[serde(rename_all = "camelCase")]
597#[non_exhaustive]
598pub struct NesEditHistoryCapabilities {
599    /// Maximum number of edit history entries the agent can use.
600    pub max_count: Option<u32>,
601    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
602    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
603    /// these keys.
604    ///
605    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
606    #[serde(rename = "_meta")]
607    pub meta: Option<Meta>,
608}
609
610impl NesEditHistoryCapabilities {
611    #[must_use]
612    pub fn new() -> Self {
613        Self::default()
614    }
615}
616
617/// Capabilities for user actions context.
618#[skip_serializing_none]
619#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
620#[serde(rename_all = "camelCase")]
621#[non_exhaustive]
622pub struct NesUserActionsCapabilities {
623    /// Maximum number of user actions the agent can use.
624    pub max_count: Option<u32>,
625    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
626    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
627    /// these keys.
628    ///
629    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
630    #[serde(rename = "_meta")]
631    pub meta: Option<Meta>,
632}
633
634impl NesUserActionsCapabilities {
635    #[must_use]
636    pub fn new() -> Self {
637        Self::default()
638    }
639}
640
641/// Capabilities for open files context.
642#[skip_serializing_none]
643#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
644#[serde(rename_all = "camelCase")]
645#[non_exhaustive]
646pub struct NesOpenFilesCapabilities {
647    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
648    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
649    /// these keys.
650    ///
651    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
652    #[serde(rename = "_meta")]
653    pub meta: Option<Meta>,
654}
655
656impl NesOpenFilesCapabilities {
657    #[must_use]
658    pub fn new() -> Self {
659        Self::default()
660    }
661}
662
663/// Capabilities for diagnostics context.
664#[skip_serializing_none]
665#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
666#[serde(rename_all = "camelCase")]
667#[non_exhaustive]
668pub struct NesDiagnosticsCapabilities {
669    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
670    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
671    /// these keys.
672    ///
673    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
674    #[serde(rename = "_meta")]
675    pub meta: Option<Meta>,
676}
677
678impl NesDiagnosticsCapabilities {
679    #[must_use]
680    pub fn new() -> Self {
681        Self::default()
682    }
683}
684
685// Client NES capabilities
686
687/// NES capabilities advertised by the client during initialization.
688#[serde_as]
689#[skip_serializing_none]
690#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
691#[serde(rename_all = "camelCase")]
692#[non_exhaustive]
693pub struct ClientNesCapabilities {
694    /// Whether the client supports the `jump` suggestion kind.
695    #[serde_as(deserialize_as = "DefaultOnError")]
696    #[schemars(extend("x-deserialize-default-on-error" = true))]
697    #[serde(default)]
698    pub jump: Option<NesJumpCapabilities>,
699    /// Whether the client supports the `rename` suggestion kind.
700    #[serde_as(deserialize_as = "DefaultOnError")]
701    #[schemars(extend("x-deserialize-default-on-error" = true))]
702    #[serde(default)]
703    pub rename: Option<NesRenameCapabilities>,
704    /// Whether the client supports the `searchAndReplace` suggestion kind.
705    #[serde_as(deserialize_as = "DefaultOnError")]
706    #[schemars(extend("x-deserialize-default-on-error" = true))]
707    #[serde(default)]
708    pub search_and_replace: Option<NesSearchAndReplaceCapabilities>,
709    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
710    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
711    /// these keys.
712    ///
713    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
714    #[serde(rename = "_meta")]
715    pub meta: Option<Meta>,
716}
717
718impl ClientNesCapabilities {
719    #[must_use]
720    pub fn new() -> Self {
721        Self::default()
722    }
723
724    #[must_use]
725    pub fn jump(mut self, jump: impl IntoOption<NesJumpCapabilities>) -> Self {
726        self.jump = jump.into_option();
727        self
728    }
729
730    #[must_use]
731    pub fn rename(mut self, rename: impl IntoOption<NesRenameCapabilities>) -> Self {
732        self.rename = rename.into_option();
733        self
734    }
735
736    #[must_use]
737    pub fn search_and_replace(
738        mut self,
739        search_and_replace: impl IntoOption<NesSearchAndReplaceCapabilities>,
740    ) -> Self {
741        self.search_and_replace = search_and_replace.into_option();
742        self
743    }
744
745    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
746    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
747    /// these keys.
748    ///
749    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
750    #[must_use]
751    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
752        self.meta = meta.into_option();
753        self
754    }
755}
756
757/// Marker for jump suggestion support.
758#[skip_serializing_none]
759#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
760#[serde(rename_all = "camelCase")]
761#[non_exhaustive]
762pub struct NesJumpCapabilities {
763    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
764    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
765    /// these keys.
766    ///
767    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
768    #[serde(rename = "_meta")]
769    pub meta: Option<Meta>,
770}
771
772impl NesJumpCapabilities {
773    #[must_use]
774    pub fn new() -> Self {
775        Self::default()
776    }
777}
778
779/// Marker for rename suggestion support.
780#[skip_serializing_none]
781#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
782#[serde(rename_all = "camelCase")]
783#[non_exhaustive]
784pub struct NesRenameCapabilities {
785    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
786    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
787    /// these keys.
788    ///
789    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
790    #[serde(rename = "_meta")]
791    pub meta: Option<Meta>,
792}
793
794impl NesRenameCapabilities {
795    #[must_use]
796    pub fn new() -> Self {
797        Self::default()
798    }
799}
800
801/// Marker for search and replace suggestion support.
802#[skip_serializing_none]
803#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
804#[serde(rename_all = "camelCase")]
805#[non_exhaustive]
806pub struct NesSearchAndReplaceCapabilities {
807    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
808    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
809    /// these keys.
810    ///
811    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
812    #[serde(rename = "_meta")]
813    pub meta: Option<Meta>,
814}
815
816impl NesSearchAndReplaceCapabilities {
817    #[must_use]
818    pub fn new() -> Self {
819        Self::default()
820    }
821}
822
823// Document event notifications (client -> agent)
824
825/// Notification sent when a file is opened in the editor.
826#[skip_serializing_none]
827#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
828#[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_OPEN_METHOD_NAME))]
829#[serde(rename_all = "camelCase")]
830#[non_exhaustive]
831pub struct DidOpenDocumentNotification {
832    /// The session ID for this notification.
833    pub session_id: SessionId,
834    /// The URI of the opened document.
835    pub uri: String,
836    /// The language identifier of the document (e.g., "rust", "python").
837    pub language_id: String,
838    /// The version number of the document.
839    pub version: i64,
840    /// The full text content of the document.
841    pub text: String,
842    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
843    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
844    /// these keys.
845    ///
846    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
847    #[serde(rename = "_meta")]
848    pub meta: Option<Meta>,
849}
850
851impl DidOpenDocumentNotification {
852    #[must_use]
853    pub fn new(
854        session_id: impl Into<SessionId>,
855        uri: impl Into<String>,
856        language_id: impl Into<String>,
857        version: i64,
858        text: impl Into<String>,
859    ) -> Self {
860        Self {
861            session_id: session_id.into(),
862            uri: uri.into(),
863            language_id: language_id.into(),
864            version,
865            text: text.into(),
866            meta: None,
867        }
868    }
869
870    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
871    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
872    /// these keys.
873    ///
874    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
875    #[must_use]
876    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
877        self.meta = meta.into_option();
878        self
879    }
880}
881
882/// Notification sent when a file is edited.
883#[skip_serializing_none]
884#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
885#[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_CHANGE_METHOD_NAME))]
886#[serde(rename_all = "camelCase")]
887#[non_exhaustive]
888pub struct DidChangeDocumentNotification {
889    /// The session ID for this notification.
890    pub session_id: SessionId,
891    /// The URI of the changed document.
892    pub uri: String,
893    /// The new version number of the document.
894    pub version: i64,
895    /// The content changes.
896    pub content_changes: Vec<TextDocumentContentChangeEvent>,
897    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
898    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
899    /// these keys.
900    ///
901    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
902    #[serde(rename = "_meta")]
903    pub meta: Option<Meta>,
904}
905
906impl DidChangeDocumentNotification {
907    #[must_use]
908    pub fn new(
909        session_id: impl Into<SessionId>,
910        uri: impl Into<String>,
911        version: i64,
912        content_changes: Vec<TextDocumentContentChangeEvent>,
913    ) -> Self {
914        Self {
915            session_id: session_id.into(),
916            uri: uri.into(),
917            version,
918            content_changes,
919            meta: None,
920        }
921    }
922
923    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
924    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
925    /// these keys.
926    ///
927    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
928    #[must_use]
929    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
930        self.meta = meta.into_option();
931        self
932    }
933}
934
935/// A content change event for a document.
936///
937/// When `range` is `None`, `text` is the full content of the document.
938/// When `range` is `Some`, `text` replaces the given range.
939#[skip_serializing_none]
940#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
941#[serde(rename_all = "camelCase")]
942#[non_exhaustive]
943pub struct TextDocumentContentChangeEvent {
944    /// The range of the document that changed. If `None`, the entire content is replaced.
945    pub range: Option<Range>,
946    /// The new text for the range, or the full document content if `range` is `None`.
947    pub text: String,
948}
949
950impl TextDocumentContentChangeEvent {
951    #[must_use]
952    pub fn full(text: impl Into<String>) -> Self {
953        Self {
954            range: None,
955            text: text.into(),
956        }
957    }
958
959    #[must_use]
960    pub fn incremental(range: Range, text: impl Into<String>) -> Self {
961        Self {
962            range: Some(range),
963            text: text.into(),
964        }
965    }
966}
967
968/// Notification sent when a file is closed.
969#[skip_serializing_none]
970#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
971#[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_CLOSE_METHOD_NAME))]
972#[serde(rename_all = "camelCase")]
973#[non_exhaustive]
974pub struct DidCloseDocumentNotification {
975    /// The session ID for this notification.
976    pub session_id: SessionId,
977    /// The URI of the closed document.
978    pub uri: String,
979    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
980    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
981    /// these keys.
982    ///
983    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
984    #[serde(rename = "_meta")]
985    pub meta: Option<Meta>,
986}
987
988impl DidCloseDocumentNotification {
989    #[must_use]
990    pub fn new(session_id: impl Into<SessionId>, uri: impl Into<String>) -> Self {
991        Self {
992            session_id: session_id.into(),
993            uri: uri.into(),
994            meta: None,
995        }
996    }
997
998    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
999    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1000    /// these keys.
1001    ///
1002    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1003    #[must_use]
1004    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1005        self.meta = meta.into_option();
1006        self
1007    }
1008}
1009
1010/// Notification sent when a file is saved.
1011#[skip_serializing_none]
1012#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1013#[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_SAVE_METHOD_NAME))]
1014#[serde(rename_all = "camelCase")]
1015#[non_exhaustive]
1016pub struct DidSaveDocumentNotification {
1017    /// The session ID for this notification.
1018    pub session_id: SessionId,
1019    /// The URI of the saved document.
1020    pub uri: String,
1021    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1022    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1023    /// these keys.
1024    ///
1025    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1026    #[serde(rename = "_meta")]
1027    pub meta: Option<Meta>,
1028}
1029
1030impl DidSaveDocumentNotification {
1031    #[must_use]
1032    pub fn new(session_id: impl Into<SessionId>, uri: impl Into<String>) -> Self {
1033        Self {
1034            session_id: session_id.into(),
1035            uri: uri.into(),
1036            meta: None,
1037        }
1038    }
1039
1040    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1041    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1042    /// these keys.
1043    ///
1044    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1045    #[must_use]
1046    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1047        self.meta = meta.into_option();
1048        self
1049    }
1050}
1051
1052/// Notification sent when a file becomes the active editor tab.
1053#[skip_serializing_none]
1054#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1055#[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_FOCUS_METHOD_NAME))]
1056#[serde(rename_all = "camelCase")]
1057#[non_exhaustive]
1058pub struct DidFocusDocumentNotification {
1059    /// The session ID for this notification.
1060    pub session_id: SessionId,
1061    /// The URI of the focused document.
1062    pub uri: String,
1063    /// The version number of the document.
1064    pub version: i64,
1065    /// The current cursor position.
1066    pub position: Position,
1067    /// The portion of the file currently visible in the editor viewport.
1068    pub visible_range: Range,
1069    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1070    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1071    /// these keys.
1072    ///
1073    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1074    #[serde(rename = "_meta")]
1075    pub meta: Option<Meta>,
1076}
1077
1078impl DidFocusDocumentNotification {
1079    #[must_use]
1080    pub fn new(
1081        session_id: impl Into<SessionId>,
1082        uri: impl Into<String>,
1083        version: i64,
1084        position: Position,
1085        visible_range: Range,
1086    ) -> Self {
1087        Self {
1088            session_id: session_id.into(),
1089            uri: uri.into(),
1090            version,
1091            position,
1092            visible_range,
1093            meta: None,
1094        }
1095    }
1096
1097    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1098    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1099    /// these keys.
1100    ///
1101    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1102    #[must_use]
1103    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1104        self.meta = meta.into_option();
1105        self
1106    }
1107}
1108
1109// NES session start
1110
1111/// Request to start an NES session.
1112#[serde_as]
1113#[skip_serializing_none]
1114#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1115#[schemars(extend("x-side" = "agent", "x-method" = NES_START_METHOD_NAME))]
1116#[serde(rename_all = "camelCase")]
1117#[non_exhaustive]
1118pub struct StartNesRequest {
1119    /// The root URI of the workspace.
1120    pub workspace_uri: Option<String>,
1121    /// The workspace folders.
1122    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1123    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1124    #[serde(default)]
1125    pub workspace_folders: Option<Vec<WorkspaceFolder>>,
1126    /// Repository metadata, if the workspace is a git repository.
1127    #[serde_as(deserialize_as = "DefaultOnError")]
1128    #[schemars(extend("x-deserialize-default-on-error" = true))]
1129    #[serde(default)]
1130    pub repository: Option<NesRepository>,
1131    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1132    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1133    /// these keys.
1134    ///
1135    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1136    #[serde(rename = "_meta")]
1137    pub meta: Option<Meta>,
1138}
1139
1140impl StartNesRequest {
1141    #[must_use]
1142    pub fn new() -> Self {
1143        Self {
1144            workspace_uri: None,
1145            workspace_folders: None,
1146            repository: None,
1147            meta: None,
1148        }
1149    }
1150
1151    #[must_use]
1152    pub fn workspace_uri(mut self, workspace_uri: impl IntoOption<String>) -> Self {
1153        self.workspace_uri = workspace_uri.into_option();
1154        self
1155    }
1156
1157    #[must_use]
1158    pub fn workspace_folders(
1159        mut self,
1160        workspace_folders: impl IntoOption<Vec<WorkspaceFolder>>,
1161    ) -> Self {
1162        self.workspace_folders = workspace_folders.into_option();
1163        self
1164    }
1165
1166    #[must_use]
1167    pub fn repository(mut self, repository: impl IntoOption<NesRepository>) -> Self {
1168        self.repository = repository.into_option();
1169        self
1170    }
1171
1172    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1173    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1174    /// these keys.
1175    ///
1176    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1177    #[must_use]
1178    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1179        self.meta = meta.into_option();
1180        self
1181    }
1182}
1183
1184impl Default for StartNesRequest {
1185    fn default() -> Self {
1186        Self::new()
1187    }
1188}
1189
1190/// A workspace folder.
1191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1192#[serde(rename_all = "camelCase")]
1193#[non_exhaustive]
1194pub struct WorkspaceFolder {
1195    /// The URI of the folder.
1196    pub uri: String,
1197    /// The display name of the folder.
1198    pub name: String,
1199}
1200
1201impl WorkspaceFolder {
1202    #[must_use]
1203    pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
1204        Self {
1205            uri: uri.into(),
1206            name: name.into(),
1207        }
1208    }
1209}
1210
1211/// Repository metadata for an NES session.
1212#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1213#[serde(rename_all = "camelCase")]
1214#[non_exhaustive]
1215pub struct NesRepository {
1216    /// The repository name.
1217    pub name: String,
1218    /// The repository owner.
1219    pub owner: String,
1220    /// The remote URL of the repository.
1221    pub remote_url: String,
1222}
1223
1224impl NesRepository {
1225    #[must_use]
1226    pub fn new(
1227        name: impl Into<String>,
1228        owner: impl Into<String>,
1229        remote_url: impl Into<String>,
1230    ) -> Self {
1231        Self {
1232            name: name.into(),
1233            owner: owner.into(),
1234            remote_url: remote_url.into(),
1235        }
1236    }
1237}
1238
1239/// Response to `nes/start`.
1240#[skip_serializing_none]
1241#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1242#[schemars(extend("x-side" = "agent", "x-method" = NES_START_METHOD_NAME))]
1243#[serde(rename_all = "camelCase")]
1244#[non_exhaustive]
1245pub struct StartNesResponse {
1246    /// The session ID for the newly started NES session.
1247    pub session_id: SessionId,
1248    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1249    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1250    /// these keys.
1251    ///
1252    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1253    #[serde(rename = "_meta")]
1254    pub meta: Option<Meta>,
1255}
1256
1257impl StartNesResponse {
1258    #[must_use]
1259    pub fn new(session_id: impl Into<SessionId>) -> Self {
1260        Self {
1261            session_id: session_id.into(),
1262            meta: None,
1263        }
1264    }
1265
1266    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1267    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1268    /// these keys.
1269    ///
1270    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1271    #[must_use]
1272    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1273        self.meta = meta.into_option();
1274        self
1275    }
1276}
1277
1278// NES session close
1279
1280/// Request to close an NES session.
1281///
1282/// The agent **must** cancel any ongoing work related to the NES session
1283/// and then free up any resources associated with the session.
1284#[skip_serializing_none]
1285#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1286#[schemars(extend("x-side" = "agent", "x-method" = NES_CLOSE_METHOD_NAME))]
1287#[serde(rename_all = "camelCase")]
1288#[non_exhaustive]
1289pub struct CloseNesRequest {
1290    /// The ID of the NES session to close.
1291    pub session_id: SessionId,
1292    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1293    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1294    /// these keys.
1295    ///
1296    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1297    #[serde(rename = "_meta")]
1298    pub meta: Option<Meta>,
1299}
1300
1301impl CloseNesRequest {
1302    #[must_use]
1303    pub fn new(session_id: impl Into<SessionId>) -> Self {
1304        Self {
1305            session_id: session_id.into(),
1306            meta: None,
1307        }
1308    }
1309
1310    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1311    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1312    /// these keys.
1313    ///
1314    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1315    #[must_use]
1316    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1317        self.meta = meta.into_option();
1318        self
1319    }
1320}
1321
1322/// Response from closing an NES session.
1323#[skip_serializing_none]
1324#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1325#[schemars(extend("x-side" = "agent", "x-method" = NES_CLOSE_METHOD_NAME))]
1326#[serde(rename_all = "camelCase")]
1327#[non_exhaustive]
1328pub struct CloseNesResponse {
1329    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1330    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1331    /// these keys.
1332    ///
1333    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1334    #[serde(rename = "_meta")]
1335    pub meta: Option<Meta>,
1336}
1337
1338impl CloseNesResponse {
1339    #[must_use]
1340    pub fn new() -> Self {
1341        Self::default()
1342    }
1343
1344    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1345    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1346    /// these keys.
1347    ///
1348    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1349    #[must_use]
1350    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1351        self.meta = meta.into_option();
1352        self
1353    }
1354}
1355
1356// NES suggest request
1357
1358/// What triggered the suggestion request.
1359#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1360#[non_exhaustive]
1361pub enum NesTriggerKind {
1362    /// Triggered by user typing or cursor movement.
1363    #[serde(rename = "automatic")]
1364    Automatic,
1365    /// Triggered by a diagnostic appearing at or near the cursor.
1366    #[serde(rename = "diagnostic")]
1367    Diagnostic,
1368    /// Triggered by an explicit user action (keyboard shortcut).
1369    #[serde(rename = "manual")]
1370    Manual,
1371    /// Custom or future suggestion trigger kind.
1372    ///
1373    /// Values beginning with `_` are reserved for implementation-specific
1374    /// extensions. Unknown values that do not begin with `_` are reserved for
1375    /// future ACP variants.
1376    #[serde(untagged)]
1377    Other(String),
1378}
1379
1380/// Request for a code suggestion.
1381#[serde_as]
1382#[skip_serializing_none]
1383#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1384#[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))]
1385#[serde(rename_all = "camelCase")]
1386#[non_exhaustive]
1387pub struct SuggestNesRequest {
1388    /// The session ID for this request.
1389    pub session_id: SessionId,
1390    /// The URI of the document to suggest for.
1391    pub uri: String,
1392    /// The version number of the document.
1393    pub version: i64,
1394    /// The current cursor position.
1395    pub position: Position,
1396    /// The current text selection range, if any.
1397    #[serde_as(deserialize_as = "DefaultOnError")]
1398    #[schemars(extend("x-deserialize-default-on-error" = true))]
1399    #[serde(default)]
1400    pub selection: Option<Range>,
1401    /// What triggered this suggestion request.
1402    pub trigger_kind: NesTriggerKind,
1403    /// Context for the suggestion, included based on agent capabilities.
1404    #[serde_as(deserialize_as = "DefaultOnError")]
1405    #[schemars(extend("x-deserialize-default-on-error" = true))]
1406    #[serde(default)]
1407    pub context: Option<NesSuggestContext>,
1408    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1409    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1410    /// these keys.
1411    ///
1412    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1413    #[serde(rename = "_meta")]
1414    pub meta: Option<Meta>,
1415}
1416
1417impl SuggestNesRequest {
1418    #[must_use]
1419    pub fn new(
1420        session_id: impl Into<SessionId>,
1421        uri: impl Into<String>,
1422        version: i64,
1423        position: Position,
1424        trigger_kind: NesTriggerKind,
1425    ) -> Self {
1426        Self {
1427            session_id: session_id.into(),
1428            uri: uri.into(),
1429            version,
1430            position,
1431            selection: None,
1432            trigger_kind,
1433            context: None,
1434            meta: None,
1435        }
1436    }
1437
1438    #[must_use]
1439    pub fn selection(mut self, selection: impl IntoOption<Range>) -> Self {
1440        self.selection = selection.into_option();
1441        self
1442    }
1443
1444    #[must_use]
1445    pub fn context(mut self, context: impl IntoOption<NesSuggestContext>) -> Self {
1446        self.context = context.into_option();
1447        self
1448    }
1449
1450    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1451    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1452    /// these keys.
1453    ///
1454    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1455    #[must_use]
1456    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1457        self.meta = meta.into_option();
1458        self
1459    }
1460}
1461
1462/// Context attached to a suggestion request.
1463#[serde_as]
1464#[skip_serializing_none]
1465#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1466#[serde(rename_all = "camelCase")]
1467#[non_exhaustive]
1468pub struct NesSuggestContext {
1469    /// Recently accessed files.
1470    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1471    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1472    #[serde(default)]
1473    pub recent_files: Option<Vec<NesRecentFile>>,
1474    /// Related code snippets.
1475    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1476    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1477    #[serde(default)]
1478    pub related_snippets: Option<Vec<NesRelatedSnippet>>,
1479    /// Recent edit history.
1480    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1481    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1482    #[serde(default)]
1483    pub edit_history: Option<Vec<NesEditHistoryEntry>>,
1484    /// Recent user actions (typing, navigation, etc.).
1485    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1486    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1487    #[serde(default)]
1488    pub user_actions: Option<Vec<NesUserAction>>,
1489    /// Currently open files in the editor.
1490    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1491    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1492    #[serde(default)]
1493    pub open_files: Option<Vec<NesOpenFile>>,
1494    /// Current diagnostics (errors, warnings).
1495    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
1496    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1497    #[serde(default)]
1498    pub diagnostics: Option<Vec<NesDiagnostic>>,
1499    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1500    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1501    /// these keys.
1502    ///
1503    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1504    #[serde(rename = "_meta")]
1505    pub meta: Option<Meta>,
1506}
1507
1508impl NesSuggestContext {
1509    #[must_use]
1510    pub fn new() -> Self {
1511        Self::default()
1512    }
1513
1514    #[must_use]
1515    pub fn recent_files(mut self, recent_files: impl IntoOption<Vec<NesRecentFile>>) -> Self {
1516        self.recent_files = recent_files.into_option();
1517        self
1518    }
1519
1520    #[must_use]
1521    pub fn related_snippets(
1522        mut self,
1523        related_snippets: impl IntoOption<Vec<NesRelatedSnippet>>,
1524    ) -> Self {
1525        self.related_snippets = related_snippets.into_option();
1526        self
1527    }
1528
1529    #[must_use]
1530    pub fn edit_history(mut self, edit_history: impl IntoOption<Vec<NesEditHistoryEntry>>) -> Self {
1531        self.edit_history = edit_history.into_option();
1532        self
1533    }
1534
1535    #[must_use]
1536    pub fn user_actions(mut self, user_actions: impl IntoOption<Vec<NesUserAction>>) -> Self {
1537        self.user_actions = user_actions.into_option();
1538        self
1539    }
1540
1541    #[must_use]
1542    pub fn open_files(mut self, open_files: impl IntoOption<Vec<NesOpenFile>>) -> Self {
1543        self.open_files = open_files.into_option();
1544        self
1545    }
1546
1547    #[must_use]
1548    pub fn diagnostics(mut self, diagnostics: impl IntoOption<Vec<NesDiagnostic>>) -> Self {
1549        self.diagnostics = diagnostics.into_option();
1550        self
1551    }
1552
1553    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1554    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1555    /// these keys.
1556    ///
1557    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1558    #[must_use]
1559    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1560        self.meta = meta.into_option();
1561        self
1562    }
1563}
1564
1565/// A recently accessed file.
1566#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1567#[serde(rename_all = "camelCase")]
1568#[non_exhaustive]
1569pub struct NesRecentFile {
1570    /// The URI of the file.
1571    pub uri: String,
1572    /// The language identifier.
1573    pub language_id: String,
1574    /// The full text content of the file.
1575    pub text: String,
1576}
1577
1578impl NesRecentFile {
1579    #[must_use]
1580    pub fn new(
1581        uri: impl Into<String>,
1582        language_id: impl Into<String>,
1583        text: impl Into<String>,
1584    ) -> Self {
1585        Self {
1586            uri: uri.into(),
1587            language_id: language_id.into(),
1588            text: text.into(),
1589        }
1590    }
1591}
1592
1593/// A related code snippet from a file.
1594#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1595#[serde(rename_all = "camelCase")]
1596#[non_exhaustive]
1597pub struct NesRelatedSnippet {
1598    /// The URI of the file containing the snippets.
1599    pub uri: String,
1600    /// The code excerpts.
1601    pub excerpts: Vec<NesExcerpt>,
1602}
1603
1604impl NesRelatedSnippet {
1605    #[must_use]
1606    pub fn new(uri: impl Into<String>, excerpts: Vec<NesExcerpt>) -> Self {
1607        Self {
1608            uri: uri.into(),
1609            excerpts,
1610        }
1611    }
1612}
1613
1614/// A code excerpt from a file.
1615#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1616#[serde(rename_all = "camelCase")]
1617#[non_exhaustive]
1618pub struct NesExcerpt {
1619    /// The start line of the excerpt (zero-based).
1620    pub start_line: u32,
1621    /// The end line of the excerpt (zero-based).
1622    pub end_line: u32,
1623    /// The text content of the excerpt.
1624    pub text: String,
1625}
1626
1627impl NesExcerpt {
1628    #[must_use]
1629    pub fn new(start_line: u32, end_line: u32, text: impl Into<String>) -> Self {
1630        Self {
1631            start_line,
1632            end_line,
1633            text: text.into(),
1634        }
1635    }
1636}
1637
1638/// An entry in the edit history.
1639#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1640#[serde(rename_all = "camelCase")]
1641#[non_exhaustive]
1642pub struct NesEditHistoryEntry {
1643    /// The URI of the edited file.
1644    pub uri: String,
1645    /// A diff representing the edit.
1646    pub diff: String,
1647}
1648
1649impl NesEditHistoryEntry {
1650    #[must_use]
1651    pub fn new(uri: impl Into<String>, diff: impl Into<String>) -> Self {
1652        Self {
1653            uri: uri.into(),
1654            diff: diff.into(),
1655        }
1656    }
1657}
1658
1659/// A user action (typing, cursor movement, etc.).
1660#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1661#[serde(rename_all = "camelCase")]
1662#[non_exhaustive]
1663pub struct NesUserAction {
1664    /// The kind of action (e.g., "insertChar", "cursorMovement").
1665    pub action: String,
1666    /// The URI of the file where the action occurred.
1667    pub uri: String,
1668    /// The position where the action occurred.
1669    pub position: Position,
1670    /// Timestamp in milliseconds since epoch.
1671    pub timestamp_ms: u64,
1672}
1673
1674impl NesUserAction {
1675    #[must_use]
1676    pub fn new(
1677        action: impl Into<String>,
1678        uri: impl Into<String>,
1679        position: Position,
1680        timestamp_ms: u64,
1681    ) -> Self {
1682        Self {
1683            action: action.into(),
1684            uri: uri.into(),
1685            position,
1686            timestamp_ms,
1687        }
1688    }
1689}
1690
1691/// An open file in the editor.
1692#[serde_as]
1693#[skip_serializing_none]
1694#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1695#[serde(rename_all = "camelCase")]
1696#[non_exhaustive]
1697pub struct NesOpenFile {
1698    /// The URI of the file.
1699    pub uri: String,
1700    /// The language identifier.
1701    pub language_id: String,
1702    /// The visible range in the editor, if any.
1703    #[serde_as(deserialize_as = "DefaultOnError")]
1704    #[schemars(extend("x-deserialize-default-on-error" = true))]
1705    #[serde(default)]
1706    pub visible_range: Option<Range>,
1707    /// Timestamp in milliseconds since epoch of when the file was last focused.
1708    #[serde_as(deserialize_as = "DefaultOnError")]
1709    #[schemars(extend("x-deserialize-default-on-error" = true))]
1710    #[serde(default)]
1711    pub last_focused_ms: Option<u64>,
1712}
1713
1714impl NesOpenFile {
1715    #[must_use]
1716    pub fn new(uri: impl Into<String>, language_id: impl Into<String>) -> Self {
1717        Self {
1718            uri: uri.into(),
1719            language_id: language_id.into(),
1720            visible_range: None,
1721            last_focused_ms: None,
1722        }
1723    }
1724
1725    #[must_use]
1726    pub fn visible_range(mut self, visible_range: impl IntoOption<Range>) -> Self {
1727        self.visible_range = visible_range.into_option();
1728        self
1729    }
1730
1731    #[must_use]
1732    pub fn last_focused_ms(mut self, last_focused_ms: impl IntoOption<u64>) -> Self {
1733        self.last_focused_ms = last_focused_ms.into_option();
1734        self
1735    }
1736}
1737
1738/// A diagnostic (error, warning, etc.).
1739#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1740#[serde(rename_all = "camelCase")]
1741#[non_exhaustive]
1742pub struct NesDiagnostic {
1743    /// The URI of the file containing the diagnostic.
1744    pub uri: String,
1745    /// The range of the diagnostic.
1746    pub range: Range,
1747    /// The severity of the diagnostic.
1748    pub severity: NesDiagnosticSeverity,
1749    /// The diagnostic message.
1750    pub message: String,
1751}
1752
1753impl NesDiagnostic {
1754    #[must_use]
1755    pub fn new(
1756        uri: impl Into<String>,
1757        range: Range,
1758        severity: NesDiagnosticSeverity,
1759        message: impl Into<String>,
1760    ) -> Self {
1761        Self {
1762            uri: uri.into(),
1763            range,
1764            severity,
1765            message: message.into(),
1766        }
1767    }
1768}
1769
1770/// Severity of a diagnostic.
1771#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1772#[non_exhaustive]
1773pub enum NesDiagnosticSeverity {
1774    /// An error.
1775    #[serde(rename = "error")]
1776    Error,
1777    /// A warning.
1778    #[serde(rename = "warning")]
1779    Warning,
1780    /// An informational message.
1781    #[serde(rename = "information")]
1782    Information,
1783    /// A hint.
1784    #[serde(rename = "hint")]
1785    Hint,
1786    /// Custom or future diagnostic severity.
1787    ///
1788    /// Values beginning with `_` are reserved for implementation-specific
1789    /// extensions. Unknown values that do not begin with `_` are reserved for
1790    /// future ACP variants.
1791    #[serde(untagged)]
1792    Other(String),
1793}
1794
1795// NES suggest response
1796
1797/// Response to `nes/suggest`.
1798#[serde_as]
1799#[skip_serializing_none]
1800#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1801#[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))]
1802#[serde(rename_all = "camelCase")]
1803#[non_exhaustive]
1804pub struct SuggestNesResponse {
1805    /// The list of suggestions.
1806    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1807    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1808    pub suggestions: Vec<NesSuggestion>,
1809    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1810    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1811    /// these keys.
1812    ///
1813    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1814    #[serde(rename = "_meta")]
1815    pub meta: Option<Meta>,
1816}
1817
1818impl SuggestNesResponse {
1819    #[must_use]
1820    pub fn new(suggestions: Vec<NesSuggestion>) -> Self {
1821        Self {
1822            suggestions,
1823            meta: None,
1824        }
1825    }
1826
1827    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1828    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1829    /// these keys.
1830    ///
1831    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1832    #[must_use]
1833    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1834        self.meta = meta.into_option();
1835        self
1836    }
1837}
1838
1839/// A suggestion returned by the agent.
1840#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1841#[serde(tag = "kind", rename_all = "camelCase")]
1842#[schemars(extend("discriminator" = {"propertyName": "kind"}))]
1843#[non_exhaustive]
1844pub enum NesSuggestion {
1845    /// A text edit suggestion.
1846    Edit(NesEditSuggestion),
1847    /// A jump-to-location suggestion.
1848    Jump(NesJumpSuggestion),
1849    /// A rename symbol suggestion.
1850    Rename(NesRenameSuggestion),
1851    /// A search-and-replace suggestion.
1852    SearchAndReplace(NesSearchAndReplaceSuggestion),
1853    /// Custom or future NES suggestion.
1854    ///
1855    /// Values beginning with `_` are reserved for implementation-specific
1856    /// extensions. Unknown values that do not begin with `_` are reserved for
1857    /// future ACP variants.
1858    ///
1859    /// Receivers that do not understand this suggestion kind should preserve
1860    /// the raw payload when storing, replaying, proxying, or forwarding
1861    /// suggestions, and otherwise ignore it or display it generically.
1862    #[serde(untagged)]
1863    Other(OtherNesSuggestion),
1864}
1865
1866/// Custom or future NES suggestion payload.
1867#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1868#[schemars(inline)]
1869#[schemars(transform = other_nes_suggestion_schema)]
1870#[serde(rename_all = "camelCase")]
1871#[non_exhaustive]
1872pub struct OtherNesSuggestion {
1873    /// Custom or future NES suggestion kind.
1874    ///
1875    /// Values beginning with `_` are reserved for implementation-specific
1876    /// extensions. Unknown values that do not begin with `_` are reserved for
1877    /// future ACP variants.
1878    pub kind: String,
1879    /// Additional fields from the unknown NES suggestion payload.
1880    #[serde(flatten)]
1881    pub fields: BTreeMap<String, serde_json::Value>,
1882}
1883
1884impl OtherNesSuggestion {
1885    #[must_use]
1886    pub fn new(kind: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1887        fields.remove("kind");
1888        Self {
1889            kind: kind.into(),
1890            fields,
1891        }
1892    }
1893}
1894
1895impl<'de> Deserialize<'de> for OtherNesSuggestion {
1896    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1897    where
1898        D: serde::Deserializer<'de>,
1899    {
1900        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1901        let kind = fields
1902            .remove("kind")
1903            .ok_or_else(|| serde::de::Error::missing_field("kind"))?;
1904        let serde_json::Value::String(kind) = kind else {
1905            return Err(serde::de::Error::custom("`kind` must be a string"));
1906        };
1907
1908        if is_known_nes_suggestion_kind(&kind) {
1909            return Err(serde::de::Error::custom(format!(
1910                "known NES suggestion `{kind}` did not match its schema"
1911            )));
1912        }
1913
1914        Ok(Self { kind, fields })
1915    }
1916}
1917
1918fn is_known_nes_suggestion_kind(kind: &str) -> bool {
1919    matches!(kind, "edit" | "jump" | "rename" | "searchAndReplace")
1920}
1921
1922fn other_nes_suggestion_schema(schema: &mut Schema) {
1923    super::schema_util::reject_known_string_discriminators(
1924        schema,
1925        "kind",
1926        &["edit", "jump", "rename", "searchAndReplace"],
1927    );
1928}
1929
1930/// A text edit suggestion.
1931#[serde_as]
1932#[skip_serializing_none]
1933#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1934#[serde(rename_all = "camelCase")]
1935#[non_exhaustive]
1936pub struct NesEditSuggestion {
1937    /// Unique identifier for accept/reject tracking.
1938    pub id: String,
1939    /// The URI of the file to edit.
1940    pub uri: String,
1941    /// The text edits to apply.
1942    pub edits: Vec<NesTextEdit>,
1943    /// Optional suggested cursor position after applying edits.
1944    #[serde_as(deserialize_as = "DefaultOnError")]
1945    #[schemars(extend("x-deserialize-default-on-error" = true))]
1946    #[serde(default)]
1947    pub cursor_position: Option<Position>,
1948}
1949
1950impl NesEditSuggestion {
1951    #[must_use]
1952    pub fn new(id: impl Into<String>, uri: impl Into<String>, edits: Vec<NesTextEdit>) -> Self {
1953        Self {
1954            id: id.into(),
1955            uri: uri.into(),
1956            edits,
1957            cursor_position: None,
1958        }
1959    }
1960
1961    #[must_use]
1962    pub fn cursor_position(mut self, cursor_position: impl IntoOption<Position>) -> Self {
1963        self.cursor_position = cursor_position.into_option();
1964        self
1965    }
1966}
1967
1968/// A text edit within a suggestion.
1969#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1970#[serde(rename_all = "camelCase")]
1971#[non_exhaustive]
1972pub struct NesTextEdit {
1973    /// The range to replace.
1974    pub range: Range,
1975    /// The replacement text.
1976    pub new_text: String,
1977}
1978
1979impl NesTextEdit {
1980    #[must_use]
1981    pub fn new(range: Range, new_text: impl Into<String>) -> Self {
1982        Self {
1983            range,
1984            new_text: new_text.into(),
1985        }
1986    }
1987}
1988
1989/// A jump-to-location suggestion.
1990#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1991#[serde(rename_all = "camelCase")]
1992#[non_exhaustive]
1993pub struct NesJumpSuggestion {
1994    /// Unique identifier for accept/reject tracking.
1995    pub id: String,
1996    /// The file to navigate to.
1997    pub uri: String,
1998    /// The target position within the file.
1999    pub position: Position,
2000}
2001
2002impl NesJumpSuggestion {
2003    #[must_use]
2004    pub fn new(id: impl Into<String>, uri: impl Into<String>, position: Position) -> Self {
2005        Self {
2006            id: id.into(),
2007            uri: uri.into(),
2008            position,
2009        }
2010    }
2011}
2012
2013/// A rename symbol suggestion.
2014#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2015#[serde(rename_all = "camelCase")]
2016#[non_exhaustive]
2017pub struct NesRenameSuggestion {
2018    /// Unique identifier for accept/reject tracking.
2019    pub id: String,
2020    /// The file URI containing the symbol.
2021    pub uri: String,
2022    /// The position of the symbol to rename.
2023    pub position: Position,
2024    /// The new name for the symbol.
2025    pub new_name: String,
2026}
2027
2028impl NesRenameSuggestion {
2029    #[must_use]
2030    pub fn new(
2031        id: impl Into<String>,
2032        uri: impl Into<String>,
2033        position: Position,
2034        new_name: impl Into<String>,
2035    ) -> Self {
2036        Self {
2037            id: id.into(),
2038            uri: uri.into(),
2039            position,
2040            new_name: new_name.into(),
2041        }
2042    }
2043}
2044
2045/// A search-and-replace suggestion.
2046#[skip_serializing_none]
2047#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2048#[serde(rename_all = "camelCase")]
2049#[non_exhaustive]
2050pub struct NesSearchAndReplaceSuggestion {
2051    /// Unique identifier for accept/reject tracking.
2052    pub id: String,
2053    /// The file URI to search within.
2054    pub uri: String,
2055    /// The text or pattern to find.
2056    pub search: String,
2057    /// The replacement text.
2058    pub replace: String,
2059    /// Whether `search` is a regular expression. Defaults to `false`.
2060    pub is_regex: Option<bool>,
2061}
2062
2063impl NesSearchAndReplaceSuggestion {
2064    #[must_use]
2065    pub fn new(
2066        id: impl Into<String>,
2067        uri: impl Into<String>,
2068        search: impl Into<String>,
2069        replace: impl Into<String>,
2070    ) -> Self {
2071        Self {
2072            id: id.into(),
2073            uri: uri.into(),
2074            search: search.into(),
2075            replace: replace.into(),
2076            is_regex: None,
2077        }
2078    }
2079
2080    #[must_use]
2081    pub fn is_regex(mut self, is_regex: impl IntoOption<bool>) -> Self {
2082        self.is_regex = is_regex.into_option();
2083        self
2084    }
2085}
2086
2087// NES accept/reject notifications
2088
2089/// Notification sent when a suggestion is accepted.
2090#[skip_serializing_none]
2091#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2092#[schemars(extend("x-side" = "agent", "x-method" = NES_ACCEPT_METHOD_NAME))]
2093#[serde(rename_all = "camelCase")]
2094#[non_exhaustive]
2095pub struct AcceptNesNotification {
2096    /// The session ID for this notification.
2097    pub session_id: SessionId,
2098    /// The ID of the accepted suggestion.
2099    pub id: String,
2100    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2101    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2102    /// these keys.
2103    ///
2104    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2105    #[serde(rename = "_meta")]
2106    pub meta: Option<Meta>,
2107}
2108
2109impl AcceptNesNotification {
2110    #[must_use]
2111    pub fn new(session_id: impl Into<SessionId>, id: impl Into<String>) -> Self {
2112        Self {
2113            session_id: session_id.into(),
2114            id: id.into(),
2115            meta: None,
2116        }
2117    }
2118
2119    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2120    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2121    /// these keys.
2122    ///
2123    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2124    #[must_use]
2125    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2126        self.meta = meta.into_option();
2127        self
2128    }
2129}
2130
2131/// Notification sent when a suggestion is rejected.
2132#[serde_as]
2133#[skip_serializing_none]
2134#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2135#[schemars(extend("x-side" = "agent", "x-method" = NES_REJECT_METHOD_NAME))]
2136#[serde(rename_all = "camelCase")]
2137#[non_exhaustive]
2138pub struct RejectNesNotification {
2139    /// The session ID for this notification.
2140    pub session_id: SessionId,
2141    /// The ID of the rejected suggestion.
2142    pub id: String,
2143    /// The reason for rejection.
2144    #[serde_as(deserialize_as = "DefaultOnError")]
2145    #[schemars(extend("x-deserialize-default-on-error" = true))]
2146    #[serde(default)]
2147    pub reason: Option<NesRejectReason>,
2148    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2149    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2150    /// these keys.
2151    ///
2152    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2153    #[serde(rename = "_meta")]
2154    pub meta: Option<Meta>,
2155}
2156
2157impl RejectNesNotification {
2158    #[must_use]
2159    pub fn new(session_id: impl Into<SessionId>, id: impl Into<String>) -> Self {
2160        Self {
2161            session_id: session_id.into(),
2162            id: id.into(),
2163            reason: None,
2164            meta: None,
2165        }
2166    }
2167
2168    #[must_use]
2169    pub fn reason(mut self, reason: impl IntoOption<NesRejectReason>) -> Self {
2170        self.reason = reason.into_option();
2171        self
2172    }
2173
2174    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2175    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2176    /// these keys.
2177    ///
2178    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2179    #[must_use]
2180    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2181        self.meta = meta.into_option();
2182        self
2183    }
2184}
2185
2186/// The reason a suggestion was rejected.
2187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2188#[non_exhaustive]
2189pub enum NesRejectReason {
2190    /// The user explicitly dismissed the suggestion.
2191    #[serde(rename = "rejected")]
2192    Rejected,
2193    /// The suggestion was shown but the user continued editing without interacting.
2194    #[serde(rename = "ignored")]
2195    Ignored,
2196    /// The suggestion was superseded by a newer suggestion.
2197    #[serde(rename = "replaced")]
2198    Replaced,
2199    /// The request was cancelled before the agent returned a response.
2200    #[serde(rename = "cancelled")]
2201    Cancelled,
2202    /// Custom or future rejection reason.
2203    ///
2204    /// Values beginning with `_` are reserved for implementation-specific
2205    /// extensions. Unknown values that do not begin with `_` are reserved for
2206    /// future ACP variants.
2207    #[serde(untagged)]
2208    Other(String),
2209}
2210
2211#[cfg(test)]
2212mod tests {
2213    use super::*;
2214    use serde_json::json;
2215
2216    #[test]
2217    fn test_position_encoding_kind_serialization() {
2218        assert_eq!(
2219            serde_json::to_value(&PositionEncodingKind::Utf16).unwrap(),
2220            json!("utf-16")
2221        );
2222        assert_eq!(
2223            serde_json::to_value(&PositionEncodingKind::Utf32).unwrap(),
2224            json!("utf-32")
2225        );
2226        assert_eq!(
2227            serde_json::to_value(&PositionEncodingKind::Utf8).unwrap(),
2228            json!("utf-8")
2229        );
2230
2231        assert_eq!(
2232            serde_json::from_value::<PositionEncodingKind>(json!("utf-16")).unwrap(),
2233            PositionEncodingKind::Utf16
2234        );
2235        assert_eq!(
2236            serde_json::from_value::<PositionEncodingKind>(json!("utf-32")).unwrap(),
2237            PositionEncodingKind::Utf32
2238        );
2239        assert_eq!(
2240            serde_json::from_value::<PositionEncodingKind>(json!("utf-8")).unwrap(),
2241            PositionEncodingKind::Utf8
2242        );
2243        assert!(serde_json::from_value::<PositionEncodingKind>(json!("_future")).is_err());
2244    }
2245
2246    #[test]
2247    fn test_client_capabilities_skip_unknown_position_encodings() {
2248        let caps: crate::v2::ClientCapabilities = serde_json::from_value(json!({
2249            "positionEncodings": ["_future", "utf-8", "utf-16"]
2250        }))
2251        .unwrap();
2252
2253        assert_eq!(
2254            caps.position_encodings,
2255            vec![PositionEncodingKind::Utf8, PositionEncodingKind::Utf16]
2256        );
2257    }
2258
2259    #[test]
2260    fn test_agent_nes_capabilities_serialization() {
2261        let caps = NesCapabilities::new()
2262            .events(
2263                NesEventCapabilities::new().document(
2264                    NesDocumentEventCapabilities::new()
2265                        .did_open(NesDocumentDidOpenCapabilities::default())
2266                        .did_change(NesDocumentDidChangeCapabilities::new(
2267                            TextDocumentSyncKind::Incremental,
2268                        ))
2269                        .did_close(NesDocumentDidCloseCapabilities::default())
2270                        .did_save(NesDocumentDidSaveCapabilities::default())
2271                        .did_focus(NesDocumentDidFocusCapabilities::default()),
2272                ),
2273            )
2274            .context(
2275                NesContextCapabilities::new()
2276                    .recent_files(NesRecentFilesCapabilities {
2277                        max_count: Some(10),
2278                        meta: None,
2279                    })
2280                    .related_snippets(NesRelatedSnippetsCapabilities::default())
2281                    .edit_history(NesEditHistoryCapabilities {
2282                        max_count: Some(6),
2283                        meta: None,
2284                    })
2285                    .user_actions(NesUserActionsCapabilities {
2286                        max_count: Some(16),
2287                        meta: None,
2288                    })
2289                    .open_files(NesOpenFilesCapabilities::default())
2290                    .diagnostics(NesDiagnosticsCapabilities::default()),
2291            );
2292
2293        let json = serde_json::to_value(&caps).unwrap();
2294        assert_eq!(
2295            json,
2296            json!({
2297                "events": {
2298                    "document": {
2299                        "didOpen": {},
2300                        "didChange": {
2301                            "syncKind": "incremental"
2302                        },
2303                        "didClose": {},
2304                        "didSave": {},
2305                        "didFocus": {}
2306                    }
2307                },
2308                "context": {
2309                    "recentFiles": {
2310                        "maxCount": 10
2311                    },
2312                    "relatedSnippets": {},
2313                    "editHistory": {
2314                        "maxCount": 6
2315                    },
2316                    "userActions": {
2317                        "maxCount": 16
2318                    },
2319                    "openFiles": {},
2320                    "diagnostics": {}
2321                }
2322            })
2323        );
2324
2325        // Round-trip
2326        let deserialized: NesCapabilities = serde_json::from_value(json).unwrap();
2327        assert_eq!(deserialized, caps);
2328    }
2329
2330    #[test]
2331    fn test_client_nes_capabilities_serialization() {
2332        let caps = ClientNesCapabilities::new()
2333            .jump(NesJumpCapabilities::default())
2334            .rename(NesRenameCapabilities::default())
2335            .search_and_replace(NesSearchAndReplaceCapabilities::default());
2336
2337        let json = serde_json::to_value(&caps).unwrap();
2338        assert_eq!(
2339            json,
2340            json!({
2341                "jump": {},
2342                "rename": {},
2343                "searchAndReplace": {}
2344            })
2345        );
2346
2347        let deserialized: ClientNesCapabilities = serde_json::from_value(json).unwrap();
2348        assert_eq!(deserialized, caps);
2349    }
2350
2351    #[test]
2352    fn test_document_did_open_serialization() {
2353        let notification = DidOpenDocumentNotification::new(
2354            "session_123",
2355            "file:///path/to/file.rs",
2356            "rust",
2357            1,
2358            "fn main() {\n    println!(\"hello\");\n}\n",
2359        );
2360
2361        let json = serde_json::to_value(&notification).unwrap();
2362        assert_eq!(
2363            json,
2364            json!({
2365                "sessionId": "session_123",
2366                "uri": "file:///path/to/file.rs",
2367                "languageId": "rust",
2368                "version": 1,
2369                "text": "fn main() {\n    println!(\"hello\");\n}\n"
2370            })
2371        );
2372
2373        let deserialized: DidOpenDocumentNotification = serde_json::from_value(json).unwrap();
2374        assert_eq!(deserialized, notification);
2375    }
2376
2377    #[test]
2378    fn test_document_did_change_incremental_serialization() {
2379        let notification = DidChangeDocumentNotification::new(
2380            "session_123",
2381            "file:///path/to/file.rs",
2382            2,
2383            vec![TextDocumentContentChangeEvent::incremental(
2384                Range::new(Position::new(1, 4), Position::new(1, 4)),
2385                "let x = 42;\n    ",
2386            )],
2387        );
2388
2389        let json = serde_json::to_value(&notification).unwrap();
2390        assert_eq!(
2391            json,
2392            json!({
2393                "sessionId": "session_123",
2394                "uri": "file:///path/to/file.rs",
2395                "version": 2,
2396                "contentChanges": [
2397                    {
2398                        "range": {
2399                            "start": { "line": 1, "character": 4 },
2400                            "end": { "line": 1, "character": 4 }
2401                        },
2402                        "text": "let x = 42;\n    "
2403                    }
2404                ]
2405            })
2406        );
2407    }
2408
2409    #[test]
2410    fn test_document_did_change_full_serialization() {
2411        let notification = DidChangeDocumentNotification::new(
2412            "session_123",
2413            "file:///path/to/file.rs",
2414            2,
2415            vec![TextDocumentContentChangeEvent::full(
2416                "fn main() {\n    let x = 42;\n    println!(\"hello\");\n}\n",
2417            )],
2418        );
2419
2420        let json = serde_json::to_value(&notification).unwrap();
2421        assert_eq!(
2422            json,
2423            json!({
2424                "sessionId": "session_123",
2425                "uri": "file:///path/to/file.rs",
2426                "version": 2,
2427                "contentChanges": [
2428                    {
2429                        "text": "fn main() {\n    let x = 42;\n    println!(\"hello\");\n}\n"
2430                    }
2431                ]
2432            })
2433        );
2434    }
2435
2436    #[test]
2437    fn test_document_did_close_serialization() {
2438        let notification =
2439            DidCloseDocumentNotification::new("session_123", "file:///path/to/file.rs");
2440        let json = serde_json::to_value(&notification).unwrap();
2441        assert_eq!(
2442            json,
2443            json!({ "sessionId": "session_123", "uri": "file:///path/to/file.rs" })
2444        );
2445    }
2446
2447    #[test]
2448    fn test_document_did_save_serialization() {
2449        let notification =
2450            DidSaveDocumentNotification::new("session_123", "file:///path/to/file.rs");
2451        let json = serde_json::to_value(&notification).unwrap();
2452        assert_eq!(
2453            json,
2454            json!({ "sessionId": "session_123", "uri": "file:///path/to/file.rs" })
2455        );
2456    }
2457
2458    #[test]
2459    fn test_document_did_focus_serialization() {
2460        let notification = DidFocusDocumentNotification::new(
2461            "session_123",
2462            "file:///path/to/file.rs",
2463            2,
2464            Position::new(5, 12),
2465            Range::new(Position::new(0, 0), Position::new(45, 0)),
2466        );
2467
2468        let json = serde_json::to_value(&notification).unwrap();
2469        assert_eq!(
2470            json,
2471            json!({
2472                "sessionId": "session_123",
2473                "uri": "file:///path/to/file.rs",
2474                "version": 2,
2475                "position": { "line": 5, "character": 12 },
2476                "visibleRange": {
2477                    "start": { "line": 0, "character": 0 },
2478                    "end": { "line": 45, "character": 0 }
2479                }
2480            })
2481        );
2482    }
2483
2484    #[test]
2485    fn test_nes_suggestion_edit_serialization() {
2486        let suggestion = NesSuggestion::Edit(
2487            NesEditSuggestion::new(
2488                "sugg_001",
2489                "file:///path/to/other_file.rs",
2490                vec![NesTextEdit::new(
2491                    Range::new(Position::new(5, 0), Position::new(5, 10)),
2492                    "let result = helper();",
2493                )],
2494            )
2495            .cursor_position(Position::new(5, 22)),
2496        );
2497
2498        let json = serde_json::to_value(&suggestion).unwrap();
2499        assert_eq!(
2500            json,
2501            json!({
2502                "kind": "edit",
2503                "id": "sugg_001",
2504                "uri": "file:///path/to/other_file.rs",
2505                "edits": [
2506                    {
2507                        "range": {
2508                            "start": { "line": 5, "character": 0 },
2509                            "end": { "line": 5, "character": 10 }
2510                        },
2511                        "newText": "let result = helper();"
2512                    }
2513                ],
2514                "cursorPosition": { "line": 5, "character": 22 }
2515            })
2516        );
2517
2518        let deserialized: NesSuggestion = serde_json::from_value(json).unwrap();
2519        assert_eq!(deserialized, suggestion);
2520    }
2521
2522    #[test]
2523    fn test_nes_suggestion_unknown_variant() {
2524        let suggestion: NesSuggestion = serde_json::from_value(json!({
2525            "kind": "_preview",
2526            "id": "sugg_001",
2527            "label": "Preview generated file"
2528        }))
2529        .unwrap();
2530
2531        let NesSuggestion::Other(unknown) = suggestion else {
2532            panic!("expected unknown NES suggestion");
2533        };
2534
2535        assert_eq!(unknown.kind, "_preview");
2536        assert_eq!(unknown.fields.get("id"), Some(&json!("sugg_001")));
2537        assert_eq!(
2538            serde_json::to_value(NesSuggestion::Other(unknown)).unwrap(),
2539            json!({
2540                "kind": "_preview",
2541                "id": "sugg_001",
2542                "label": "Preview generated file"
2543            })
2544        );
2545    }
2546
2547    #[test]
2548    fn test_nes_suggestion_unknown_does_not_hide_malformed_known_variant() {
2549        assert!(
2550            serde_json::from_value::<NesSuggestion>(json!({
2551                "kind": "edit"
2552            }))
2553            .is_err()
2554        );
2555    }
2556
2557    #[test]
2558    fn test_nes_suggestion_jump_serialization() {
2559        let suggestion = NesSuggestion::Jump(NesJumpSuggestion::new(
2560            "sugg_002",
2561            "file:///path/to/other_file.rs",
2562            Position::new(15, 4),
2563        ));
2564
2565        let json = serde_json::to_value(&suggestion).unwrap();
2566        assert_eq!(
2567            json,
2568            json!({
2569                "kind": "jump",
2570                "id": "sugg_002",
2571                "uri": "file:///path/to/other_file.rs",
2572                "position": { "line": 15, "character": 4 }
2573            })
2574        );
2575
2576        let deserialized: NesSuggestion = serde_json::from_value(json).unwrap();
2577        assert_eq!(deserialized, suggestion);
2578    }
2579
2580    #[test]
2581    fn test_nes_suggestion_rename_serialization() {
2582        let suggestion = NesSuggestion::Rename(NesRenameSuggestion::new(
2583            "sugg_003",
2584            "file:///path/to/file.rs",
2585            Position::new(5, 10),
2586            "calculateTotal",
2587        ));
2588
2589        let json = serde_json::to_value(&suggestion).unwrap();
2590        assert_eq!(
2591            json,
2592            json!({
2593                "kind": "rename",
2594                "id": "sugg_003",
2595                "uri": "file:///path/to/file.rs",
2596                "position": { "line": 5, "character": 10 },
2597                "newName": "calculateTotal"
2598            })
2599        );
2600
2601        let deserialized: NesSuggestion = serde_json::from_value(json).unwrap();
2602        assert_eq!(deserialized, suggestion);
2603    }
2604
2605    #[test]
2606    fn test_nes_suggestion_search_and_replace_serialization() {
2607        let suggestion = NesSuggestion::SearchAndReplace(
2608            NesSearchAndReplaceSuggestion::new(
2609                "sugg_004",
2610                "file:///path/to/file.rs",
2611                "oldFunction",
2612                "newFunction",
2613            )
2614            .is_regex(false),
2615        );
2616
2617        let json = serde_json::to_value(&suggestion).unwrap();
2618        assert_eq!(
2619            json,
2620            json!({
2621                "kind": "searchAndReplace",
2622                "id": "sugg_004",
2623                "uri": "file:///path/to/file.rs",
2624                "search": "oldFunction",
2625                "replace": "newFunction",
2626                "isRegex": false
2627            })
2628        );
2629
2630        let deserialized: NesSuggestion = serde_json::from_value(json).unwrap();
2631        assert_eq!(deserialized, suggestion);
2632    }
2633
2634    #[test]
2635    fn test_nes_start_request_serialization() {
2636        let request = StartNesRequest::new()
2637            .workspace_uri("file:///Users/alice/projects/my-app")
2638            .workspace_folders(vec![WorkspaceFolder::new(
2639                "file:///Users/alice/projects/my-app",
2640                "my-app",
2641            )])
2642            .repository(NesRepository::new(
2643                "my-app",
2644                "alice",
2645                "https://github.com/alice/my-app.git",
2646            ));
2647
2648        let json = serde_json::to_value(&request).unwrap();
2649        assert_eq!(
2650            json,
2651            json!({
2652                "workspaceUri": "file:///Users/alice/projects/my-app",
2653                "workspaceFolders": [
2654                    {
2655                        "uri": "file:///Users/alice/projects/my-app",
2656                        "name": "my-app"
2657                    }
2658                ],
2659                "repository": {
2660                    "name": "my-app",
2661                    "owner": "alice",
2662                    "remoteUrl": "https://github.com/alice/my-app.git"
2663                }
2664            })
2665        );
2666    }
2667
2668    #[test]
2669    fn test_nes_start_response_serialization() {
2670        let response = StartNesResponse::new("session_abc123");
2671        let json = serde_json::to_value(&response).unwrap();
2672        assert_eq!(json, json!({ "sessionId": "session_abc123" }));
2673    }
2674
2675    #[test]
2676    fn test_nes_trigger_kind_serialization() {
2677        assert_eq!(
2678            serde_json::to_value(&NesTriggerKind::Automatic).unwrap(),
2679            json!("automatic")
2680        );
2681        assert_eq!(
2682            serde_json::to_value(&NesTriggerKind::Diagnostic).unwrap(),
2683            json!("diagnostic")
2684        );
2685        assert_eq!(
2686            serde_json::to_value(&NesTriggerKind::Manual).unwrap(),
2687            json!("manual")
2688        );
2689    }
2690
2691    #[test]
2692    fn test_nes_reject_reason_serialization() {
2693        assert_eq!(
2694            serde_json::to_value(&NesRejectReason::Rejected).unwrap(),
2695            json!("rejected")
2696        );
2697        assert_eq!(
2698            serde_json::to_value(&NesRejectReason::Ignored).unwrap(),
2699            json!("ignored")
2700        );
2701        assert_eq!(
2702            serde_json::to_value(&NesRejectReason::Replaced).unwrap(),
2703            json!("replaced")
2704        );
2705        assert_eq!(
2706            serde_json::to_value(&NesRejectReason::Cancelled).unwrap(),
2707            json!("cancelled")
2708        );
2709    }
2710
2711    #[test]
2712    fn test_nes_accept_notification_serialization() {
2713        let notification = AcceptNesNotification::new("session_123", "sugg_001");
2714        let json = serde_json::to_value(&notification).unwrap();
2715        assert_eq!(
2716            json,
2717            json!({ "sessionId": "session_123", "id": "sugg_001" })
2718        );
2719    }
2720
2721    #[test]
2722    fn test_nes_reject_notification_serialization() {
2723        let notification =
2724            RejectNesNotification::new("session_123", "sugg_001").reason(NesRejectReason::Rejected);
2725        let json = serde_json::to_value(&notification).unwrap();
2726        assert_eq!(
2727            json,
2728            json!({ "sessionId": "session_123", "id": "sugg_001", "reason": "rejected" })
2729        );
2730    }
2731
2732    #[test]
2733    fn test_nes_suggest_request_with_context_serialization() {
2734        let request = SuggestNesRequest::new(
2735            "session_123",
2736            "file:///path/to/file.rs",
2737            2,
2738            Position::new(5, 12),
2739            NesTriggerKind::Automatic,
2740        )
2741        .selection(Range::new(Position::new(5, 4), Position::new(5, 12)))
2742        .context(
2743            NesSuggestContext::new()
2744                .recent_files(vec![NesRecentFile::new(
2745                    "file:///path/to/utils.rs",
2746                    "rust",
2747                    "pub fn helper() -> i32 { 42 }\n",
2748                )])
2749                .diagnostics(vec![NesDiagnostic::new(
2750                    "file:///path/to/file.rs",
2751                    Range::new(Position::new(5, 0), Position::new(5, 10)),
2752                    NesDiagnosticSeverity::Error,
2753                    "cannot find value `foo` in this scope",
2754                )]),
2755        );
2756
2757        let json = serde_json::to_value(&request).unwrap();
2758        assert_eq!(json["sessionId"], "session_123");
2759        assert_eq!(json["uri"], "file:///path/to/file.rs");
2760        assert_eq!(json["version"], 2);
2761        assert_eq!(json["triggerKind"], "automatic");
2762        assert_eq!(
2763            json["context"]["recentFiles"][0]["uri"],
2764            "file:///path/to/utils.rs"
2765        );
2766        assert_eq!(json["context"]["diagnostics"][0]["severity"], "error");
2767    }
2768
2769    #[test]
2770    fn test_text_document_sync_kind_serialization() {
2771        assert_eq!(
2772            serde_json::to_value(&TextDocumentSyncKind::Full).unwrap(),
2773            json!("full")
2774        );
2775        assert_eq!(
2776            serde_json::to_value(&TextDocumentSyncKind::Incremental).unwrap(),
2777            json!("incremental")
2778        );
2779        assert!(serde_json::from_value::<TextDocumentSyncKind>(json!("_future")).is_err());
2780    }
2781
2782    #[test]
2783    fn test_document_event_capabilities_drop_unknown_did_change_sync_kind() {
2784        let caps: NesDocumentEventCapabilities = serde_json::from_value(json!({
2785            "didChange": {
2786                "syncKind": "_future"
2787            }
2788        }))
2789        .unwrap();
2790
2791        assert_eq!(caps.did_change, None);
2792    }
2793
2794    #[test]
2795    fn test_document_did_change_capabilities_requires_sync_kind() {
2796        assert!(serde_json::from_value::<NesDocumentDidChangeCapabilities>(json!({})).is_err());
2797    }
2798
2799    #[test]
2800    fn test_nes_suggest_response_serialization() {
2801        let response = SuggestNesResponse::new(vec![
2802            NesSuggestion::Edit(NesEditSuggestion::new(
2803                "sugg_001",
2804                "file:///path/to/file.rs",
2805                vec![NesTextEdit::new(
2806                    Range::new(Position::new(5, 0), Position::new(5, 10)),
2807                    "let result = helper();",
2808                )],
2809            )),
2810            NesSuggestion::Jump(NesJumpSuggestion::new(
2811                "sugg_002",
2812                "file:///path/to/other.rs",
2813                Position::new(10, 0),
2814            )),
2815        ]);
2816
2817        let json = serde_json::to_value(&response).unwrap();
2818        assert_eq!(json["suggestions"].as_array().unwrap().len(), 2);
2819        assert_eq!(json["suggestions"][0]["kind"], "edit");
2820        assert_eq!(json["suggestions"][1]["kind"], "jump");
2821    }
2822}