ai_agent/bridge/
code_session_api.rs1use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
11use serde::{Deserialize, Serialize};
12
13const ANTHROPIC_VERSION: &str = "2023-06-01";
14
15fn oauth_headers(access_token: &str) -> HeaderMap {
17 let mut headers = HeaderMap::new();
18 if let Ok(val) = HeaderValue::from_str(&format!("Bearer {}", access_token)) {
19 headers.insert(AUTHORIZATION, val);
20 }
21 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
22 headers.insert(
23 HeaderName::from_static("anthropic-version"),
24 HeaderValue::from_static(ANTHROPIC_VERSION),
25 );
26 headers
27}
28
29pub async fn create_code_session(
40 base_url: &str,
41 access_token: &str,
42 title: &str,
43 timeout_ms: u64,
44 tags: Option<Vec<String>>,
45) -> Option<String> {
46 let url = format!("{}/v1/code/sessions", base_url);
47 let headers = oauth_headers(access_token);
48
49 let mut body = serde_json::json!({
54 "title": title,
55 "bridge": {}
56 });
57
58 if let Some(tags) = tags {
59 if !tags.is_empty() {
60 body["tags"] = serde_json::json!(tags);
61 }
62 }
63
64 let client = reqwest::Client::new();
65 let response = client
66 .post(&url)
67 .headers(headers)
68 .timeout(std::time::Duration::from_millis(timeout_ms))
69 .json(&body)
70 .send()
71 .await
72 .ok()?;
73
74 let status = response.status();
75 if status != reqwest::StatusCode::OK && status != reqwest::StatusCode::CREATED {
76 let status_code = status.as_u16();
77 let body = response.text().await.unwrap_or_default();
78 let detail = extract_error_detail_from_text(&body);
79 log_for_debugging(&format!(
80 "[code-session] Session create failed {}{}",
81 status_code,
82 detail.map(|d| format!(": {}", d)).unwrap_or_default()
83 ));
84 return None;
85 }
86
87 let data: serde_json::Value = response.json().await.ok()?;
88
89 let session = data.get("session")?;
91 let session_obj = session.as_object()?;
92 let id = session_obj.get("id")?.as_str()?;
93 if !id.starts_with("cse_") {
94 let data_str: String = serde_json::to_string(&data)
95 .ok()
96 .map(|s| s.chars().take(200).collect())
97 .unwrap_or_default();
98 log_for_debugging(&format!(
99 "[code-session] No session.id (cse_*) in response: {}",
100 data_str
101 ));
102 return None;
103 }
104
105 Some(id.to_string())
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct RemoteCredentials {
112 #[serde(rename = "worker_jwt")]
113 pub worker_jwt: String,
114 #[serde(rename = "api_base_url")]
115 pub api_base_url: String,
116 #[serde(rename = "expires_in")]
117 pub expires_in: u64,
118 #[serde(rename = "worker_epoch")]
119 pub worker_epoch: i64,
120}
121
122pub async fn fetch_remote_credentials(
133 session_id: &str,
134 base_url: &str,
135 access_token: &str,
136 timeout_ms: u64,
137 trusted_device_token: Option<&str>,
138) -> Option<RemoteCredentials> {
139 let url = format!("{}/v1/code/sessions/{}/bridge", base_url, session_id);
140 let mut headers = oauth_headers(access_token);
141
142 if let Some(token) = trusted_device_token {
143 if let Ok(val) = HeaderValue::from_str(token) {
144 headers.insert(HeaderName::from_static("x-trusted-device-token"), val);
145 }
146 }
147
148 let client = reqwest::Client::new();
149 let response = client
150 .post(&url)
151 .headers(headers)
152 .timeout(std::time::Duration::from_millis(timeout_ms))
153 .json(&serde_json::json!({}))
154 .send()
155 .await
156 .ok()?;
157
158 let status = response.status();
159 if status != reqwest::StatusCode::OK {
160 let status_code = status.as_u16();
161 let body = response.text().await.unwrap_or_default();
162 let detail = extract_error_detail_from_text(&body);
163 log_for_debugging(&format!(
164 "[code-session] /bridge failed {}{}",
165 status_code,
166 detail.map(|d| format!(": {}", d)).unwrap_or_default()
167 ));
168 return None;
169 }
170
171 let data: serde_json::Value = response.json().await.ok()?;
172
173 let worker_jwt = data.get("worker_jwt")?.as_str()?.to_string();
175 let expires_in = data.get("expires_in")?.as_u64()?;
176 let api_base_url = data.get("api_base_url")?.as_str()?.to_string();
177
178 let raw_epoch = data.get("worker_epoch")?;
181 let epoch = if let Some(s) = raw_epoch.as_str() {
182 s.parse().ok()?
183 } else {
184 raw_epoch.as_i64()?
185 };
186
187 Some(RemoteCredentials {
188 worker_jwt,
189 api_base_url,
190 expires_in,
191 worker_epoch: epoch,
192 })
193}
194
195fn extract_error_detail_from_text(body: &str) -> Option<String> {
197 let data: serde_json::Value = serde_json::from_str(body).ok()?;
198 data.get("message")
199 .and_then(|m| m.as_str())
200 .map(|s| s.to_string())
201}
202
203#[allow(unused_variables)]
205fn log_for_debugging(msg: &str) {
206 eprintln!("{}", msg);
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_oauth_headers() {
215 let headers = oauth_headers("test-token");
216 assert!(headers.get("Authorization").is_some());
218 assert!(headers.get("Content-Type").is_some());
220 }
221}