Skip to main content

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