qcli/provider/
endpoint.rs1use std::io::BufReader;
4use std::time::Duration;
5
6use serde_json::Value;
7
8use super::reply::{self, Delta, Reply};
9use super::{Key, ProviderError, error};
10
11const CONNECT: Duration = Duration::from_secs(30);
13
14const FIRST_BYTE: Duration = Duration::from_secs(300);
18
19const API_VERSION: &str = "2023-06-01";
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Endpoint {
25 pub address: String,
28 pub model: String,
30 pub key_header: String,
33 pub key: Key,
35}
36
37impl Endpoint {
38 #[must_use]
40 pub fn url(&self) -> String {
41 format!("{}/v1/messages", self.address.trim().trim_end_matches('/'))
42 }
43
44 pub fn stream(
53 &self,
54 body: &Value,
55 on_delta: &mut dyn FnMut(Delta),
56 cancelled: &dyn Fn() -> bool,
57 ) -> Result<Reply, ProviderError> {
58 let url = self.url();
59 let mut body = body.clone();
60 if let Some(object) = body.as_object_mut() {
61 object.insert("model".to_owned(), Value::String(self.model.clone()));
62 object.insert("stream".to_owned(), Value::Bool(true));
63 }
64 let config = ureq::Agent::config_builder()
65 .timeout_connect(Some(CONNECT))
66 .timeout_recv_response(Some(FIRST_BYTE))
67 .http_status_as_error(false)
70 .build();
71 let agent = ureq::Agent::new_with_config(config);
72 let header = self.key_header.trim();
73 let secret = if header.eq_ignore_ascii_case("authorization") {
74 format!("Bearer {}", self.key.expose())
75 } else {
76 self.key.expose().to_owned()
77 };
78 let unreachable =
81 |error: ureq::Error| ProviderError::Unreachable { url: url.clone(), reason: error.to_string() };
82 let mut answer = agent
83 .post(&url)
84 .header("anthropic-version", API_VERSION)
85 .header("accept", "text/event-stream")
86 .header(header, &secret)
87 .content_type("application/json")
88 .send(body.to_string().as_str())
89 .map_err(unreachable)?;
90 let status = answer.status().as_u16();
91 if !(200..300).contains(&status) {
92 let said = answer.body_mut().read_to_string().unwrap_or_default();
93 return Err(ProviderError::Refused { url, status, said: error::complaint(&said) });
94 }
95 let lines = BufReader::new(answer.into_body().into_reader());
96 reply::read(lines, on_delta, cancelled)
97 }
98}