Skip to main content

codetether_agent/a2a/git_credentials/
request.rs

1//! Control-plane requests for Git credential material.
2//!
3//! The worker asks the A2A server for short-lived Git credentials on demand so
4//! repository operations do not depend on long-lived local secrets.
5//!
6//! # Examples
7//!
8//! ```ignore
9//! let creds = request_git_credentials(client, server, &token, None, "ws-1", "get", &query).await?;
10//! ```
11
12use anyhow::{Context, Result, anyhow};
13use reqwest::{Client, StatusCode};
14use serde::Serialize;
15
16use super::{GitCredentialMaterial, GitCredentialQuery};
17
18#[derive(Debug, Serialize)]
19struct GitCredentialRequestBody<'a> {
20    operation: &'a str,
21    protocol: Option<&'a str>,
22    host: Option<&'a str>,
23    path: Option<&'a str>,
24}
25
26/// Requests short-lived Git credentials from the A2A control plane.
27///
28/// Missing credentials return `Ok(None)` when the server responds with `404`.
29///
30/// # Examples
31///
32/// ```ignore
33/// let creds = request_git_credentials(client, server, &token, None, "ws-1", "get", &query).await?;
34/// ```
35pub async fn request_git_credentials(
36    server: &str,
37    token: &Option<String>,
38    worker_id: Option<&str>,
39    workspace_id: &str,
40    operation: &str,
41    query: &GitCredentialQuery,
42) -> Result<Option<GitCredentialMaterial>> {
43    let client = Client::builder()
44        .timeout(std::time::Duration::from_secs(30))
45        .build()
46        .unwrap_or_default();
47    let mut request = client.post(format!(
48        "{}/v1/agent/workspaces/{workspace_id}/git/credentials",
49        server.trim_end_matches('/')
50    ));
51    if let Some(token) = token {
52        request = request.bearer_auth(token);
53    }
54    if let Some(worker_id) = worker_id.filter(|value| !value.trim().is_empty()) {
55        request = request.header("X-Worker-ID", worker_id);
56    }
57    let response = request
58        .json(&GitCredentialRequestBody {
59            operation,
60            protocol: query.protocol.as_deref(),
61            host: query.host.as_deref(),
62            path: query.path.as_deref(),
63        })
64        .send()
65        .await
66        .context("Failed to request Git credentials from server")?;
67    match response.status() {
68        StatusCode::OK => Ok(Some(
69            response
70                .json::<GitCredentialMaterial>()
71                .await
72                .context("Failed to decode Git credential response")?,
73        )),
74        StatusCode::NOT_FOUND => Ok(None),
75        status => Err(anyhow!(
76            "Git credential request failed with {}: {}",
77            status,
78            response.text().await.unwrap_or_default()
79        )),
80    }
81}