1use std::future::Future;
2use std::path::PathBuf;
3use std::pin::Pin;
4
5use serde_json::Value;
6
7use crate::provider::LLMError;
8
9pub const COPILOT_PROVIDER_KEY: &str = "copilot";
10pub const COPILOT_MODEL_ID: &str = vtcode_config::constants::models::copilot::DEFAULT_MODEL;
11pub const COPILOT_AUTH_DOC_PATH: &str = "docs/providers/copilot.md";
12
13pub type CopilotPromptSessionFuture<'a> =
14 Pin<Box<dyn Future<Output = Result<crate::copilot::PromptSession, LLMError>> + Send + 'a>>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CopilotAcpCompatibilityState {
18 FullTools,
19 PromptOnly,
20 Unavailable,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum CopilotAuthEvent {
25 VerificationCode { url: String, user_code: String },
26 Progress { message: String },
27 Success { account: Option<String> },
28 Failure { message: String },
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct CopilotDiscoveredModel {
33 pub id: String,
34 pub(crate) name: String,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum CopilotAuthStatusKind {
39 Authenticated,
40 Unauthenticated,
41 ServerUnavailable,
42 AuthFlowFailed,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct CopilotAuthStatus {
47 pub kind: CopilotAuthStatusKind,
48 pub message: Option<String>,
49}
50
51impl CopilotAuthStatus {
52 #[must_use]
53 pub(crate) fn authenticated(message: Option<String>) -> Self {
54 Self {
55 kind: CopilotAuthStatusKind::Authenticated,
56 message,
57 }
58 }
59
60 #[must_use]
61 pub(crate) fn unauthenticated(message: Option<String>) -> Self {
62 Self {
63 kind: CopilotAuthStatusKind::Unauthenticated,
64 message,
65 }
66 }
67
68 #[must_use]
69 pub(crate) fn server_unavailable(message: impl Into<String>) -> Self {
70 Self {
71 kind: CopilotAuthStatusKind::ServerUnavailable,
72 message: Some(message.into()),
73 }
74 }
75
76 #[must_use]
77 pub(crate) fn auth_flow_failed(message: impl Into<String>) -> Self {
78 Self {
79 kind: CopilotAuthStatusKind::AuthFlowFailed,
80 message: Some(message.into()),
81 }
82 }
83
84 #[must_use]
85 pub(crate) fn is_authenticated(&self) -> bool {
86 matches!(self.kind, CopilotAuthStatusKind::Authenticated)
87 }
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct CopilotToolCallRequest {
92 pub tool_call_id: String,
93 pub tool_name: String,
94 pub arguments: Value,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct CopilotToolUse {
99 tool_call_id: String,
100 tool_name: String,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum CopilotObservedToolCallStatus {
105 Pending,
106 InProgress,
107 Completed,
108 Failed,
109}
110
111#[derive(Debug, Clone, PartialEq)]
112pub struct CopilotObservedToolCall {
113 pub tool_call_id: String,
114 pub tool_name: String,
115 pub status: CopilotObservedToolCallStatus,
116 pub arguments: Option<Value>,
117 pub output: Option<String>,
118 pub terminal_id: Option<String>,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct CopilotTerminalEnvVar {
123 pub name: String,
124 pub value: String,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct CopilotTerminalCreateRequest {
129 pub session_id: String,
130 pub command: String,
131 pub args: Vec<String>,
132 pub env: Vec<CopilotTerminalEnvVar>,
133 pub cwd: Option<PathBuf>,
134 pub output_byte_limit: Option<usize>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct CopilotTerminalCreateResponse {
139 pub terminal_id: String,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct CopilotTerminalOutputRequest {
144 pub(crate) session_id: String,
145 pub terminal_id: String,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct CopilotTerminalReleaseRequest {
150 pub(crate) session_id: String,
151 pub terminal_id: String,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct CopilotTerminalKillRequest {
156 pub(crate) session_id: String,
157 pub terminal_id: String,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct CopilotTerminalWaitForExitRequest {
162 pub(crate) session_id: String,
163 pub terminal_id: String,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct CopilotTerminalExitStatus {
168 pub exit_code: Option<u32>,
169 pub signal: Option<String>,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct CopilotTerminalOutputResponse {
174 pub output: String,
175 pub truncated: bool,
176 pub exit_status: Option<CopilotTerminalExitStatus>,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct CopilotToolCallSuccess {
181 pub text_result_for_llm: String,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct CopilotToolCallFailure {
186 pub text_result_for_llm: String,
187 pub error: String,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub enum CopilotToolCallResponse {
192 Success(CopilotToolCallSuccess),
193 Failure(CopilotToolCallFailure),
194}
195
196#[derive(Debug, Clone, PartialEq)]
197pub enum CopilotPermissionRequest {
198 Shell {
199 tool_call_id: Option<String>,
200 full_command_text: String,
201 intention: String,
202 commands: Vec<CopilotShellCommandSummary>,
203 possible_paths: Vec<String>,
204 possible_urls: Vec<String>,
205 has_write_file_redirection: bool,
206 can_offer_session_approval: bool,
207 warning: Option<String>,
208 },
209 Write {
210 tool_call_id: Option<String>,
211 intention: String,
212 file_name: String,
213 diff: String,
214 new_file_contents: Option<String>,
215 },
216 Read {
217 tool_call_id: Option<String>,
218 intention: String,
219 path: String,
220 },
221 Mcp {
222 tool_call_id: Option<String>,
223 server_name: String,
224 tool_name: String,
225 tool_title: String,
226 args: Option<Value>,
227 read_only: bool,
228 },
229 Url {
230 tool_call_id: Option<String>,
231 intention: String,
232 url: String,
233 },
234 Memory {
235 tool_call_id: Option<String>,
236 subject: String,
237 fact: String,
238 citations: String,
239 },
240 CustomTool {
241 tool_call_id: Option<String>,
242 tool_name: String,
243 tool_description: String,
244 args: Option<Value>,
245 },
246 Hook {
247 tool_call_id: Option<String>,
248 tool_name: String,
249 tool_args: Option<Value>,
250 hook_message: Option<String>,
251 },
252 Unknown {
253 kind: Option<String>,
254 raw: Value,
255 },
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct CopilotShellCommandSummary {
260 pub(crate) identifier: String,
261 pub(crate) read_only: bool,
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
265pub enum CopilotPermissionDecision {
266 Approved,
267 ApprovedAlways,
268 DeniedByRules,
269 DeniedNoApprovalRule,
270 DeniedInteractivelyByUser { feedback: Option<String> },
271 DeniedByContentExclusionPolicy { path: String, message: String },
272}
273
274impl CopilotPermissionDecision {
275 #[must_use]
276 pub(crate) fn to_rpc_result(&self) -> Value {
277 match self {
278 Self::Approved | Self::ApprovedAlways => serde_json::json!({
279 "kind": "approved",
280 }),
281 Self::DeniedByRules => serde_json::json!({
282 "kind": "denied-by-rules",
283 "rules": [],
284 }),
285 Self::DeniedNoApprovalRule => serde_json::json!({
286 "kind": "denied-no-approval-rule-and-could-not-request-from-user",
287 }),
288 Self::DeniedInteractivelyByUser { feedback } => {
289 let mut result = serde_json::Map::from_iter([(
290 "kind".to_string(),
291 Value::String("denied-interactively-by-user".to_string()),
292 )]);
293 if let Some(feedback) = feedback.as_ref().filter(|value| !value.trim().is_empty()) {
294 result.insert("feedback".to_string(), Value::String(feedback.clone()));
295 }
296 Value::Object(result)
297 }
298 Self::DeniedByContentExclusionPolicy { path, message } => {
299 serde_json::json!({
300 "kind": "denied-by-content-exclusion-policy",
301 "path": path,
302 "message": message,
303 })
304 }
305 }
306 }
307}