File size: 4,772 Bytes
854994d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | use codex_client::Request;
use codex_client::RequestCompression;
use codex_client::RetryOn;
use codex_client::RetryPolicy;
use http::Method;
use http::header::HeaderMap;
use std::collections::HashMap;
use std::time::Duration;
use url::Url;
/// High-level retry configuration for a provider.
///
/// This is converted into a `RetryPolicy` used by `codex-client` to drive
/// transport-level retries for both unary and streaming calls.
#[derive(Debug, Clone)]
pub struct RetryConfig {
pub max_attempts: u64,
pub base_delay: Duration,
pub retry_429: bool,
pub retry_5xx: bool,
pub retry_transport: bool,
}
impl RetryConfig {
pub fn to_policy(&self) -> RetryPolicy {
RetryPolicy {
max_attempts: self.max_attempts,
base_delay: self.base_delay,
retry_on: RetryOn {
retry_429: self.retry_429,
retry_5xx: self.retry_5xx,
retry_transport: self.retry_transport,
},
}
}
}
/// HTTP endpoint configuration used to talk to a concrete API deployment.
///
/// Encapsulates base URL, default headers, query params, retry policy, and
/// stream idle timeout, plus helper methods for building requests.
#[derive(Debug, Clone)]
pub struct Provider {
pub name: String,
pub base_url: String,
pub query_params: Option<HashMap<String, String>>,
pub headers: HeaderMap,
pub retry: RetryConfig,
pub stream_idle_timeout: Duration,
}
impl Provider {
pub fn url_for_path(&self, path: &str) -> String {
let base = self.base_url.trim_end_matches('/');
let path = path.trim_start_matches('/');
let mut url = if path.is_empty() {
base.to_string()
} else {
format!("{base}/{path}")
};
if let Some(params) = &self.query_params
&& !params.is_empty()
{
let qs = params
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join("&");
url.push('?');
url.push_str(&qs);
}
url
}
pub fn build_request(&self, method: Method, path: &str) -> Request {
Request {
method,
url: self.url_for_path(path),
headers: self.headers.clone(),
body: None,
compression: RequestCompression::None,
timeout: None,
response_body_limit_bytes: None,
}
}
pub fn websocket_url_for_path(&self, path: &str) -> Result<Url, url::ParseError> {
let mut url = Url::parse(&self.url_for_path(path))?;
let scheme = match url.scheme() {
"http" => "ws",
"https" => "wss",
"ws" | "wss" => return Ok(url),
_ => return Ok(url),
};
let _ = url.set_scheme(scheme);
Ok(url)
}
}
pub fn is_azure_responses_provider(name: &str, base_url: Option<&str>) -> bool {
if name.eq_ignore_ascii_case("azure") {
true
} else if let Some(base_url) = base_url {
matches_azure_responses_base_url(base_url)
} else {
false
}
}
fn matches_azure_responses_base_url(base_url: &str) -> bool {
let base_url = base_url.to_ascii_lowercase();
const AZURE_MARKERS: [&str; 6] = [
"openai.azure.",
"cognitiveservices.azure.",
"aoai.azure.",
"azure-api.",
"azurefd.",
"windows.net/openai",
];
AZURE_MARKERS.iter().any(|marker| base_url.contains(marker))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_azure_responses_base_urls() {
let positive_cases = [
"https://foo.openai.azure.com/openai",
"https://foo.openai.azure.us/openai/deployments/bar",
"https://foo.cognitiveservices.azure.cn/openai",
"https://foo.aoai.azure.com/openai",
"https://foo.openai.azure-api.net/openai",
"https://foo.z01.azurefd.net/",
];
for base_url in positive_cases {
assert!(
is_azure_responses_provider("test", Some(base_url)),
"expected {base_url} to be detected as Azure"
);
}
assert!(is_azure_responses_provider(
"Azure",
Some("https://example.com")
));
let negative_cases = [
"https://api.openai.com/v1",
"https://example.com/openai",
"https://myproxy.azurewebsites.net/openai",
];
for base_url in negative_cases {
assert!(
!is_azure_responses_provider("test", Some(base_url)),
"expected {base_url} not to be detected as Azure"
);
}
}
}
|