Skip to main content

cursor_sdk/
types.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
7#[serde(rename_all = "camelCase")]
8pub struct ApiKeyInfo {
9    pub api_key_name: String,
10    pub created_at: String,
11    pub user_email: Option<String>,
12}
13
14#[derive(Debug, Clone, Deserialize, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ListResponse<T> {
17    pub items: Vec<T>,
18    pub next_cursor: Option<String>,
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct ModelListResponse {
23    pub items: Vec<String>,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct RepositoryListResponse {
28    pub items: Vec<RepositoryItem>,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct RepositoryItem {
33    pub url: String,
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize)]
37pub struct EnvironmentInfo {
38    #[serde(rename = "type")]
39    pub kind: String,
40    pub name: Option<String>,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub struct RepositoryRef {
46    pub url: Option<String>,
47    pub starting_ref: Option<String>,
48    pub pr_url: Option<String>,
49}
50
51#[derive(Debug, Clone, Deserialize, Serialize)]
52pub struct ModelSelection {
53    pub id: String,
54}
55
56#[derive(Debug, Clone, Deserialize, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct ImageInput {
59    pub data: String,
60    pub mime_type: String,
61}
62
63#[derive(Debug, Clone, Deserialize, Serialize)]
64pub struct Prompt {
65    pub text: String,
66    #[serde(default, skip_serializing_if = "Vec::is_empty")]
67    pub images: Vec<ImageInput>,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize)]
71#[serde(rename_all = "camelCase")]
72pub struct AgentSummary {
73    pub id: String,
74    pub name: String,
75    pub status: String,
76    pub env: EnvironmentInfo,
77    pub url: String,
78    pub created_at: String,
79    pub updated_at: String,
80    pub latest_run_id: Option<String>,
81}
82
83#[derive(Debug, Clone, Deserialize, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct Agent {
86    pub id: String,
87    pub name: String,
88    pub status: String,
89    pub env: EnvironmentInfo,
90    #[serde(default)]
91    pub repos: Vec<RepositoryRef>,
92    pub branch_name: Option<String>,
93    pub auto_generate_branch: Option<bool>,
94    pub auto_create_pr: Option<bool>,
95    pub skip_reviewer_request: Option<bool>,
96    pub url: String,
97    pub created_at: String,
98    pub updated_at: String,
99    pub latest_run_id: Option<String>,
100}
101
102#[derive(Debug, Clone, Deserialize, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct Run {
105    pub id: String,
106    pub agent_id: String,
107    pub status: String,
108    pub created_at: String,
109    pub updated_at: String,
110}
111
112impl Run {
113    pub fn is_terminal(&self) -> bool {
114        matches!(
115            self.status.as_str(),
116            "FINISHED" | "ERROR" | "CANCELLED" | "EXPIRED"
117        )
118    }
119}
120
121#[derive(Debug, Clone, Deserialize, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub struct CreateAgentRequest {
124    pub prompt: Prompt,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub model: Option<ModelSelection>,
127    pub repos: Vec<RepositoryRef>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub branch_name: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub auto_generate_branch: Option<bool>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub auto_create_pr: Option<bool>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub skip_reviewer_request: Option<bool>,
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
139pub struct CreateRunRequest {
140    pub prompt: Prompt,
141}
142
143#[derive(Debug, Clone, Deserialize, Serialize)]
144pub struct CreateAgentResponse {
145    pub agent: Agent,
146    pub run: Run,
147}
148
149#[derive(Debug, Clone, Deserialize, Serialize)]
150pub struct CreateRunResponse {
151    pub run: Run,
152}
153
154#[derive(Debug, Clone, Deserialize, Serialize)]
155#[serde(rename_all = "camelCase")]
156pub struct Artifact {
157    pub path: String,
158    pub size_bytes: u64,
159    pub updated_at: String,
160}
161
162#[derive(Debug, Clone, Deserialize, Serialize)]
163#[serde(rename_all = "camelCase")]
164pub struct DownloadArtifactResponse {
165    pub url: String,
166    pub expires_at: String,
167}
168
169#[derive(Debug, Clone, Serialize)]
170pub struct RunStreamMessage {
171    pub id: Option<String>,
172    pub event: RunStreamEvent,
173}
174
175#[derive(Debug, Clone, Serialize)]
176pub enum RunStreamEvent {
177    Status {
178        run_id: String,
179        status: String,
180    },
181    Assistant {
182        text: String,
183    },
184    Thinking {
185        text: String,
186    },
187    ToolCall {
188        payload: Value,
189    },
190    Heartbeat,
191    Result {
192        run_id: String,
193        status: String,
194    },
195    Error {
196        code: Option<String>,
197        message: String,
198    },
199    Done,
200    Unknown {
201        name: String,
202        payload: Value,
203    },
204}
205
206#[derive(Debug, Clone)]
207pub struct WaitForRunOptions {
208    pub last_event_id: Option<String>,
209    pub poll_interval: Duration,
210    pub timeout: Option<Duration>,
211    pub max_poll_attempts: Option<u32>,
212}
213
214impl Default for WaitForRunOptions {
215    fn default() -> Self {
216        Self {
217            last_event_id: None,
218            poll_interval: Duration::from_secs(2),
219            timeout: None,
220            max_poll_attempts: None,
221        }
222    }
223}
224
225#[derive(Debug, Clone)]
226pub struct WaitForRunResult {
227    pub run: Run,
228    pub stream_messages: Vec<RunStreamMessage>,
229    pub last_event_id: Option<String>,
230    pub used_polling_fallback: bool,
231}
232
233#[cfg(test)]
234mod tests {
235    use super::Run;
236
237    fn run_with_status(status: &str) -> Run {
238        Run {
239            id: "run-1".to_owned(),
240            agent_id: "bc-1".to_owned(),
241            status: status.to_owned(),
242            created_at: "2026-04-13T18:30:00.000Z".to_owned(),
243            updated_at: "2026-04-13T18:30:00.000Z".to_owned(),
244        }
245    }
246
247    #[test]
248    fn terminal_statuses_are_detected() {
249        for status in ["FINISHED", "ERROR", "CANCELLED", "EXPIRED"] {
250            assert!(run_with_status(status).is_terminal(), "status {status} should be terminal");
251        }
252
253        for status in ["CREATING", "RUNNING", "ACTIVE"] {
254            assert!(!run_with_status(status).is_terminal(), "status {status} should not be terminal");
255        }
256    }
257}