Skip to main content

vtcode_llm/copilot/
types.rs

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}