qcli/provider/
endpoint.rs1use std::io::{self, BufRead, BufReader};
4use std::sync::mpsc;
5use std::thread;
6use std::time::{Duration, Instant};
7
8use serde_json::Value;
9
10use super::reply::{self, Delta, Reply};
11use super::{Key, ProviderError, error};
12
13const CONNECT: Duration = Duration::from_secs(30);
15
16const FIRST_BYTE: Duration = Duration::from_secs(300);
20
21const BODY_LIMIT: Duration = Duration::from_secs(60 * 60);
24
25const API_VERSION: &str = "2023-06-01";
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Endpoint {
31 pub address: String,
34 pub model: String,
36 pub key_header: String,
39 pub key: Option<Key>,
42 pub silence: Duration,
44}
45
46impl Endpoint {
47 pub const SILENCE: Duration = Duration::from_secs(120);
50
51 #[must_use]
53 pub fn url(&self) -> String {
54 format!("{}/v1/messages", self.address.trim().trim_end_matches('/'))
55 }
56
57 pub fn stream(
66 &self,
67 body: &Value,
68 on_delta: &mut dyn FnMut(Delta),
69 cancelled: &dyn Fn() -> bool,
70 ) -> Result<Reply, ProviderError> {
71 let url = self.url();
72 let mut body = body.clone();
73 if let Some(object) = body.as_object_mut() {
74 object.insert("model".to_owned(), Value::String(self.model.clone()));
75 object.insert("stream".to_owned(), Value::Bool(true));
76 }
77 let config = ureq::Agent::config_builder()
78 .timeout_connect(Some(CONNECT))
79 .timeout_recv_response(Some(FIRST_BYTE))
80 .timeout_recv_body(Some(BODY_LIMIT))
83 .http_status_as_error(false)
86 .build();
87 let agent = ureq::Agent::new_with_config(config);
88 let header = self.key_header.trim();
89 let key = self.key.as_ref().map_or("", Key::expose);
90 let secret =
91 if header.eq_ignore_ascii_case("authorization") { format!("Bearer {key}") } else { key.to_owned() };
92 let unreachable =
95 |error: ureq::Error| ProviderError::Unreachable { url: url.clone(), reason: error.to_string() };
96 let mut request =
97 agent.post(&url).header("anthropic-version", API_VERSION).header("accept", "text/event-stream");
98 if self.key.is_some() {
99 request = request.header(header, &secret);
100 }
101 let mut answer =
102 request.content_type("application/json").send(body.to_string().as_str()).map_err(unreachable)?;
103 let status = answer.status().as_u16();
104 if !(200..300).contains(&status) {
105 let said = answer.body_mut().read_to_string().unwrap_or_default();
106 let refused = ProviderError::Refused { url, status, said: error::complaint(&said) };
107 return Err(refused.without(key));
108 }
109 let lines = Watched::start(BufReader::new(answer.into_body().into_reader()), self.silence, cancelled);
110 reply::read(lines, on_delta, cancelled).map_err(|error| error.without(key))
111 }
112}
113
114const POLL: Duration = Duration::from_millis(100);
116
117struct Watched<'a> {
122 lines: mpsc::Receiver<io::Result<String>>,
123 silence: Duration,
124 cancelled: &'a dyn Fn() -> bool,
125}
126
127impl<'a> Watched<'a> {
128 fn start(reader: impl BufRead + Send + 'static, silence: Duration, cancelled: &'a dyn Fn() -> bool) -> Self {
129 let (sender, lines) = mpsc::sync_channel(64);
132 thread::spawn(move || {
133 for line in reader.lines() {
134 if sender.send(line).is_err() {
135 return;
136 }
137 }
138 });
139 Self { lines, silence, cancelled }
140 }
141}
142
143impl Iterator for Watched<'_> {
144 type Item = io::Result<String>;
145
146 fn next(&mut self) -> Option<Self::Item> {
147 let waiting = Instant::now();
148 loop {
149 if (self.cancelled)() {
150 return None;
151 }
152 let left = self.silence.saturating_sub(waiting.elapsed());
153 if left.is_zero() {
154 let said =
155 format!("the provider sent nothing for {} seconds and was given up on", self.silence.as_secs());
156 return Some(Err(io::Error::new(io::ErrorKind::TimedOut, said)));
157 }
158 match self.lines.recv_timeout(left.min(POLL)) {
159 Ok(line) => return Some(line),
160 Err(mpsc::RecvTimeoutError::Timeout) => {}
161 Err(mpsc::RecvTimeoutError::Disconnected) => return None,
162 }
163 }
164 }
165}