Skip to main content

relay_knowledge/interfaces/agent/acp/
protocol.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::api::{AgentRetrievalResult, CodeGraphContextResponse, RuntimeIdentity};
5
6/// ACP initialize response with relay-knowledge capability metadata.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct AcpInitializeResponse {
9    #[serde(rename = "_meta")]
10    pub meta: AcpInitializeMeta,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct AcpInitializeMeta {
15    #[serde(rename = "relayKnowledge")]
16    pub relay_knowledge: AcpRelayKnowledgeCapability,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct AcpRelayKnowledgeCapability {
21    #[serde(rename = "graphRetrieval")]
22    pub graph_retrieval: bool,
23    #[serde(rename = "readOnly")]
24    pub read_only: bool,
25    #[serde(rename = "supportsCancellation")]
26    pub supports_cancellation: bool,
27    #[serde(rename = "supportsIndexRefreshPermission")]
28    pub supports_index_refresh_permission: bool,
29}
30
31/// Local ACP session request.
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
33pub struct AcpSessionRequest {
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub client_name: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub client_version: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub actor_id: Option<String>,
40}
41
42/// Created ACP session metadata.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct AcpSession {
45    pub session_id: String,
46    pub runtime_identity: RuntimeIdentity,
47    pub policy_id: String,
48    pub authorized_scope_count: usize,
49}
50
51/// ACP prompt request with structured relay metadata.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct AcpPromptRequest {
54    pub prompt: String,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub request_id: Option<String>,
57    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
58    pub meta: Option<AcpPromptMeta>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct AcpPromptMeta {
63    #[serde(rename = "relayKnowledge", skip_serializing_if = "Option::is_none")]
64    pub relay_knowledge: Option<AcpRelayKnowledgePrompt>,
65}
66
67#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
68pub struct AcpRelayKnowledgePrompt {
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub query: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub source_scope: Option<String>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub repository: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub ref_selector: Option<String>,
77    #[serde(default, skip_serializing_if = "Vec::is_empty")]
78    pub path_filters: Vec<String>,
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    pub language_filters: Vec<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub limit: Option<usize>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub freshness: Option<String>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub max_context_bytes: Option<usize>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub include_code: Option<bool>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub exclude_generated: Option<bool>,
91}
92
93/// ACP prompt response containing bounded progress and an optional context artifact.
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct AcpPromptResponse {
96    pub session_id: String,
97    pub request_id: String,
98    pub updates: Vec<AcpSessionUpdate>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub context_artifact: Option<AcpContextArtifact>,
101    pub stop_reason: AcpStopReason,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub error: Option<AcpErrorPayload>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107pub struct AcpContextArtifact {
108    pub artifact_id: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub result: Option<AgentRetrievalResult>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub codegraph_context: Option<CodeGraphContextResponse>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117pub enum AcpStopReason {
118    Completed,
119    Failed,
120    Cancelled,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct AcpErrorPayload {
125    pub error_kind: String,
126    pub message: String,
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct AcpSessionUpdate {
131    pub request_id: String,
132    pub kind: AcpSessionUpdateKind,
133    pub status: AcpSessionUpdateStatus,
134    pub message: String,
135    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
136    pub meta: Option<Value>,
137}
138
139impl AcpSessionUpdate {
140    pub(super) fn pending(request_id: &str, message: &str) -> Self {
141        Self::new(
142            request_id,
143            AcpSessionUpdateKind::ToolCallUpdate,
144            AcpSessionUpdateStatus::Pending,
145            message,
146            None,
147        )
148    }
149
150    pub(super) fn in_progress(request_id: &str, message: &str) -> Self {
151        Self::new(
152            request_id,
153            AcpSessionUpdateKind::ToolCallUpdate,
154            AcpSessionUpdateStatus::InProgress,
155            message,
156            None,
157        )
158    }
159
160    pub(super) fn meta(request_id: &str, message: &str, meta: Value) -> Self {
161        Self::new(
162            request_id,
163            AcpSessionUpdateKind::SessionUpdate,
164            AcpSessionUpdateStatus::InProgress,
165            message,
166            Some(meta),
167        )
168    }
169
170    pub(super) fn completed(request_id: &str, message: &str) -> Self {
171        Self::new(
172            request_id,
173            AcpSessionUpdateKind::ToolCallUpdate,
174            AcpSessionUpdateStatus::Completed,
175            message,
176            None,
177        )
178    }
179
180    pub(super) fn failed(request_id: &str, message: &str, status: AcpSessionUpdateStatus) -> Self {
181        Self::new(
182            request_id,
183            AcpSessionUpdateKind::ToolCallUpdate,
184            status,
185            message,
186            None,
187        )
188    }
189
190    fn new(
191        request_id: &str,
192        kind: AcpSessionUpdateKind,
193        status: AcpSessionUpdateStatus,
194        message: &str,
195        meta: Option<Value>,
196    ) -> Self {
197        Self {
198            request_id: request_id.to_owned(),
199            kind,
200            status,
201            message: message.to_owned(),
202            meta,
203        }
204    }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum AcpSessionUpdateKind {
210    SessionUpdate,
211    ToolCallUpdate,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "snake_case")]
216pub enum AcpSessionUpdateStatus {
217    Pending,
218    InProgress,
219    Completed,
220    Failed,
221    Cancelled,
222}
223
224#[cfg(test)]
225#[path = "protocol_tests.rs"]
226mod tests;