Skip to main content

codetether_agent/provider/
tenant_keys.rs

1//! Per-task provider registry construction from tenant-scoped claim payloads.
2
3use super::registry::ProviderRegistry;
4use crate::secrets::ProviderSecrets;
5use anyhow::Result;
6use serde::Deserialize;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Deserialize, Default)]
10pub struct TenantProviderKeyPayload {
11    #[serde(default)]
12    pub source: Option<String>,
13    #[serde(default)]
14    pub providers: HashMap<String, ProviderSecrets>,
15}
16
17/// Owned per-task key material. Dropping this value clears the original strings
18/// received from the server before provider instances are dropped.
19#[derive(Debug)]
20pub struct PerTaskProviderKeys {
21    payload: TenantProviderKeyPayload,
22}
23
24impl PerTaskProviderKeys {
25    pub fn from_value(value: &serde_json::Value) -> Result<Option<Self>> {
26        if value.is_null() {
27            return Ok(None);
28        }
29        let payload: TenantProviderKeyPayload = serde_json::from_value(value.clone())?;
30        if payload.providers.is_empty() {
31            Ok(None)
32        } else {
33            Ok(Some(Self { payload }))
34        }
35    }
36
37    pub fn source(&self) -> &str {
38        self.payload.source.as_deref().unwrap_or("byok")
39    }
40
41    pub fn provider_names(&self) -> Vec<String> {
42        let mut names = self.payload.providers.keys().cloned().collect::<Vec<_>>();
43        names.sort();
44        names
45    }
46
47    pub fn build_registry(&self) -> ProviderRegistry {
48        ProviderRegistry::from_provider_secrets_map(&self.payload.providers)
49    }
50}
51
52impl Drop for PerTaskProviderKeys {
53    fn drop(&mut self) {
54        for secrets in self.payload.providers.values_mut() {
55            if let Some(key) = secrets.api_key.as_mut() {
56                unsafe {
57                    key.as_bytes_mut().fill(0);
58                }
59                key.clear();
60            }
61            if let Some(org) = secrets.organization.as_mut() {
62                unsafe {
63                    org.as_bytes_mut().fill(0);
64                }
65                org.clear();
66            }
67            if let Some(headers) = secrets.headers.as_mut() {
68                for value in headers.values_mut() {
69                    unsafe {
70                        value.as_bytes_mut().fill(0);
71                    }
72                    value.clear();
73                }
74                headers.clear();
75            }
76        }
77    }
78}