1pub mod config_chain;
8pub mod model_resolve;
9pub mod paths;
10pub mod plugin_protocol;
11pub mod plugin_service;
12pub mod project;
13pub mod protocol;
14pub mod subscription_usage;
15pub mod tool_prompt;
16pub mod types;
17pub mod usage_totals;
18
19pub use types::*;
20pub use usage_totals::UsageTotals;
21
22pub fn write_json_line<T: serde::Serialize>(
28 writer: &mut impl std::io::Write,
29 val: &T,
30) -> Result<()> {
31 let mut line = serde_json::to_string(val).map_err(|e| Error::Io(e.to_string()))?;
32 line.push('\n');
33 writer
34 .write_all(line.as_bytes())
35 .map_err(|e| Error::Io(e.to_string()))?;
36 writer.flush().map_err(|e| Error::Io(e.to_string()))?;
37 Ok(())
38}
39
40pub fn read_json_line<T: serde::de::DeserializeOwned>(
42 reader: &mut impl std::io::BufRead,
43) -> Result<Option<T>> {
44 let mut line = String::new();
45 let n = reader
46 .read_line(&mut line)
47 .map_err(|e| Error::Io(e.to_string()))?;
48 if n == 0 {
49 return Ok(None);
50 }
51 let val = serde_json::from_str(&line).map_err(|e| Error::Parse(e.to_string()))?;
52 Ok(Some(val))
53}
54
55pub async fn write_json_line_async<T: serde::Serialize>(
61 writer: &mut (impl futures::io::AsyncWrite + Unpin),
62 val: &T,
63) -> Result<()> {
64 use futures::io::AsyncWriteExt;
65 let mut line = serde_json::to_string(val).map_err(|e| Error::Io(e.to_string()))?;
66 line.push('\n');
67 writer
68 .write_all(line.as_bytes())
69 .await
70 .map_err(|e| Error::Io(e.to_string()))?;
71 writer.flush().await.map_err(|e| Error::Io(e.to_string()))?;
72 Ok(())
73}
74
75pub async fn read_json_line_async<T: serde::de::DeserializeOwned>(
77 reader: &mut (impl futures::io::AsyncBufRead + Unpin),
78) -> Result<Option<T>> {
79 use futures::io::AsyncBufReadExt;
80 let mut line = String::new();
81 let n = reader
82 .read_line(&mut line)
83 .await
84 .map_err(|e| Error::Io(e.to_string()))?;
85 if n == 0 {
86 return Ok(None);
87 }
88 let val = serde_json::from_str(&line).map_err(|e| Error::Parse(e.to_string()))?;
89 Ok(Some(val))
90}
91
92pub fn truncate_str(s: &str, max_bytes: usize) -> &str {
98 if s.len() <= max_bytes {
99 return s;
100 }
101 let mut end = max_bytes;
102 while end > 0 && !s.is_char_boundary(end) {
103 end -= 1;
104 }
105 &s[..end]
106}
107
108pub fn truncate_str_end(s: &str, max_bytes: usize) -> &str {
111 if s.len() <= max_bytes {
112 return s;
113 }
114 let mut start = s.len() - max_bytes;
115 while start < s.len() && !s.is_char_boundary(start) {
116 start += 1;
117 }
118 &s[start..]
119}
120
121#[derive(Debug, thiserror::Error)]
126pub enum Error {
127 #[error("no provider registered for api: {0}")]
128 NoProvider(String),
129 #[error("no API key for provider: {0}")]
130 NoApiKey(String),
131 #[error("HTTP error: {0}")]
132 Http(String),
133 #[error("HTTP {status}: {message}")]
134 HttpStatus {
135 status: u16,
136 message: String,
137 retry_after: Option<u64>,
139 },
140 #[error("parse error: {0}")]
141 Parse(String),
142 #[error("IO error: {0}")]
143 Io(String),
144 #[error("channel closed")]
145 ChannelClosed,
146 #[error("cancelled")]
147 Cancelled,
148 #[error("provider throttled until {0}")]
149 Throttled(String),
150}
151
152pub type Result<T> = std::result::Result<T, Error>;
153
154#[cfg(test)]
164pub(crate) static TEST_ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());