1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use anyhow::{Result, anyhow};
7use reqwest::Client;
8use serde_json::Value;
9
10use crate::api::http_proxy::resolve_http_proxy_url_for_target;
11use crate::types::{
12 AssistantMessage, AssistantMessageEvent, Model, OnPayloadCallback, OnResponseCallback, ProviderEnv,
13 ProviderResponse, StopReason, StreamOptions,
14};
15use crate::utils::error_body::{error_body_from_response, format_provider_error, normalize_provider_error};
16use crate::utils::event_stream::AssistantMessageEventStream;
17use crate::utils::headers::{has_header, headers_to_record, merge_provider_headers};
18
19pub fn build_http_client(timeout_ms: Option<u64>) -> Result<Client> {
20 build_http_client_for_target(timeout_ms, None, None)
21}
22
23pub fn build_http_client_for_target(
24 timeout_ms: Option<u64>,
25 target_url: Option<&str>,
26 env: Option<&ProviderEnv>,
27) -> Result<Client> {
28 let mut builder = Client::builder();
29 if let Some(ms) = timeout_ms {
30 builder = builder.timeout(std::time::Duration::from_millis(ms));
31 }
32 if let Some(target_url) = target_url
33 && let Some(proxy_url) = resolve_http_proxy_url_for_target(target_url, env)?
34 {
35 let proxy = reqwest::Proxy::all(proxy_url.as_str())?;
36 builder = builder.proxy(proxy);
37 }
38 Ok(builder.build()?)
39}
40
41pub fn get_client_api_key(provider: &str, api_key: Option<&str>, headers: &HashMap<String, String>) -> Result<String> {
42 if let Some(key) = api_key {
43 return Ok(key.to_string());
44 }
45 if has_header(headers, "authorization") || has_header(headers, "cf-aig-authorization") {
46 return Ok("unused".to_string());
47 }
48 Err(anyhow!("No API key for provider: {provider}"))
49}
50
51pub async fn apply_on_payload(callback: Option<&OnPayloadCallback>, payload: Value, model: &Model) -> Value {
52 if let Some(cb) = callback {
53 let m = model.clone();
54 let original = payload.clone();
55 if let Some(next) = cb(payload, m).await {
56 return next;
57 }
58 return original;
59 }
60 payload
61}
62
63pub async fn apply_on_response(callback: Option<&OnResponseCallback>, response: ProviderResponse, model: &Model) {
64 if let Some(cb) = callback {
65 let m = model.clone();
66 cb(response, m).await;
67 }
68}
69
70pub fn merge_model_headers(model: &Model, options: Option<&StreamOptions>) -> HashMap<String, String> {
71 let base = model.headers.clone().unwrap_or_default();
72 merge_provider_headers(&base, options.and_then(|o| o.headers.as_ref()))
73}
74
75pub const REQUEST_ABORTED: &str = "Request aborted";
76
77pub fn is_request_aborted(token: &Option<tokio_util::sync::CancellationToken>) -> bool {
78 token.as_ref().is_some_and(|t| t.is_cancelled())
79}
80
81pub fn request_aborted_error() -> anyhow::Error {
82 anyhow!(REQUEST_ABORTED)
83}
84
85pub fn is_abort_error(error: &anyhow::Error) -> bool {
86 error.to_string() == REQUEST_ABORTED
87}
88
89pub async fn send_with_abort(
90 token: &Option<tokio_util::sync::CancellationToken>,
91 request: reqwest::RequestBuilder,
92) -> Result<reqwest::Response> {
93 if is_request_aborted(token) {
94 return Err(request_aborted_error());
95 }
96 match token {
97 Some(token) => {
98 let token = token.clone();
99 tokio::select! {
100 result = request.send() => result.map_err(Into::into),
101 _ = token.cancelled() => Err(request_aborted_error()),
102 }
103 }
104 None => request.send().await.map_err(Into::into),
105 }
106}
107
108pub fn finish_stream_error(
109 stream: &AssistantMessageEventStream,
110 output: &mut AssistantMessage,
111 error: anyhow::Error,
112 aborted: bool,
113) {
114 output.stop_reason = if aborted {
115 StopReason::Aborted
116 } else {
117 StopReason::Error
118 };
119 output.error_message = Some(format_provider_error(&normalize_provider_error(&error), None));
120 stream.push(AssistantMessageEvent::Error {
121 reason: output.stop_reason,
122 error: output.clone(),
123 });
124 stream.end();
125}
126
127pub async fn check_response_ok(response: reqwest::Response) -> Result<reqwest::Response> {
128 if response.status().is_success() {
129 return Ok(response);
130 }
131 let status = response.status();
132 let body = error_body_from_response(response).await;
133 Err(anyhow!("{status}: {body}"))
134}
135
136pub type StreamTask = Pin<Box<dyn Future<Output = ()> + Send>>;
137
138pub fn spawn_stream_task(fut: impl Future<Output = ()> + Send + 'static) -> StreamTask {
139 Box::pin(async move {
140 tokio::spawn(fut);
141 })
142}
143
144pub fn wrap_on_payload<F>(f: F) -> OnPayloadCallback
145where
146 F: Fn(Value, Model) -> Pin<Box<dyn Future<Output = Option<Value>> + Send>> + Send + Sync + 'static,
147{
148 Arc::new(f)
149}
150
151pub fn wrap_on_response<F>(f: F) -> OnResponseCallback
152where
153 F: Fn(ProviderResponse, Model) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync + 'static,
154{
155 Arc::new(f)
156}
157
158pub async fn invoke_on_response_from_reqwest(
159 callback: Option<&OnResponseCallback>,
160 response: &reqwest::Response,
161 model: &Model,
162) {
163 let provider_response = ProviderResponse {
164 status: response.status().as_u16(),
165 headers: headers_to_record(response.headers()),
166 };
167 apply_on_response(callback, provider_response, model).await;
168}