use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
const ANTHROPIC_VERSION: &str = "2023-06-01";
fn oauth_headers(access_token: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
if let Ok(val) = HeaderValue::from_str(&format!("Bearer {}", access_token)) {
headers.insert(AUTHORIZATION, val);
}
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(
HeaderName::from_static("anthropic-version"),
HeaderValue::from_static(ANTHROPIC_VERSION),
);
headers
}
pub async fn create_code_session(
base_url: &str,
access_token: &str,
title: &str,
timeout_ms: u64,
tags: Option<Vec<String>>,
) -> Option<String> {
let url = format!("{}/v1/code/sessions", base_url);
let headers = oauth_headers(access_token);
let mut body = serde_json::json!({
"title": title,
"bridge": {}
});
if let Some(tags) = tags {
if !tags.is_empty() {
body["tags"] = serde_json::json!(tags);
}
}
let client = reqwest::Client::new();
let response = client
.post(&url)
.headers(headers)
.timeout(std::time::Duration::from_millis(timeout_ms))
.json(&body)
.send()
.await
.ok()?;
let status = response.status();
if status != reqwest::StatusCode::OK && status != reqwest::StatusCode::CREATED {
let status_code = status.as_u16();
let body = response.text().await.unwrap_or_default();
let detail = extract_error_detail_from_text(&body);
log_for_debugging(&format!(
"[code-session] Session create failed {}{}",
status_code,
detail.map(|d| format!(": {}", d)).unwrap_or_default()
));
return None;
}
let data: serde_json::Value = response.json().await.ok()?;
let session = data.get("session")?;
let session_obj = session.as_object()?;
let id = session_obj.get("id")?.as_str()?;
if !id.starts_with("cse_") {
let data_str: String = serde_json::to_string(&data)
.ok()
.map(|s| s.chars().take(200).collect())
.unwrap_or_default();
log_for_debugging(&format!(
"[code-session] No session.id (cse_*) in response: {}",
data_str
));
return None;
}
Some(id.to_string())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteCredentials {
#[serde(rename = "worker_jwt")]
pub worker_jwt: String,
#[serde(rename = "api_base_url")]
pub api_base_url: String,
#[serde(rename = "expires_in")]
pub expires_in: u64,
#[serde(rename = "worker_epoch")]
pub worker_epoch: i64,
}
pub async fn fetch_remote_credentials(
session_id: &str,
base_url: &str,
access_token: &str,
timeout_ms: u64,
trusted_device_token: Option<&str>,
) -> Option<RemoteCredentials> {
let url = format!("{}/v1/code/sessions/{}/bridge", base_url, session_id);
let mut headers = oauth_headers(access_token);
if let Some(token) = trusted_device_token {
if let Ok(val) = HeaderValue::from_str(token) {
headers.insert(HeaderName::from_static("x-trusted-device-token"), val);
}
}
let client = reqwest::Client::new();
let response = client
.post(&url)
.headers(headers)
.timeout(std::time::Duration::from_millis(timeout_ms))
.json(&serde_json::json!({}))
.send()
.await
.ok()?;
let status = response.status();
if status != reqwest::StatusCode::OK {
let status_code = status.as_u16();
let body = response.text().await.unwrap_or_default();
let detail = extract_error_detail_from_text(&body);
log_for_debugging(&format!(
"[code-session] /bridge failed {}{}",
status_code,
detail.map(|d| format!(": {}", d)).unwrap_or_default()
));
return None;
}
let data: serde_json::Value = response.json().await.ok()?;
let worker_jwt = data.get("worker_jwt")?.as_str()?.to_string();
let expires_in = data.get("expires_in")?.as_u64()?;
let api_base_url = data.get("api_base_url")?.as_str()?.to_string();
let raw_epoch = data.get("worker_epoch")?;
let epoch = if let Some(s) = raw_epoch.as_str() {
s.parse().ok()?
} else {
raw_epoch.as_i64()?
};
Some(RemoteCredentials {
worker_jwt,
api_base_url,
expires_in,
worker_epoch: epoch,
})
}
fn extract_error_detail_from_text(body: &str) -> Option<String> {
let data: serde_json::Value = serde_json::from_str(body).ok()?;
data.get("message")
.and_then(|m| m.as_str())
.map(|s| s.to_string())
}
#[allow(unused_variables)]
fn log_for_debugging(msg: &str) {
eprintln!("{}", msg);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oauth_headers() {
let headers = oauth_headers("test-token");
assert!(headers.get("Authorization").is_some());
assert!(headers.get("Content-Type").is_some());
}
}