Skip to content
Clone

clear worksapce

ZhenYi committed
parent 649265f commit 91cbbf1 Browse files

300 files changed

+1876 −16082

This change is very large, so some diffs are not shown. Clone the repository to see the full change.

File tree 300
Deleted agent/llm/provider/openai/config.rs
@@ -1,210 +0,0 @@
1use crate::error::LlmError;
2use crate::provider::LlmProvider;
3use crate::types::{ChatRequest, ChatResponse, ProviderCapabilities, StreamEvent};
4use futures::StreamExt;
5use std::fmt;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::time::Duration;
9use tokio_stream::Stream;
10
11use super::error::map_response_err;
12use super::request::build_request_body;
13use super::response::parse_response;
14use super::stream::sse_to_stream_events;
15use eventsource_stream::Eventsource;
16
17type EventStream = Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>;
18
19#[derive(Clone)]
20pub struct SecretString(String);
21
22impl SecretString {
23 pub fn new(value: impl Into<String>) -> Self {
24 Self(value.into())
25 }
26
27 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32impl fmt::Debug for SecretString {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.write_str("SecretString(***)")
35 }
36}
37
38#[derive(Debug, Clone)]
39pub struct OpenAIConfig {
40 pub api_key: SecretString,
41 pub model: String,
42 pub organization: Option<String>,
43 pub project: Option<String>,
44 pub max_context_tokens: usize,
45 pub max_output_tokens: usize,
46 pub request_timeout: Duration,
47 pub base_url: String,
48}
49
50impl OpenAIConfig {
51 pub fn new(api_key: impl Into<String>) -> Self {
52 Self {
53 api_key: SecretString::new(api_key),
54 model: "gpt-4o-mini".to_string(),
55 organization: None,
56 project: None,
57 max_context_tokens: 128_000,
58 max_output_tokens: 16_384,
59 request_timeout: Duration::from_secs(60),
60 base_url: "https://api.openai.com".to_string(),
61 }
62 }
63
64 pub fn with_model(mut self, model: impl Into<String>) -> Self {
65 self.model = model.into();
66 self
67 }
68
69 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
70 self.base_url = base_url.into();
71 self
72 }
73
74 pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
75 self.organization = Some(organization.into());
76 self
77 }
78
79 pub fn with_project(mut self, project: impl Into<String>) -> Self {
80 self.project = Some(project.into());
81 self
82 }
83
84 pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
85 self.request_timeout = timeout;
86 self
87 }
88
89 pub fn with_max_context_tokens(mut self, n: usize) -> Self {
90 self.max_context_tokens = n;
91 self
92 }
93
94 pub fn with_max_output_tokens(mut self, n: usize) -> Self {
95 self.max_output_tokens = n;
96 self
97 }
98}
99
100#[derive(Clone)]
101pub struct OpenAIProvider {
102 client: reqwest::Client,
103 config: Arc<OpenAIConfig>,
104}
105
106impl OpenAIProvider {
107 pub fn new(config: OpenAIConfig) -> Result<Self, LlmError> {
108 let client = reqwest::Client::builder()
109 .timeout(config.request_timeout)
110 .build()
111 .map_err(|e| LlmError::Config(format!("reqwest client build failed: {e}")))?;
112 Ok(Self {
113 client,
114 config: Arc::new(config),
115 })
116 }
117
118 fn chat_url(&self) -> String {
119 format!("{}/v1/responses", self.config.base_url)
120 }
121}
122
123#[async_trait::async_trait]
124impl LlmProvider for OpenAIProvider {
125 fn provider_id(&self) -> &str {
126 "openai"
127 }
128
129 fn model_id(&self) -> &str {
130 &self.config.model
131 }
132
133 fn capabilities(&self) -> ProviderCapabilities {
134 ProviderCapabilities {
135 streaming: true,
136 tool_calling: true,
137 structured_output: true,
138 vision: true,
139 reasoning: true,
140 embeddings: false,
141 max_context_tokens: self.config.max_context_tokens,
142 max_output_tokens: self.config.max_output_tokens,
143 }
144 }
145
146 async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError> {
147 let body = build_request_body(&self.config.model, &request, false)?;
148 let mut req = self
149 .client
150 .post(self.chat_url())
151 .bearer_auth(self.config.api_key.as_str())
152 .json(&body);
153 if let Some(org) = &self.config.organization {
154 req = req.header("OpenAI-Organization", org);
155 }
156 if let Some(proj) = &self.config.project {
157 req = req.header("OpenAI-Project", proj);
158 }
159 let resp = req
160 .send()
161 .await
162 .map_err(|e| map_response_err(e, "openai"))?;
163 let status = resp.status();
164 let bytes = resp
165 .bytes()
166 .await
167 .map_err(|e| LlmError::Network(format!("openai response body read failed: {e}")))?;
168 if !status.is_success() {
169 return Err(super::error::classify_status(
170 "openai",
171 status.as_u16(),
172 &bytes,
173 ));
174 }
175 parse_response(&bytes)
176 }
177
178 async fn chat_stream(&self, request: ChatRequest) -> Result<EventStream, LlmError> {
179 let body = build_request_body(&self.config.model, &request, true)?;
180 let mut req = self
181 .client
182 .post(self.chat_url())
183 .bearer_auth(self.config.api_key.as_str())
184 .json(&body);
185 if let Some(org) = &self.config.organization {
186 req = req.header("OpenAI-Organization", org);
187 }
188 if let Some(proj) = &self.config.project {
189 req = req.header("OpenAI-Project", proj);
190 }
191 let resp = req
192 .send()
193 .await
194 .map_err(|e| map_response_err(e, "openai"))?;
195 let status = resp.status();
196 if !status.is_success() {
197 let bytes = resp
198 .bytes()
199 .await
200 .map_err(|e| LlmError::Network(format!("openai stream body read failed: {e}")))?;
201 return Err(super::error::classify_status(
202 "openai",
203 status.as_u16(),
204 &bytes,
205 ));
206 }
207 let sse = resp.bytes_stream().eventsource();
208 Ok(Box::pin(sse_to_stream_events(sse).boxed()))
209 }
210}
Deleted agent/llm/provider/openai/error.rs
@@ -1,249 +0,0 @@
1use crate::error::LlmError;
2
3pub fn map_response_err(err: reqwest::Error, provider: &'static str) -> LlmError {
4 if err.is_timeout() {
5 return LlmError::Network(format!("{provider} request timed out: {err}"));
6 }
7 if err.is_connect() {
8 return LlmError::Network(format!("{provider} connect failed: {err}"));
9 }
10 if err.is_request() {
11 return LlmError::Network(format!("{provider} request error: {err}"));
12 }
13 LlmError::Network(format!("{provider} transport error: {err}"))
14}
15
16pub fn classify_status(provider: &'static str, status: u16, body: &[u8]) -> LlmError {
17 if let Some(env) = parse_error_envelope(body) {
18 return classify_from_envelope(provider, status, &env);
19 }
20
21 match status {
22 401 | 403 => LlmError::AuthError {
23 provider: provider.into(),
24 },
25 404 => LlmError::ApiError {
26 provider: provider.into(),
27 status,
28 message: String::from_utf8_lossy(body).into_owned(),
29 },
30 429 => LlmError::RateLimited {
31 provider: provider.into(),
32 retry_after_secs: None,
33 },
34 400 if body_mentions(body, "context_length") => LlmError::ContextLengthExceeded {
35 tokens_used: 0,
36 limit: 0,
37 },
38 s if (500..600).contains(&s) => LlmError::ApiError {
39 provider: provider.into(),
40 status,
41 message: String::from_utf8_lossy(body).into_owned(),
42 },
43 _ => LlmError::ApiError {
44 provider: provider.into(),
45 status,
46 message: String::from_utf8_lossy(body).into_owned(),
47 },
48 }
49}
50
51#[derive(Debug, Default)]
52struct ErrorEnvelope {
53 message: String,
54 kind: String,
55 code: String,
56 param: Option<String>,
57}
58
59fn parse_error_envelope(body: &[u8]) -> Option<ErrorEnvelope> {
60 let v: serde_json::Value = serde_json::from_slice(body).ok()?;
61 let env = v.get("error")?;
62 let mut out = ErrorEnvelope::default();
63 if let Some(msg) = env.get("message").and_then(|m| m.as_str()) {
64 out.message = msg.to_string();
65 }
66 if let Some(t) = env.get("type").and_then(|t| t.as_str()) {
67 out.kind = t.to_string();
68 }
69 if let Some(c) = env.get("code").and_then(|c| c.as_str()) {
70 out.code = c.to_string();
71 }
72 out.param = env.get("param").and_then(|p| p.as_str()).map(String::from);
73 Some(out)
74}
75
76fn classify_from_envelope(provider: &'static str, status: u16, env: &ErrorEnvelope) -> LlmError {
77 let lower_kind = env.kind.to_ascii_lowercase();
78 let lower_code = env.code.to_ascii_lowercase();
79 let lower_msg = env.message.to_ascii_lowercase();
80
81 if matches!(status, 401 | 403)
82 || lower_code == "invalid_api_key"
83 || lower_code == "insufficient_quota"
84 || lower_kind == "authentication_error"
85 {
86 return LlmError::AuthError {
87 provider: provider.into(),
88 };
89 }
90
91 if status == 429
92 || lower_code == "rate_limit_exceeded"
93 || lower_kind == "rate_limit_error"
94 || lower_msg.contains("rate limit")
95 {
96 return LlmError::RateLimited {
97 provider: provider.into(),
98 retry_after_secs: None,
99 };
100 }
101
102 if lower_code == "context_length_exceeded"
103 || lower_msg.contains("context length")
104 || lower_msg.contains("maximum context length")
105 {
106 return LlmError::ContextLengthExceeded {
107 tokens_used: 0,
108 limit: 0,
109 };
110 }
111
112 if lower_code == "content_policy_violation"
113 || lower_msg.contains("content policy")
114 || lower_msg.contains("content_filter")
115 {
116 return LlmError::ContentFiltered {
117 provider: provider.into(),
118 reason: env.message.clone(),
119 };
120 }
121
122 if lower_code == "model_not_found"
123 || lower_msg.contains("model not found")
124 || lower_msg.contains("does not exist")
125 {
126 return LlmError::ModelNotFound {
127 provider: provider.into(),
128 model: env.param.clone().unwrap_or_default(),
129 };
130 }
131
132 LlmError::ApiError {
133 provider: provider.into(),
134 status,
135 message: env.message.clone(),
136 }
137}
138
139fn body_mentions(body: &[u8], needle: &str) -> bool {
140 let s = String::from_utf8_lossy(body);
141 s.contains(needle)
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn envelope(kind: &str, code: &str, message: &str) -> Vec<u8> {
149 serde_json::to_vec(&serde_json::json!({
150 "error": {"type": kind, "code": code, "message": message}
151 }))
152 .unwrap()
153 }
154
155 #[test]
156 fn rate_limit_by_status() {
157 let e = classify_status("openai", 429, b"{}");
158 assert!(matches!(e, LlmError::RateLimited { .. }));
159 }
160
161 #[test]
162 fn auth_by_envelope_code() {
163 let body = envelope("invalid_request_error", "invalid_api_key", "bad key");
164 let e = classify_status("openai", 401, &body);
165 assert!(matches!(e, LlmError::AuthError { .. }));
166 }
167
168 #[test]
169 fn auth_by_insufficient_quota() {
170 let body = envelope("insufficient_quota_error", "insufficient_quota", "no $");
171 let e = classify_status("openai", 429, &body);
172 assert!(matches!(e, LlmError::AuthError { .. }));
173 }
174
175 #[test]
176 fn context_length_via_envelope() {
177 let body = envelope(
178 "invalid_request_error",
179 "context_length_exceeded",
180 "too many tokens",
181 );
182 let e = classify_status("openai", 400, &body);
183 assert!(matches!(e, LlmError::ContextLengthExceeded { .. }));
184 }
185
186 #[test]
187 fn context_length_via_bare_400_body() {
188 let e = classify_status("openai", 400, b"oh no, context_length limit reached");
189 assert!(matches!(e, LlmError::ContextLengthExceeded { .. }));
190 }
191
192 #[test]
193 fn content_filter_recognised() {
194 let body = envelope(
195 "invalid_request_error",
196 "content_policy_violation",
197 "filtered",
198 );
199 let e = classify_status("openai", 400, &body);
200 assert!(matches!(e, LlmError::ContentFiltered { .. }));
201 }
202
203 #[test]
204 fn model_not_found_recognised() {
205 let body = envelope(
206 "invalid_request_error",
207 "model_not_found",
208 "model o99 does not exist",
209 );
210 let e = classify_status("openai", 404, &body);
211 assert!(matches!(e, LlmError::ModelNotFound { .. }));
212 }
213
214 #[test]
215 fn unknown_envelope_falls_back_to_api_error() {
216 let body = envelope("server_error", "internal", "boom");
217 let e = classify_status("openai", 500, &body);
218 match e {
219 LlmError::ApiError {
220 status,
221 message,
222 provider,
223 } => {
224 assert_eq!(status, 500);
225 assert_eq!(message, "boom");
226 assert_eq!(provider, "openai");
227 }
228 other => panic!("expected ApiError, got {other:?}"),
229 }
230 }
231
232 #[test]
233 fn non_json_error_body_yields_api_error() {
234 let e = classify_status("openai", 500, b"<html>oops</html>");
235 assert!(matches!(e, LlmError::ApiError { status: 500, .. }));
236 }
237
238 #[test]
239 fn rate_limit_envelope_with_400_status_still_routes_to_rate_limited() {
240 let body = envelope("rate_limit_error", "rate_limit_exceeded", "slow down");
241 let e = classify_status("openai", 429, &body);
242 assert!(matches!(e, LlmError::RateLimited { .. }));
243 }
244
245 #[test]
246 fn transport_timeout_helper_compiles_and_routes_to_network() {
247 let _ = reqwest::Client::new();
248 }
249}
Deleted agent/llm/provider/openai/mod.rs
@@ -1,7 +0,0 @@
1mod config;
2mod error;
3mod request;
4mod response;
5mod stream;
6
7pub use config::{OpenAIConfig, OpenAIProvider, SecretString};
Deleted agent/llm/provider/openai/request.rs
@@ -1,309 +0,0 @@
1use crate::error::LlmError;
2use crate::types::*;
3use serde_json::{Value, json};
4
5pub fn build_request_body(
6 model: &str,
7 request: &ChatRequest,
8 stream: bool,
9) -> Result<Value, LlmError> {
10 let effective_model = request.model_override.as_deref().unwrap_or(model);
11
12 let mut input: Vec<Value> = Vec::new();
13 let mut instructions: Option<String> = None;
14
15 for msg in &request.messages {
16 match msg.role {
17 MessageRole::System => {
18 let text = msg.text_content();
19 if !text.is_empty() {
20 instructions = Some(match instructions {
21 Some(existing) => format!("{existing}\n{text}"),
22 None => text,
23 });
24 }
25 }
26 MessageRole::User => {
27 let content = encode_content(&msg.content)?;
28 input.push(json!({ "role": "user", "content": content }));
29 }
30 MessageRole::Assistant => {
31 let mut item = json!({ "role": "assistant" });
32 let text = msg.text_content();
33 if !text.is_empty() {
34 item.as_object_mut().unwrap().insert(
35 "content".into(),
36 json!([{ "type": "output_text", "text": text }]),
37 );
38 }
39 if !msg.tool_calls.is_empty() {
40 let calls: Vec<Value> = msg
41 .tool_calls
42 .iter()
43 .map(|tc| {
44 json!({
45 "type": "function_call",
46 "call_id": tc.id,
47 "name": tc.name,
48 "arguments": serde_json::to_string(&tc.arguments)
49 .unwrap_or_else(|_| "{}".into()),
50 })
51 })
52 .collect();
53 item.as_object_mut()
54 .unwrap()
55 .insert("tool_calls".into(), Value::Array(calls));
56 }
57 input.push(item);
58 }
59 MessageRole::Tool => {
60 let id = msg.tool_call_id.as_deref().ok_or_else(|| {
61 LlmError::Serialization(
62 serde_json::from_str::<()>("\"tool message requires tool_call_id\"")
63 .unwrap_err(),
64 )
65 })?;
66 input.push(json!({
67 "type": "function_call_output",
68 "call_id": id,
69 "output": msg.text_content(),
70 }));
71 }
72 }
73 }
74
75 let mut body = json!({
76 "model": effective_model,
77 "input": input,
78 });
79
80 if let Some(inst) = instructions {
81 body.as_object_mut()
82 .unwrap()
83 .insert("instructions".into(), Value::String(inst));
84 }
85
86 if let Some(max_tokens) = request.max_tokens {
87 body.as_object_mut()
88 .unwrap()
89 .insert("max_output_tokens".into(), Value::from(max_tokens));
90 }
91 if let Some(t) = request.temperature {
92 body.as_object_mut()
93 .unwrap()
94 .insert("temperature".into(), Value::from(t));
95 }
96 if let Some(p) = request.top_p {
97 body.as_object_mut()
98 .unwrap()
99 .insert("top_p".into(), Value::from(p));
100 }
101 if !request.stop_sequences.is_empty() {
102 body.as_object_mut()
103 .unwrap()
104 .insert("truncation".into(), Value::String("auto".into()));
105 }
106
107 if !request.tools.is_empty() {
108 let tools: Vec<Value> = request
109 .tools
110 .iter()
111 .map(|t| {
112 json!({
113 "type": "function",
114 "name": t.name,
115 "description": t.description,
116 "parameters": t.parameters,
117 })
118 })
119 .collect();
120 body.as_object_mut()
121 .unwrap()
122 .insert("tools".into(), Value::Array(tools));
123 }
124
125 if let Some(format) = request.response_format {
126 let text_format = match format {
127 ResponseFormat::Text => json!({ "type": "text" }),
128 ResponseFormat::Json => json!({ "type": "json_object" }),
129 };
130 body.as_object_mut()
131 .unwrap()
132 .insert("text".into(), text_format);
133 }
134
135 if let Some(opts) = &request.provider_options
136 && let Some(obj) = opts.as_object()
137 {
138 for (k, v) in obj {
139 body.as_object_mut().unwrap().insert(k.clone(), v.clone());
140 }
141 }
142
143 if stream {
144 body.as_object_mut()
145 .unwrap()
146 .insert("stream".into(), Value::Bool(true));
147 }
148
149 Ok(body)
150}
151
152fn encode_content(content: &MessageContent) -> Result<Value, LlmError> {
153 match content {
154 MessageContent::Text(s) => Ok(json!([{ "type": "input_text", "text": s }])),
155 MessageContent::Parts(parts) => {
156 let items: Result<Vec<Value>, LlmError> = parts
157 .iter()
158 .map(|p| match p {
159 ContentPart::Text(s) => Ok(json!({ "type": "input_text", "text": s })),
160 ContentPart::Image { url, detail } => Ok(json!({
161 "type": "input_image",
162 "image_url": url,
163 "detail": match detail {
164 ImageDetail::Low => "low",
165 ImageDetail::High => "high",
166 ImageDetail::Auto => "auto",
167 },
168 })),
169 })
170 .collect();
171 Ok(Value::Array(items?))
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use serde_json::json;
180
181 #[test]
182 fn user_text_becomes_input_text() {
183 let body = build_request_body(
184 "gpt-4o-mini",
185 &ChatRequest::new(vec![ChatMessage::user("hi")]),
186 false,
187 )
188 .unwrap();
189 assert_eq!(body["model"], "gpt-4o-mini");
190 assert_eq!(body["input"][0]["role"], "user");
191 assert_eq!(body["input"][0]["content"][0]["type"], "input_text");
192 assert_eq!(body["input"][0]["content"][0]["text"], "hi");
193 }
194
195 #[test]
196 fn system_messages_become_instructions() {
197 let req = ChatRequest::new(vec![
198 ChatMessage::system("be terse"),
199 ChatMessage::system("use bullet points"),
200 ChatMessage::user("hi"),
201 ]);
202 let body = build_request_body("m", &req, false).unwrap();
203 let instructions = body["instructions"].as_str().unwrap();
204 assert!(instructions.contains("be terse"));
205 assert!(instructions.contains("use bullet points"));
206 }
207
208 #[test]
209 fn image_with_detail_becomes_input_image() {
210 let msg = ChatMessage {
211 role: MessageRole::User,
212 content: MessageContent::Parts(vec![ContentPart::Image {
213 url: "https://example.invalid/x.png".into(),
214 detail: ImageDetail::High,
215 }]),
216 name: None,
217 tool_calls: Vec::new(),
218 tool_call_id: None,
219 };
220 let body = build_request_body("m", &ChatRequest::new(vec![msg]), false).unwrap();
221 let item = &body["input"][0]["content"][0];
222 assert_eq!(item["type"], "input_image");
223 assert_eq!(item["detail"], "high");
224 }
225
226 #[test]
227 fn assistant_tool_calls_become_function_call_items() {
228 let mut msg = ChatMessage::assistant("");
229 msg.tool_calls = vec![ToolCall {
230 id: "c1".into(),
231 name: "search".into(),
232 arguments: json!({ "q": "rust" }),
233 }];
234 let body = build_request_body("m", &ChatRequest::new(vec![msg]), false).unwrap();
235 let item = &body["input"][0];
236 assert_eq!(item["role"], "assistant");
237 assert_eq!(item["tool_calls"][0]["type"], "function_call");
238 assert_eq!(item["tool_calls"][0]["call_id"], "c1");
239 assert_eq!(item["tool_calls"][0]["name"], "search");
240 }
241
242 #[test]
243 fn tool_messages_become_function_call_output() {
244 let body = build_request_body(
245 "m",
246 &ChatRequest::new(vec![ChatMessage::tool("c1", "42")]),
247 false,
248 )
249 .unwrap();
250 assert_eq!(body["input"][0]["type"], "function_call_output");
251 assert_eq!(body["input"][0]["call_id"], "c1");
252 assert_eq!(body["input"][0]["output"], "42");
253 }
254
255 #[test]
256 fn tool_call_id_missing_yields_error() {
257 let msg = ChatMessage {
258 role: MessageRole::Tool,
259 content: MessageContent::Text("x".into()),
260 name: None,
261 tool_calls: Vec::new(),
262 tool_call_id: None,
263 };
264 assert!(build_request_body("m", &ChatRequest::new(vec![msg]), false).is_err());
265 }
266
267 #[test]
268 fn tools_become_function_tool_entries() {
269 let req =
270 ChatRequest::new(vec![ChatMessage::user("hi")]).with_tools(vec![ToolDefinition {
271 name: "search".into(),
272 description: "Search".into(),
273 parameters: json!({"type": "object"}),
274 concurrency: ConcurrencyPolicy::default(),
275 }]);
276 let body = build_request_body("m", &req, false).unwrap();
277 assert_eq!(body["tools"][0]["type"], "function");
278 assert_eq!(body["tools"][0]["name"], "search");
279 }
280
281 #[test]
282 fn json_response_format_sets_text_field() {
283 let req = ChatRequest::new(vec![ChatMessage::user("hi")])
284 .with_response_format(ResponseFormat::Json);
285 let body = build_request_body("m", &req, false).unwrap();
286 assert_eq!(body["text"]["type"], "json_object");
287 }
288
289 #[test]
290 fn stream_flag_is_set_when_requested() {
291 let body = build_request_body("m", &ChatRequest::new(vec![]), true).unwrap();
292 assert_eq!(body["stream"], true);
293 }
294
295 #[test]
296 fn model_override_wins_over_configured_model() {
297 let req = ChatRequest::new(vec![]).with_model_override("o3");
298 let body = build_request_body("gpt-4o-mini", &req, false).unwrap();
299 assert_eq!(body["model"], "o3");
300 }
301
302 #[test]
303 fn provider_options_are_merged_into_body() {
304 let mut req = ChatRequest::new(vec![]);
305 req.provider_options = Some(json!({ "prompt_cache_key": "session-1" }));
306 let body = build_request_body("m", &req, false).unwrap();
307 assert_eq!(body["prompt_cache_key"], "session-1");
308 }
309}
Deleted agent/llm/provider/openai/response.rs
@@ -1,248 +0,0 @@
1use crate::error::LlmError;
2use crate::token::TokenUsageStats;
3use crate::types::*;
4use serde_json::Value;
5
6pub fn parse_response(bytes: &[u8]) -> Result<ChatResponse, LlmError> {
7 let v: Value = serde_json::from_slice(bytes).map_err(LlmError::Serialization)?;
8
9 let provider = "openai".to_string();
10 let model = v
11 .get("model")
12 .and_then(|m| m.as_str())
13 .unwrap_or("unknown")
14 .to_string();
15
16 let mut content = String::new();
17 let mut tool_calls: Vec<ToolCall> = Vec::new();
18
19 if let Some(items) = v.get("output").and_then(|o| o.as_array()) {
20 for item in items {
21 match item.get("type").and_then(|t| t.as_str()) {
22 Some("message") => {
23 if let Some(parts) = item.get("content").and_then(|c| c.as_array()) {
24 for part in parts {
25 if part.get("type").and_then(|t| t.as_str()) == Some("output_text")
26 && let Some(text) = part.get("text").and_then(|t| t.as_str())
27 {
28 if !content.is_empty() {
29 content.push('\n');
30 }
31 content.push_str(text);
32 }
33 }
34 }
35 }
36 Some("function_call") => {
37 let id = item
38 .get("call_id")
39 .and_then(|s| s.as_str())
40 .unwrap_or("")
41 .to_string();
42 let name = item
43 .get("name")
44 .and_then(|s| s.as_str())
45 .unwrap_or("")
46 .to_string();
47 let arguments = item
48 .get("arguments")
49 .and_then(|a| {
50 if a.is_string() {
51 serde_json::from_str(a.as_str().unwrap_or("{}")).ok()
52 } else {
53 Some(a.clone())
54 }
55 })
56 .unwrap_or_else(|| Value::Object(Default::default()));
57 tool_calls.push(ToolCall {
58 id,
59 name,
60 arguments,
61 });
62 }
63 _ => {}
64 }
65 }
66 }
67
68 let finish_reason = finish_reason_from_status(&v, !tool_calls.is_empty());
69
70 let usage = parse_usage(v.get("usage"));
71
72 let mut resp = ChatResponse::new(content, provider, model).with_usage(usage);
73 resp.finish_reason = finish_reason;
74 if !tool_calls.is_empty() {
75 resp.tool_calls = tool_calls;
76 if v.get("status").is_none() {
77 resp.finish_reason = FinishReason::ToolUse;
78 }
79 }
80 Ok(resp)
81}
82
83fn finish_reason_from_status(v: &Value, has_tool_calls: bool) -> FinishReason {
84 if let Some(status) = v.get("status").and_then(|s| s.as_str()) {
85 match status {
86 "completed" => {
87 if has_tool_calls {
88 FinishReason::ToolUse
89 } else {
90 FinishReason::Stop
91 }
92 }
93 "incomplete" => {
94 if let Some(reason) = v
95 .get("incomplete_details")
96 .and_then(|d| d.get("reason"))
97 .and_then(|r| r.as_str())
98 {
99 match reason {
100 "max_output_tokens" => FinishReason::MaxTokens,
101 "content_filter" => FinishReason::ContentFilter,
102 _ => FinishReason::Error,
103 }
104 } else {
105 FinishReason::Error
106 }
107 }
108 "failed" => FinishReason::Error,
109 _ => FinishReason::Error,
110 }
111 } else if has_tool_calls {
112 FinishReason::ToolUse
113 } else {
114 FinishReason::Stop
115 }
116}
117
118fn parse_usage(v: Option<&Value>) -> TokenUsageStats {
119 let Some(v) = v else {
120 return TokenUsageStats::default();
121 };
122 let input = v.get("input_tokens").and_then(|t| t.as_u64()).unwrap_or(0) as usize;
123 let output = v.get("output_tokens").and_then(|t| t.as_u64()).unwrap_or(0) as usize;
124 let reasoning = v
125 .get("output_tokens_details")
126 .and_then(|d| d.get("reasoning_tokens"))
127 .and_then(|t| t.as_u64())
128 .map(|n| n as usize);
129 let mut stats = TokenUsageStats::new(input, output);
130 if let Some(r) = reasoning {
131 stats = stats.with_reasoning(r);
132 }
133 stats
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use serde_json::json;
140
141 #[test]
142 fn parses_simple_text_completion() {
143 let body = json!({
144 "model": "gpt-4o-mini",
145 "status": "completed",
146 "output": [{
147 "type": "message",
148 "role": "assistant",
149 "content": [{"type": "output_text", "text": "hello"}]
150 }],
151 "usage": {"input_tokens": 3, "output_tokens": 1}
152 });
153 let bytes = serde_json::to_vec(&body).unwrap();
154 let resp = parse_response(&bytes).unwrap();
155 assert_eq!(resp.content, "hello");
156 assert_eq!(resp.finish_reason, FinishReason::Stop);
157 assert_eq!(resp.usage.input_tokens, 3);
158 assert_eq!(resp.usage.output_tokens, 1);
159 assert_eq!(resp.model, "gpt-4o-mini");
160 assert_eq!(resp.provider, "openai");
161 }
162
163 #[test]
164 fn parses_function_call() {
165 let body = json!({
166 "model": "gpt-4o-mini",
167 "status": "completed",
168 "output": [
169 {"type": "message", "role": "assistant",
170 "content": [{"type": "output_text", "text": "calling"}]},
171 {"type": "function_call", "call_id": "c1", "name": "search",
172 "arguments": "{\"q\":\"rust\"}"}
173 ]
174 });
175 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
176 assert_eq!(resp.tool_calls.len(), 1);
177 assert_eq!(resp.tool_calls[0].name, "search");
178 assert_eq!(resp.tool_calls[0].id, "c1");
179 assert_eq!(resp.tool_calls[0].arguments, json!({"q": "rust"}));
180 assert_eq!(resp.finish_reason, FinishReason::ToolUse);
181 }
182
183 #[test]
184 fn incomplete_max_tokens_maps_to_max_tokens() {
185 let body = json!({
186 "model": "m",
187 "status": "incomplete",
188 "incomplete_details": {"reason": "max_output_tokens"},
189 "output": []
190 });
191 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
192 assert_eq!(resp.finish_reason, FinishReason::MaxTokens);
193 }
194
195 #[test]
196 fn incomplete_content_filter_maps_to_content_filter() {
197 let body = json!({
198 "model": "m",
199 "status": "incomplete",
200 "incomplete_details": {"reason": "content_filter"},
201 "output": []
202 });
203 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
204 assert_eq!(resp.finish_reason, FinishReason::ContentFilter);
205 }
206
207 #[test]
208 fn reasoning_tokens_are_picked_up() {
209 let body = json!({
210 "model": "m",
211 "status": "completed",
212 "output": [{"type": "message", "role": "assistant",
213 "content": [{"type": "output_text", "text": "x"}]}],
214 "usage": {"input_tokens": 1, "output_tokens": 2,
215 "output_tokens_details": {"reasoning_tokens": 5}}
216 });
217 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
218 assert_eq!(resp.usage.reasoning_tokens, Some(5));
219 }
220
221 #[test]
222 fn empty_output_array_is_tolerated() {
223 let body = json!({"model": "m", "status": "completed", "output": []});
224 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
225 assert_eq!(resp.content, "");
226 assert_eq!(resp.finish_reason, FinishReason::Stop);
227 }
228
229 #[test]
230 fn malformed_json_returns_serialization_error() {
231 let err = parse_response(b"not-json").unwrap_err();
232 assert!(matches!(err, LlmError::Serialization(_)));
233 }
234
235 #[test]
236 fn function_call_with_object_arguments_is_kept_as_object() {
237 let body = json!({
238 "model": "m",
239 "status": "completed",
240 "output": [
241 {"type": "function_call", "call_id": "c1", "name": "search",
242 "arguments": {"q": "rust"}}
243 ]
244 });
245 let resp = parse_response(&serde_json::to_vec(&body).unwrap()).unwrap();
246 assert_eq!(resp.tool_calls[0].arguments, json!({"q": "rust"}));
247 }
248}
Deleted agent/llm/provider/openai/stream.rs
@@ -1,259 +0,0 @@
1use crate::error::LlmError;
2use crate::token::TokenUsageStats;
3use crate::types::*;
4use async_stream::try_stream;
5use futures::{Stream, StreamExt};
6use serde_json::Value;
7
8pub fn parse_event_data(data: &str) -> Result<Vec<StreamEvent>, LlmError> {
9 if data.trim() == "[DONE]" {
10 return Ok(vec![StreamEvent::Done {
11 finish_reason: FinishReason::Stop,
12 }]);
13 }
14 let v: Value = serde_json::from_str(data)
15 .map_err(|e| LlmError::Stream(format!("openai sse: bad json: {e}")))?;
16 let ty = v.get("type").and_then(|t| t.as_str()).unwrap_or("");
17 let mut out = Vec::new();
18 match ty {
19 "response.output_text.delta" => {
20 if let Some(d) = v.get("delta").and_then(|d| d.as_str()) {
21 out.push(StreamEvent::TextDelta {
22 delta: d.to_string(),
23 });
24 }
25 }
26 "response.reasoning_summary_text.delta"
27 | "response.reasoning_text.delta"
28 | "response.reasoning.delta" => {
29 if let Some(d) = v.get("delta").and_then(|d| d.as_str()) {
30 out.push(StreamEvent::ReasoningDelta {
31 delta: d.to_string(),
32 });
33 }
34 }
35 "response.function_call_arguments.delta" => {
36 let call_id = v
37 .get("item_id")
38 .and_then(|s| s.as_str())
39 .or_else(|| v.get("call_id").and_then(|s| s.as_str()))
40 .unwrap_or("")
41 .to_string();
42 let name = v.get("name").and_then(|s| s.as_str()).map(String::from);
43 let delta = v
44 .get("delta")
45 .and_then(|d| d.as_str())
46 .unwrap_or("")
47 .to_string();
48 out.push(StreamEvent::ToolCallDelta {
49 index: v
50 .get("output_index")
51 .and_then(|i| i.as_u64())
52 .map(|n| n as usize),
53 id: call_id,
54 name,
55 args_delta: delta,
56 });
57 }
58 "response.output_item.added" | "response.output_item.done" => {
59 if let Some(item) = v.get("item")
60 && item.get("type").and_then(|t| t.as_str()) == Some("function_call")
61 {
62 let call_id = item
63 .get("call_id")
64 .and_then(|s| s.as_str())
65 .or_else(|| item.get("id").and_then(|s| s.as_str()))
66 .unwrap_or("")
67 .to_string();
68 let name = item.get("name").and_then(|s| s.as_str()).map(String::from);
69 let args = item
70 .get("arguments")
71 .and_then(|a| a.as_str())
72 .unwrap_or("")
73 .to_string();
74 if !call_id.is_empty() && !args.is_empty() {
75 out.push(StreamEvent::ToolCallDelta {
76 index: v
77 .get("output_index")
78 .and_then(|i| i.as_u64())
79 .map(|n| n as usize),
80 id: call_id,
81 name,
82 args_delta: args,
83 });
84 }
85 }
86 }
87 "response.completed" | "response.done" => {
88 if let Some(resp) = v.get("response")
89 && let Some(usage) = resp.get("usage")
90 {
91 out.push(StreamEvent::Usage(parse_usage(usage)));
92 }
93 out.push(StreamEvent::Done {
94 finish_reason: FinishReason::Stop,
95 });
96 }
97 "response.incomplete" => {
98 out.push(StreamEvent::Done {
99 finish_reason: FinishReason::Error,
100 });
101 }
102 _ => {}
103 }
104 Ok(out)
105}
106
107fn parse_usage(v: &Value) -> TokenUsageStats {
108 let input = v.get("input_tokens").and_then(|t| t.as_u64()).unwrap_or(0) as usize;
109 let output = v.get("output_tokens").and_then(|t| t.as_u64()).unwrap_or(0) as usize;
110 let reasoning = v
111 .get("output_tokens_details")
112 .and_then(|d| d.get("reasoning_tokens"))
113 .and_then(|t| t.as_u64())
114 .map(|n| n as usize);
115 let mut stats = TokenUsageStats::new(input, output);
116 if let Some(r) = reasoning {
117 stats = stats.with_reasoning(r);
118 }
119 stats
120}
121
122pub fn sse_to_stream_events<E>(
123 mut events: eventsource_stream::EventStream<
124 impl futures::Stream<Item = Result<bytes::Bytes, E>> + Unpin + Send,
125 >,
126) -> impl Stream<Item = Result<StreamEvent, LlmError>>
127where
128 E: std::fmt::Display,
129{
130 try_stream! {
131 while let Some(event) = events.next().await {
132 let event = event.map_err(|e| {
133 LlmError::Network(format!("openai sse transport error: {e}"))
134 })?;
135 let events = parse_event_data(&event.data)?;
136 for ev in events {
137 yield ev;
138 }
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use serde_json::json;
147
148 #[test]
149 fn done_sentinel_yields_stop_event() {
150 let evs = parse_event_data("[DONE]").unwrap();
151 assert_eq!(
152 evs,
153 vec![StreamEvent::Done {
154 finish_reason: FinishReason::Stop
155 }]
156 );
157 }
158
159 #[test]
160 fn text_delta_yields_text_delta() {
161 let data = json!({"type": "response.output_text.delta", "delta": "hi"}).to_string();
162 let evs = parse_event_data(&data).unwrap();
163 assert!(matches!(&evs[0], StreamEvent::TextDelta { delta } if delta == "hi"));
164 }
165
166 #[test]
167 fn reasoning_delta_yields_reasoning_delta() {
168 let data = json!({"type": "response.reasoning_text.delta", "delta": "hmm"}).to_string();
169 let evs = parse_event_data(&data).unwrap();
170 assert!(matches!(&evs[0], StreamEvent::ReasoningDelta { delta } if delta == "hmm"));
171 }
172
173 #[test]
174 fn function_call_args_delta_yields_tool_call_delta() {
175 let data = json!({
176 "type": "response.function_call_arguments.delta",
177 "item_id": "c1",
178 "output_index": 0,
179 "delta": "{\"q\":"
180 })
181 .to_string();
182 let evs = parse_event_data(&data).unwrap();
183 match &evs[0] {
184 StreamEvent::ToolCallDelta {
185 index,
186 id,
187 name,
188 args_delta,
189 } => {
190 assert_eq!(*index, Some(0));
191 assert_eq!(id, "c1");
192 assert!(name.is_none());
193 assert_eq!(args_delta, "{\"q\":");
194 }
195 other => panic!("unexpected: {other:?}"),
196 }
197 }
198
199 #[test]
200 fn output_item_added_seeds_tool_call() {
201 let data = json!({
202 "type": "response.output_item.added",
203 "output_index": 0,
204 "item": {
205 "type": "function_call",
206 "call_id": "c1",
207 "name": "search",
208 "arguments": ""
209 }
210 })
211 .to_string();
212 let evs = parse_event_data(&data).unwrap();
213 assert!(evs.is_empty());
214 }
215
216 #[test]
217 fn completed_event_yields_usage_and_done() {
218 let data = json!({
219 "type": "response.completed",
220 "response": {
221 "usage": {"input_tokens": 3, "output_tokens": 1}
222 }
223 })
224 .to_string();
225 let evs = parse_event_data(&data).unwrap();
226 assert!(matches!(&evs[0], StreamEvent::Usage(_)));
227 assert!(matches!(
228 &evs[1],
229 StreamEvent::Done {
230 finish_reason: FinishReason::Stop
231 }
232 ));
233 }
234
235 #[test]
236 fn incomplete_event_yields_error_done() {
237 let data = json!({"type": "response.incomplete"}).to_string();
238 let evs = parse_event_data(&data).unwrap();
239 assert!(matches!(
240 &evs[0],
241 StreamEvent::Done {
242 finish_reason: FinishReason::Error
243 }
244 ));
245 }
246
247 #[test]
248 fn unknown_event_type_is_ignored() {
249 let data = json!({"type": "response.created"}).to_string();
250 let evs = parse_event_data(&data).unwrap();
251 assert!(evs.is_empty());
252 }
253
254 #[test]
255 fn malformed_json_returns_stream_error() {
256 let err = parse_event_data("not-json").unwrap_err();
257 assert!(matches!(err, LlmError::Stream(_)));
258 }
259}
Deleted agent/llm/provider/mod.rs
@@ -1,161 +0,0 @@
1use crate::error::LlmError;
2use crate::token::{
3 IMAGE_TOKENS_HIGH, IMAGE_TOKENS_LOW, MESSAGE_OVERHEAD_TOKENS, estimate_json_tokens,
4 estimate_text_tokens,
5};
6use crate::types::*;
7use std::pin::Pin;
8use tokio_stream::Stream;
9
10pub mod openai;
11
12#[async_trait::async_trait]
13pub trait LlmProvider: Send + Sync + 'static {
14 fn provider_id(&self) -> &str;
15
16 fn model_id(&self) -> &str;
17
18 fn capabilities(&self) -> ProviderCapabilities;
19
20 async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError>;
21
22 async fn chat_stream(
23 &self,
24 request: ChatRequest,
25 ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError>;
26
27 fn count_tokens(&self, request: &ChatRequest) -> usize {
28 let messages: usize = request
29 .messages
30 .iter()
31 .map(|m| {
32 let content = match &m.content {
33 MessageContent::Text(s) => estimate_text_tokens(s),
34 MessageContent::Parts(parts) => parts
35 .iter()
36 .map(|p| match p {
37 ContentPart::Text(s) => estimate_text_tokens(s),
38 ContentPart::Image { detail, .. } => match detail {
39 ImageDetail::Low => IMAGE_TOKENS_LOW,
40 ImageDetail::High | ImageDetail::Auto => IMAGE_TOKENS_HIGH,
41 },
42 })
43 .sum(),
44 };
45 let tool_calls: usize = m
46 .tool_calls
47 .iter()
48 .map(|tc| estimate_text_tokens(&tc.name) + estimate_json_tokens(&tc.arguments))
49 .sum();
50 let name = m.name.as_deref().map_or(0, estimate_text_tokens);
51 content + tool_calls + name + MESSAGE_OVERHEAD_TOKENS
52 })
53 .sum();
54
55 let tools: usize = request
56 .tools
57 .iter()
58 .map(|t| {
59 estimate_text_tokens(&t.name)
60 + estimate_text_tokens(&t.description)
61 + estimate_json_tokens(&t.parameters)
62 })
63 .sum();
64
65 messages + tools
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::error::LlmError;
73 use serde_json::json;
74
75 struct Stub;
76
77 #[async_trait::async_trait]
78 impl LlmProvider for Stub {
79 fn provider_id(&self) -> &str {
80 "stub"
81 }
82 fn model_id(&self) -> &str {
83 "stub-1"
84 }
85 fn capabilities(&self) -> ProviderCapabilities {
86 ProviderCapabilities::default()
87 }
88 async fn chat(&self, _: ChatRequest) -> Result<ChatResponse, LlmError> {
89 Err(LlmError::Provider("stub".into()))
90 }
91 async fn chat_stream(
92 &self,
93 _: ChatRequest,
94 ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError>
95 {
96 Err(LlmError::Provider("stub".into()))
97 }
98 }
99
100 fn user(text: &str) -> ChatMessage {
101 ChatMessage::user(text)
102 }
103
104 #[test]
105 fn tool_schemas_are_counted() {
106 let base = ChatRequest::new(vec![user("hi")]);
107 let with_tool = ChatRequest::new(vec![user("hi")]).with_tools(vec![ToolDefinition {
108 name: "search".into(),
109 description: "Search the web for a query string".into(),
110 parameters: json!({
111 "type": "object",
112 "properties": { "query": { "type": "string" } },
113 "required": ["query"]
114 }),
115 concurrency: ConcurrencyPolicy::default(),
116 }]);
117 assert!(
118 Stub.count_tokens(&with_tool) > Stub.count_tokens(&base),
119 "tool definitions must contribute to the input estimate"
120 );
121 }
122
123 #[test]
124 fn image_detail_changes_the_estimate() {
125 let img = |detail| {
126 ChatRequest::new(vec![ChatMessage {
127 role: MessageRole::User,
128 content: MessageContent::Parts(vec![ContentPart::Image {
129 url: "https://example.invalid/a.png".into(),
130 detail,
131 }]),
132 name: None,
133 tool_calls: Vec::new(),
134 tool_call_id: None,
135 }])
136 };
137 let low = Stub.count_tokens(&img(ImageDetail::Low));
138 let high = Stub.count_tokens(&img(ImageDetail::High));
139 assert!(high > low, "high detail must not be priced as low detail");
140 }
141
142 #[test]
143 fn cjk_is_not_inflated_by_byte_counting() {
144 let zh = "这是一段中文测试文本内容";
145 let req = ChatRequest::new(vec![user(zh)]);
146 assert_eq!(Stub.count_tokens(&req), 12 + MESSAGE_OVERHEAD_TOKENS);
147 }
148
149 #[test]
150 fn tool_call_arguments_are_counted() {
151 let mut msg = ChatMessage::assistant("");
152 msg.tool_calls = vec![ToolCall {
153 id: "c1".into(),
154 name: "search".into(),
155 arguments: json!({ "query": "a fairly long search query here" }),
156 }];
157 let with_call = ChatRequest::new(vec![msg]);
158 let without = ChatRequest::new(vec![ChatMessage::assistant("")]);
159 assert!(Stub.count_tokens(&with_call) > Stub.count_tokens(&without));
160 }
161}
Deleted agent/llm/tests/openai_provider.rs
@@ -1,233 +0,0 @@
1use futures::StreamExt;
2use llm::error::LlmError;
3use llm::provider::LlmProvider;
4use llm::provider::openai::{OpenAIConfig, OpenAIProvider};
5use llm::types::*;
6use serde_json::json;
7use wiremock::matchers::{method, path};
8use wiremock::{Mock, MockServer, ResponseTemplate};
9
10fn provider_for(base_url: &str) -> OpenAIProvider {
11 OpenAIProvider::new(
12 OpenAIConfig::new("test-key")
13 .with_model("gpt-4o-mini")
14 .with_base_url(base_url)
15 .with_request_timeout(std::time::Duration::from_secs(5)),
16 )
17 .expect("client builds")
18}
19
20#[tokio::test]
21async fn chat_decodes_a_text_completion() {
22 let server = MockServer::start().await;
23 let body = json!({
24 "id": "resp_1",
25 "model": "gpt-4o-mini",
26 "status": "completed",
27 "output": [{
28 "type": "message",
29 "role": "assistant",
30 "content": [{"type": "output_text", "text": "hello"}]
31 }],
32 "usage": {"input_tokens": 3, "output_tokens": 1}
33 });
34 Mock::given(method("POST"))
35 .and(path("/v1/responses"))
36 .respond_with(ResponseTemplate::new(200).set_body_json(body))
37 .mount(&server)
38 .await;
39
40 let provider = provider_for(&server.uri());
41 let resp = provider
42 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]))
43 .await
44 .unwrap();
45 assert_eq!(resp.content, "hello");
46 assert_eq!(resp.finish_reason, FinishReason::Stop);
47 assert_eq!(resp.usage.input_tokens, 3);
48}
49
50#[tokio::test]
51async fn chat_routes_a_429_envelope_to_rate_limited() {
52 let server = MockServer::start().await;
53 Mock::given(method("POST"))
54 .and(path("/v1/responses"))
55 .respond_with(ResponseTemplate::new(429).set_body_json(json!({
56 "error": {
57 "type": "rate_limit_error",
58 "code": "rate_limit_exceeded",
59 "message": "slow down"
60 }
61 })))
62 .mount(&server)
63 .await;
64
65 let provider = provider_for(&server.uri());
66 let err = provider
67 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]))
68 .await
69 .unwrap_err();
70 assert!(matches!(err, LlmError::RateLimited { .. }));
71}
72
73#[tokio::test]
74async fn chat_routes_a_401_to_auth_error() {
75 let server = MockServer::start().await;
76 Mock::given(method("POST"))
77 .and(path("/v1/responses"))
78 .respond_with(ResponseTemplate::new(401).set_body_json(json!({
79 "error": {
80 "type": "authentication_error",
81 "code": "invalid_api_key",
82 "message": "bad key"
83 }
84 })))
85 .mount(&server)
86 .await;
87
88 let provider = provider_for(&server.uri());
89 let err = provider
90 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]))
91 .await
92 .unwrap_err();
93 assert!(matches!(err, LlmError::AuthError { .. }));
94}
95
96#[tokio::test]
97async fn chat_routes_400_context_length_envelope() {
98 let server = MockServer::start().await;
99 Mock::given(method("POST"))
100 .and(path("/v1/responses"))
101 .respond_with(ResponseTemplate::new(400).set_body_json(json!({
102 "error": {
103 "type": "invalid_request_error",
104 "code": "context_length_exceeded",
105 "message": "too many tokens"
106 }
107 })))
108 .mount(&server)
109 .await;
110
111 let provider = provider_for(&server.uri());
112 let err = provider
113 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]))
114 .await
115 .unwrap_err();
116 assert!(matches!(err, LlmError::ContextLengthExceeded { .. }));
117}
118
119#[tokio::test]
120async fn chat_sends_bearer_and_org_headers() {
121 use wiremock::matchers::header;
122 let server = MockServer::start().await;
123 Mock::given(method("POST"))
124 .and(path("/v1/responses"))
125 .and(header("Authorization", "Bearer test-key"))
126 .and(header("OpenAI-Organization", "org-x"))
127 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
128 "model": "gpt-4o-mini",
129 "status": "completed",
130 "output": []
131 })))
132 .expect(1)
133 .mount(&server)
134 .await;
135
136 let mut config = OpenAIConfig::new("test-key").with_base_url(server.uri());
137 config.organization = Some("org-x".into());
138 let provider = OpenAIProvider::new(config).unwrap();
139 let _ = provider
140 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]))
141 .await
142 .unwrap();
143}
144
145#[tokio::test]
146async fn chat_stream_decodes_text_deltas() {
147 let server = MockServer::start().await;
148 let sse_body = "data: {\"type\":\"response.output_text.delta\",\"delta\":\"he\"}\n\n\
149 data: {\"type\":\"response.output_text.delta\",\"delta\":\"llo\"}\n\n\
150 data: {\"type\":\"response.completed\",\"response\":{\"usage\":{\"input_tokens\":3,\"output_tokens\":1}}}\n\n\
151 data: [DONE]\n\n";
152 Mock::given(method("POST"))
153 .and(path("/v1/responses"))
154 .respond_with(
155 ResponseTemplate::new(200)
156 .insert_header("content-type", "text/event-stream")
157 .set_body_string(sse_body),
158 )
159 .mount(&server)
160 .await;
161
162 let provider = provider_for(&server.uri());
163 let stream = provider
164 .chat_stream(ChatRequest::new(vec![ChatMessage::user("hi")]))
165 .await
166 .unwrap();
167 let events: Vec<StreamEvent> = stream.map(|r| r.unwrap()).collect().await;
168
169 let texts: Vec<String> = events
170 .iter()
171 .filter_map(|e| match e {
172 StreamEvent::TextDelta { delta } => Some(delta.clone()),
173 _ => None,
174 })
175 .collect();
176 assert_eq!(texts, vec!["he", "llo"]);
177 assert!(events.iter().any(|e| matches!(
178 e,
179 StreamEvent::Done {
180 finish_reason: FinishReason::Stop
181 }
182 )));
183 assert!(events.iter().any(|e| matches!(e, StreamEvent::Usage(_))));
184}
185
186#[tokio::test]
187async fn chat_stream_surfaces_404_as_model_not_found() {
188 let server = MockServer::start().await;
189 Mock::given(method("POST"))
190 .and(path("/v1/responses"))
191 .respond_with(ResponseTemplate::new(404).set_body_json(json!({
192 "error": {
193 "type": "invalid_request_error",
194 "code": "model_not_found",
195 "message": "model o99 does not exist"
196 }
197 })))
198 .mount(&server)
199 .await;
200
201 let provider = provider_for(&server.uri());
202 let result = provider
203 .chat_stream(ChatRequest::new(vec![ChatMessage::user("hi")]))
204 .await;
205 let err = match result {
206 Ok(_) => panic!("expected ModelNotFound, got Ok"),
207 Err(e) => e,
208 };
209 assert!(matches!(err, LlmError::ModelNotFound { .. }));
210}
211
212#[tokio::test]
213async fn request_body_uses_model_override() {
214 use wiremock::matchers::body_partial_json;
215 let server = MockServer::start().await;
216 Mock::given(method("POST"))
217 .and(path("/v1/responses"))
218 .and(body_partial_json(json!({"model": "o3"})))
219 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
220 "model": "o3",
221 "status": "completed",
222 "output": []
223 })))
224 .expect(1)
225 .mount(&server)
226 .await;
227
228 let provider = provider_for(&server.uri());
229 provider
230 .chat(ChatRequest::new(vec![ChatMessage::user("hi")]).with_model_override("o3"))
231 .await
232 .unwrap();
233}
Deleted agent/llm/types/capabilities.rs
@@ -1,65 +0,0 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
4pub struct ProviderCapabilities {
5 #[serde(default)]
6 pub streaming: bool,
7 #[serde(default)]
8 pub tool_calling: bool,
9 #[serde(default)]
10 pub structured_output: bool,
11 #[serde(default)]
12 pub vision: bool,
13 #[serde(default)]
14 pub reasoning: bool,
15 #[serde(default)]
16 pub embeddings: bool,
17 #[serde(default)]
18 pub max_context_tokens: usize,
19 #[serde(default)]
20 pub max_output_tokens: usize,
21}
22
23impl ProviderCapabilities {
24 pub fn fits_context(&self, input_tokens: usize, output_tokens: usize) -> bool {
25 self.max_context_tokens == 0
26 || input_tokens.saturating_add(output_tokens) <= self.max_context_tokens
27 }
28
29 pub fn remaining_input_budget(&self, output_tokens: usize) -> Option<usize> {
30 (self.max_context_tokens != 0)
31 .then(|| self.max_context_tokens.saturating_sub(output_tokens))
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 #[test]
40 fn unknown_context_window_never_rejects() {
41 let caps = ProviderCapabilities::default();
42 assert!(caps.fits_context(usize::MAX, usize::MAX));
43 assert_eq!(caps.remaining_input_budget(10), None);
44 }
45
46 #[test]
47 fn known_context_window_enforces_the_limit() {
48 let caps = ProviderCapabilities {
49 max_context_tokens: 100,
50 ..Default::default()
51 };
52 assert!(caps.fits_context(90, 10));
53 assert!(!caps.fits_context(91, 10));
54 assert_eq!(caps.remaining_input_budget(10), Some(90));
55 }
56
57 #[test]
58 fn budget_saturates_rather_than_underflowing() {
59 let caps = ProviderCapabilities {
60 max_context_tokens: 10,
61 ..Default::default()
62 };
63 assert_eq!(caps.remaining_input_budget(999), Some(0));
64 }
65}
Deleted agent/llm/types/message.rs
@@ -1,202 +0,0 @@
1use crate::types::tools::ToolCall;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum MessageRole {
7 System,
8 User,
9 Assistant,
10 Tool,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(untagged)]
15pub enum MessageContent {
16 Text(String),
17 Parts(Vec<ContentPart>),
18}
19
20impl Default for MessageContent {
21 fn default() -> Self {
22 Self::Text(String::new())
23 }
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum ContentPart {
29 Text(String),
30 Image { url: String, detail: ImageDetail },
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum ImageDetail {
36 #[default]
37 Auto,
38 Low,
39 High,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct ChatMessage {
44 pub role: MessageRole,
45 pub content: MessageContent,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub name: Option<String>,
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub tool_calls: Vec<ToolCall>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub tool_call_id: Option<String>,
52}
53
54impl ChatMessage {
55 fn text(role: MessageRole, text: impl Into<String>) -> Self {
56 Self {
57 role,
58 content: MessageContent::Text(text.into()),
59 name: None,
60 tool_calls: Vec::new(),
61 tool_call_id: None,
62 }
63 }
64
65 pub fn system(text: impl Into<String>) -> Self {
66 Self::text(MessageRole::System, text)
67 }
68
69 pub fn user(text: impl Into<String>) -> Self {
70 Self::text(MessageRole::User, text)
71 }
72
73 pub fn assistant(text: impl Into<String>) -> Self {
74 Self::text(MessageRole::Assistant, text)
75 }
76
77 pub fn tool(tool_call_id: impl Into<String>, text: impl Into<String>) -> Self {
78 Self {
79 tool_call_id: Some(tool_call_id.into()),
80 ..Self::text(MessageRole::Tool, text)
81 }
82 }
83
84 pub fn with_name(mut self, name: impl Into<String>) -> Self {
85 self.name = Some(name.into());
86 self
87 }
88
89 pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
90 self.tool_calls = tool_calls;
91 self
92 }
93
94 pub fn validate(&self) -> Result<(), &'static str> {
95 match self.role {
96 MessageRole::Tool if self.tool_call_id.is_none() => {
97 Err("tool message requires tool_call_id")
98 }
99 MessageRole::Tool if !self.tool_calls.is_empty() => {
100 Err("tool message must not carry tool_calls")
101 }
102 MessageRole::Assistant | MessageRole::Tool => Ok(()),
103 _ if !self.tool_calls.is_empty() => Err("only assistant messages may carry tool_calls"),
104 _ => Ok(()),
105 }
106 }
107
108 pub fn text_content(&self) -> String {
109 match &self.content {
110 MessageContent::Text(s) => s.clone(),
111 MessageContent::Parts(parts) => parts
112 .iter()
113 .filter_map(|p| match p {
114 ContentPart::Text(s) => Some(s.as_str()),
115 ContentPart::Image { .. } => None,
116 })
117 .collect(),
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use serde_json::json;
126
127 fn call() -> ToolCall {
128 ToolCall {
129 id: "c".into(),
130 name: "n".into(),
131 arguments: json!({}),
132 }
133 }
134
135 #[test]
136 fn tool_constructor_always_sets_the_id() {
137 let m = ChatMessage::tool("call_1", "42");
138 assert_eq!(m.tool_call_id.as_deref(), Some("call_1"));
139 assert!(m.validate().is_ok());
140 }
141
142 #[test]
143 fn validate_rejects_tool_message_without_id() {
144 let mut m = ChatMessage::tool("call_1", "42");
145 m.tool_call_id = None;
146 assert!(m.validate().is_err());
147 }
148
149 #[test]
150 fn validate_rejects_tool_calls_on_non_assistant_roles() {
151 assert!(
152 ChatMessage::user("hi")
153 .with_tool_calls(vec![call()])
154 .validate()
155 .is_err()
156 );
157 assert!(
158 ChatMessage::system("hi")
159 .with_tool_calls(vec![call()])
160 .validate()
161 .is_err()
162 );
163 assert!(
164 ChatMessage::assistant("")
165 .with_tool_calls(vec![call()])
166 .validate()
167 .is_ok()
168 );
169 }
170
171 #[test]
172 fn plain_messages_validate() {
173 assert!(ChatMessage::user("hi").validate().is_ok());
174 assert!(ChatMessage::system("hi").validate().is_ok());
175 }
176
177 #[test]
178 fn text_content_flattens_parts_and_skips_images() {
179 let m = ChatMessage {
180 role: MessageRole::User,
181 content: MessageContent::Parts(vec![
182 ContentPart::Text("a".into()),
183 ContentPart::Image {
184 url: "u".into(),
185 detail: ImageDetail::Low,
186 },
187 ContentPart::Text("b".into()),
188 ]),
189 name: None,
190 tool_calls: Vec::new(),
191 tool_call_id: None,
192 };
193 assert_eq!(m.text_content(), "ab");
194 }
195
196 #[test]
197 fn round_trips_through_json() {
198 let m = ChatMessage::user("hi").with_name("alice");
199 let back: ChatMessage = serde_json::from_str(&serde_json::to_string(&m).unwrap()).unwrap();
200 assert_eq!(back, m);
201 }
202}
Deleted agent/llm/types/request.rs
@@ -1,168 +0,0 @@
1use crate::types::message::ChatMessage;
2use crate::types::tools::ToolDefinition;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6#[serde(rename_all = "snake_case")]
7pub enum ResponseFormat {
8 #[default]
9 Text,
10 Json,
11}
12
13/// `#[non_exhaustive]` keeps future fields from being breaking changes; build
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15#[non_exhaustive]
16pub struct ChatRequest {
17 pub messages: Vec<ChatMessage>,
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub tools: Vec<ToolDefinition>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub max_tokens: Option<usize>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub temperature: Option<f32>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub top_p: Option<f32>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub response_format: Option<ResponseFormat>,
28 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub stop_sequences: Vec<String>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub provider_options: Option<serde_json::Value>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub model_override: Option<String>,
34}
35
36impl ChatRequest {
37 pub fn new(messages: Vec<ChatMessage>) -> Self {
38 Self {
39 messages,
40 ..Default::default()
41 }
42 }
43
44 pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
45 self.tools = tools;
46 self
47 }
48
49 pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
50 self.max_tokens = Some(max_tokens);
51 self
52 }
53
54 pub fn with_temperature(mut self, temperature: f32) -> Self {
55 if !temperature.is_finite() {
56 return self;
57 }
58 self.temperature = Some(temperature.clamp(0.0, 2.0));
59 self
60 }
61
62 pub fn with_top_p(mut self, top_p: f32) -> Self {
63 if !top_p.is_finite() {
64 return self;
65 }
66 self.top_p = Some(top_p.clamp(0.0, 1.0));
67 self
68 }
69
70 pub fn with_response_format(mut self, format: ResponseFormat) -> Self {
71 self.response_format = Some(format);
72 self
73 }
74
75 pub fn with_stop_sequences(mut self, stop: Vec<String>) -> Self {
76 self.stop_sequences = stop;
77 self
78 }
79
80 pub fn with_provider_options(mut self, options: serde_json::Value) -> Self {
81 self.provider_options = Some(options);
82 self
83 }
84
85 pub fn with_model_override(mut self, model: impl Into<String>) -> Self {
86 self.model_override = Some(model.into());
87 self
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn builder_sets_fields() {
97 let req = ChatRequest::new(vec![ChatMessage::user("hi")])
98 .with_max_tokens(64)
99 .with_model_override("gpt-4o");
100 assert_eq!(req.max_tokens, Some(64));
101 assert_eq!(req.model_override.as_deref(), Some("gpt-4o"));
102 }
103
104 #[test]
105 fn sampling_parameters_are_clamped() {
106 assert_eq!(
107 ChatRequest::default().with_temperature(9.0).temperature,
108 Some(2.0)
109 );
110 assert_eq!(
111 ChatRequest::default().with_temperature(-1.0).temperature,
112 Some(0.0)
113 );
114 assert_eq!(ChatRequest::default().with_top_p(5.0).top_p, Some(1.0));
115 }
116
117 #[test]
118 fn nan_and_infinity_inputs_are_dropped() {
119 let req = ChatRequest::default()
120 .with_temperature(f32::NAN)
121 .with_top_p(f32::INFINITY);
122 assert_eq!(req.temperature, None);
123 assert_eq!(req.top_p, None);
124
125 let req = ChatRequest::default()
126 .with_temperature(f32::NEG_INFINITY)
127 .with_top_p(f32::NAN);
128 assert_eq!(req.temperature, None);
129 assert_eq!(req.top_p, None);
130 }
131
132 #[test]
133 fn finite_inputs_still_replace_previous_value() {
134 let req = ChatRequest::default()
135 .with_temperature(0.7)
136 .with_temperature(f32::NAN);
137 assert_eq!(req.temperature, Some(0.7));
138 }
139
140 #[test]
141 fn sampling_parameters_clamp_inclusive_endpoints() {
142 assert_eq!(
143 ChatRequest::default().with_temperature(0.0).temperature,
144 Some(0.0)
145 );
146 assert_eq!(
147 ChatRequest::default().with_temperature(2.0).temperature,
148 Some(2.0)
149 );
150 assert_eq!(ChatRequest::default().with_top_p(0.0).top_p, Some(0.0));
151 assert_eq!(ChatRequest::default().with_top_p(1.0).top_p, Some(1.0));
152 }
153
154 #[test]
155 fn round_trips_through_json() {
156 let req = ChatRequest::new(vec![ChatMessage::user("hi")]).with_max_tokens(8);
157 let json = serde_json::to_string(&req).unwrap();
158 let back: ChatRequest = serde_json::from_str(&json).unwrap();
159 assert_eq!(back.messages.len(), 1);
160 assert_eq!(back.max_tokens, Some(8));
161 }
162
163 #[test]
164 fn empty_optionals_are_omitted_from_json() {
165 let json = serde_json::to_string(&ChatRequest::new(vec![])).unwrap();
166 assert_eq!(json, r#"{"messages":[]}"#);
167 }
168}
Deleted agent/llm/types/response.rs
@@ -1,108 +0,0 @@
1use crate::token::TokenUsageStats;
2use crate::types::tools::ToolCall;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct ChatResponse {
7 pub content: String,
8 #[serde(default, skip_serializing_if = "Vec::is_empty")]
9 pub tool_calls: Vec<ToolCall>,
10 pub finish_reason: FinishReason,
11 #[serde(default)]
12 pub usage: TokenUsageStats,
13 pub model: String,
14 pub provider: String,
15}
16
17impl ChatResponse {
18 pub fn new(
19 content: impl Into<String>,
20 provider: impl Into<String>,
21 model: impl Into<String>,
22 ) -> Self {
23 Self {
24 content: content.into(),
25 tool_calls: Vec::new(),
26 finish_reason: FinishReason::Stop,
27 usage: TokenUsageStats::default(),
28 model: model.into(),
29 provider: provider.into(),
30 }
31 }
32
33 pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
34 self.finish_reason = FinishReason::ToolUse;
35 self.tool_calls = tool_calls;
36 self
37 }
38
39 pub fn with_usage(mut self, usage: TokenUsageStats) -> Self {
40 self.usage = usage;
41 self
42 }
43
44 pub fn with_finish_reason(mut self, reason: FinishReason) -> Self {
45 self.finish_reason = reason;
46 self
47 }
48
49 pub fn requests_tools(&self) -> bool {
50 !self.tool_calls.is_empty()
51 }
52
53 pub fn is_truncated(&self) -> bool {
54 matches!(
55 self.finish_reason,
56 FinishReason::MaxTokens | FinishReason::ContentFilter | FinishReason::Error
57 )
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum FinishReason {
64 Stop,
65 ToolUse,
66 MaxTokens,
67 ContentFilter,
68 Error,
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use serde_json::json;
75
76 #[test]
77 fn tool_calls_imply_tool_use_finish_reason() {
78 let r = ChatResponse::new("", "openai", "gpt-4o").with_tool_calls(vec![ToolCall {
79 id: "c".into(),
80 name: "n".into(),
81 arguments: json!({}),
82 }]);
83 assert_eq!(r.finish_reason, FinishReason::ToolUse);
84 assert!(r.requests_tools());
85 assert!(!r.is_truncated());
86 }
87
88 #[test]
89 fn truncation_covers_incomplete_outcomes() {
90 let base = ChatResponse::new("x", "p", "m");
91 assert!(!base.clone().is_truncated());
92 for reason in [
93 FinishReason::MaxTokens,
94 FinishReason::ContentFilter,
95 FinishReason::Error,
96 ] {
97 assert!(base.clone().with_finish_reason(reason).is_truncated());
98 }
99 }
100
101 #[test]
102 fn round_trips_through_json() {
103 let r = ChatResponse::new("hi", "openai", "gpt-4o")
104 .with_usage(TokenUsageStats::new(3, 4).with_reasoning(1));
105 let back: ChatResponse = serde_json::from_str(&serde_json::to_string(&r).unwrap()).unwrap();
106 assert_eq!(back, r);
107 }
108}
Deleted agent/llm/types/stream.rs
@@ -1,277 +0,0 @@
1use crate::error::LlmError;
2use crate::token::TokenUsageStats;
3use crate::types::response::FinishReason;
4use crate::types::tools::ToolCall;
5use std::collections::BTreeMap;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum StreamEvent {
9 TextDelta {
10 delta: String,
11 },
12 ReasoningDelta {
13 delta: String,
14 },
15 ToolCallDelta {
16 index: Option<usize>,
17 id: String,
18 name: Option<String>,
19 args_delta: String,
20 },
21 Usage(TokenUsageStats),
22 Done {
23 finish_reason: FinishReason,
24 },
25}
26
27#[derive(Debug, Default)]
28pub struct StreamAccumulator {
29 text: String,
30 reasoning: String,
31 calls: BTreeMap<CallKey, PartialToolCall>,
32 usage: Option<TokenUsageStats>,
33 finish_reason: Option<FinishReason>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
37enum CallKey {
38 Index(usize),
39 Id(String),
40}
41
42#[derive(Debug, Default)]
43struct PartialToolCall {
44 id: String,
45 name: Option<String>,
46 args: String,
47}
48
49impl StreamAccumulator {
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn push(&mut self, event: &StreamEvent) {
55 match event {
56 StreamEvent::TextDelta { delta } => self.text.push_str(delta),
57 StreamEvent::ReasoningDelta { delta } => self.reasoning.push_str(delta),
58 StreamEvent::ToolCallDelta {
59 index,
60 id,
61 name,
62 args_delta,
63 } => {
64 let key = match index {
65 Some(i) => CallKey::Index(*i),
66 None => CallKey::Id(id.clone()),
67 };
68 let entry = self.calls.entry(key).or_default();
69 if !id.is_empty() {
70 entry.id = id.clone();
71 }
72 if let Some(name) = name
73 && !name.is_empty()
74 {
75 entry.name = Some(name.clone());
76 }
77 entry.args.push_str(args_delta);
78 }
79 StreamEvent::Usage(usage) => match &mut self.usage {
80 Some(existing) => existing.merge(usage),
81 None => self.usage = Some(usage.clone()),
82 },
83 StreamEvent::Done { finish_reason } => self.finish_reason = Some(*finish_reason),
84 }
85 }
86
87 pub fn text(&self) -> &str {
88 &self.text
89 }
90
91 pub fn reasoning(&self) -> &str {
92 &self.reasoning
93 }
94
95 pub fn usage(&self) -> Option<&TokenUsageStats> {
96 self.usage.as_ref()
97 }
98
99 pub fn finish_reason(&self) -> Option<FinishReason> {
100 self.finish_reason
101 }
102
103 pub fn tool_calls(&self) -> Result<Vec<ToolCall>, LlmError> {
104 self.calls
105 .values()
106 .map(|p| {
107 let name = p.name.clone().ok_or_else(|| {
108 LlmError::Stream(format!("tool call '{}' never received a name", p.id))
109 })?;
110 let args = p.args.trim();
111 let arguments = if args.is_empty() {
112 serde_json::Value::Object(serde_json::Map::new())
113 } else {
114 serde_json::from_str(args).map_err(|e| {
115 LlmError::Stream(format!("tool call '{name}' has malformed arguments: {e}"))
116 })?
117 };
118 Ok(ToolCall {
119 id: p.id.clone(),
120 name,
121 arguments,
122 })
123 })
124 .collect()
125 }
126
127 pub fn finish(
128 self,
129 provider: impl Into<String>,
130 model: impl Into<String>,
131 ) -> Result<crate::types::response::ChatResponse, LlmError> {
132 let tool_calls = self.tool_calls()?;
133 let finish_reason = self
134 .finish_reason
135 .ok_or_else(|| LlmError::Stream("stream ended without a Done event".into()))?;
136 Ok(crate::types::response::ChatResponse {
137 content: self.text,
138 tool_calls,
139 finish_reason,
140 usage: self.usage.unwrap_or_default(),
141 model: model.into(),
142 provider: provider.into(),
143 })
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use serde_json::json;
151
152 fn delta(index: Option<usize>, id: &str, name: Option<&str>, args: &str) -> StreamEvent {
153 StreamEvent::ToolCallDelta {
154 index,
155 id: id.to_string(),
156 name: name.map(str::to_string),
157 args_delta: args.to_string(),
158 }
159 }
160
161 #[test]
162 fn concatenates_text_and_reasoning_separately() {
163 let mut acc = StreamAccumulator::new();
164 acc.push(&StreamEvent::TextDelta { delta: "he".into() });
165 acc.push(&StreamEvent::ReasoningDelta { delta: "hm".into() });
166 acc.push(&StreamEvent::TextDelta {
167 delta: "llo".into(),
168 });
169 assert_eq!(acc.text(), "hello");
170 assert_eq!(acc.reasoning(), "hm");
171 }
172
173 #[test]
174 fn reassembles_split_tool_call_arguments() {
175 let mut acc = StreamAccumulator::new();
176 acc.push(&delta(Some(0), "c1", Some("search"), r#"{"q":"#));
177 acc.push(&delta(Some(0), "", None, r#""rust"}"#));
178 let calls = acc.tool_calls().unwrap();
179 assert_eq!(calls.len(), 1);
180 assert_eq!(calls[0].id, "c1");
181 assert_eq!(calls[0].name, "search");
182 assert_eq!(calls[0].arguments, json!({ "q": "rust" }));
183 }
184
185 #[test]
186 fn keeps_parallel_calls_separate_by_index() {
187 let mut acc = StreamAccumulator::new();
188 acc.push(&delta(Some(0), "c1", Some("a"), r#"{"x":1}"#));
189 acc.push(&delta(Some(1), "c2", Some("b"), r#"{"y":"#));
190 acc.push(&delta(Some(1), "", None, "2}"));
191 let calls = acc.tool_calls().unwrap();
192 assert_eq!(calls.len(), 2);
193 assert_eq!(calls[0].name, "a");
194 assert_eq!(calls[1].arguments, json!({ "y": 2 }));
195 }
196
197 #[test]
198 fn groups_by_id_when_index_is_absent() {
199 let mut acc = StreamAccumulator::new();
200 acc.push(&delta(None, "c1", Some("a"), r#"{"x":"#));
201 acc.push(&delta(None, "c1", None, "1}"));
202 acc.push(&delta(None, "c2", Some("b"), "{}"));
203 assert_eq!(acc.tool_calls().unwrap().len(), 2);
204 }
205
206 #[test]
207 fn later_fragments_do_not_erase_id_or_name() {
208 let mut acc = StreamAccumulator::new();
209 acc.push(&delta(Some(0), "c1", Some("search"), "{"));
210 acc.push(&delta(Some(0), "", Some(""), "}"));
211 let calls = acc.tool_calls().unwrap();
212 assert_eq!(calls[0].id, "c1");
213 assert_eq!(calls[0].name, "search");
214 }
215
216 #[test]
217 fn empty_arguments_become_an_empty_object() {
218 let mut acc = StreamAccumulator::new();
219 acc.push(&delta(Some(0), "c1", Some("noop"), ""));
220 assert_eq!(acc.tool_calls().unwrap()[0].arguments, json!({}));
221 }
222
223 #[test]
224 fn truncated_arguments_are_reported_not_swallowed() {
225 let mut acc = StreamAccumulator::new();
226 acc.push(&delta(Some(0), "c1", Some("search"), r#"{"q":"ru"#));
227 assert!(matches!(acc.tool_calls(), Err(LlmError::Stream(_))));
228 }
229
230 #[test]
231 fn call_without_a_name_is_an_error() {
232 let mut acc = StreamAccumulator::new();
233 acc.push(&delta(Some(0), "c1", None, "{}"));
234 assert!(matches!(acc.tool_calls(), Err(LlmError::Stream(_))));
235 }
236
237 #[test]
238 fn mixed_index_and_id_keys_for_same_call_is_malformed() {
239 let mut acc = StreamAccumulator::new();
240 acc.push(&delta(Some(0), "c1", Some("search"), r#"{"q":"ru"#));
241 acc.push(&delta(None, "c1", None, r#"st"}"#));
242 assert!(matches!(acc.tool_calls(), Err(LlmError::Stream(_))));
243 }
244
245 #[test]
246 fn usage_events_accumulate() {
247 let mut acc = StreamAccumulator::new();
248 acc.push(&StreamEvent::Usage(TokenUsageStats::new(5, 1)));
249 acc.push(&StreamEvent::Usage(TokenUsageStats::new(0, 2)));
250 assert_eq!(acc.usage().unwrap().output_tokens, 3);
251 }
252
253 #[test]
254 fn finish_builds_a_response() {
255 let mut acc = StreamAccumulator::new();
256 acc.push(&StreamEvent::TextDelta { delta: "hi".into() });
257 acc.push(&StreamEvent::Usage(TokenUsageStats::new(2, 1)));
258 acc.push(&StreamEvent::Done {
259 finish_reason: FinishReason::Stop,
260 });
261 let resp = acc.finish("openai", "gpt-4o").unwrap();
262 assert_eq!(resp.content, "hi");
263 assert_eq!(resp.finish_reason, FinishReason::Stop);
264 assert_eq!(resp.usage.input_tokens, 2);
265 assert_eq!(resp.provider, "openai");
266 }
267
268 #[test]
269 fn finish_rejects_a_stream_that_never_completed() {
270 let mut acc = StreamAccumulator::new();
271 acc.push(&StreamEvent::TextDelta { delta: "hi".into() });
272 assert!(matches!(
273 acc.finish("openai", "gpt-4o"),
274 Err(LlmError::Stream(_))
275 ));
276 }
277}
Deleted agent/llm/types/tools.rs
@@ -1,46 +0,0 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4pub struct ToolCall {
5 pub id: String,
6 pub name: String,
7 pub arguments: serde_json::Value,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum ConcurrencyPolicy {
13 #[default]
14 Safe,
15 Exclusive,
16 Ordered,
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct ToolDefinition {
21 pub name: String,
22 pub description: String,
23 pub parameters: serde_json::Value,
24 #[serde(default)]
25 pub concurrency: ConcurrencyPolicy,
26}
27
28impl ToolDefinition {
29 pub fn new(
30 name: impl Into<String>,
31 description: impl Into<String>,
32 parameters: serde_json::Value,
33 ) -> Self {
34 Self {
35 name: name.into(),
36 description: description.into(),
37 parameters,
38 concurrency: ConcurrencyPolicy::default(),
39 }
40 }
41
42 pub fn with_concurrency(mut self, concurrency: ConcurrencyPolicy) -> Self {
43 self.concurrency = concurrency;
44 self
45 }
46}
Deleted agent/llm/Cargo.toml
@@ -1,29 +0,0 @@
1[package]
2name = "llm"
3version.workspace = true
4edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
10
11[lib]
12path = "lib.rs"
13name = "llm"
14[dependencies]
15thiserror.workspace = true
16serde.workspace = true
17serde_json.workspace = true
18async-trait.workspace = true
19futures.workspace = true
20tokio-stream.workspace = true
21tracing.workspace = true
22reqwest = { workspace = true, default-features = false, features = ["default-tls", "json", "stream"] }
23bytes.workspace = true
24eventsource-stream = "0.2"
25async-stream = "0.3"
26
27[dev-dependencies]
28tokio = { workspace = true, features = ["macros", "rt"] }
29wiremock = "0.6"
No newline at end of file
Deleted agent/llm/embed.rs
@@ -1,7 +0,0 @@
1use crate::error::LlmError;
2
3#[async_trait::async_trait]
4pub trait EmbeddingProvider: Send + Sync {
5 async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>, LlmError>;
6 fn embedding_dimensions(&self) -> usize;
7}
Deleted agent/llm/error.rs
@@ -1,244 +0,0 @@
1use std::time::Duration;
2
3#[derive(Debug, thiserror::Error)]
4pub enum LlmError {
5 #[error("Agent error: {0}")]
6 Agent(String),
7
8 #[error("Provider error: {0}")]
9 Provider(String),
10
11 #[error("Token budget exceeded: used {used}, budget {budget}")]
12 TokenBudgetExceeded { used: usize, budget: usize },
13
14 #[error("Configuration error: {0}")]
15 Config(String),
16
17 #[error("Memory error: {0}")]
18 Memory(String),
19
20 #[error("Coordination error: {0}")]
21 Coordination(String),
22
23 #[error("Isolation error: {0}")]
24 Isolation(String),
25
26 #[error("Protocol error: {0}")]
27 Protocol(String),
28
29 #[error("IO error: {0}")]
30 Io(#[from] std::io::Error),
31
32 #[error("Rate limited by {provider}. Retry after {retry_after_secs:?}s")]
33 RateLimited {
34 provider: String,
35 retry_after_secs: Option<u64>,
36 },
37
38 #[error("Context length exceeded: {tokens_used} tokens, limit {limit}")]
39 ContextLengthExceeded { tokens_used: usize, limit: usize },
40
41 #[error("Content filtered by {provider}: {reason}")]
42 ContentFiltered { provider: String, reason: String },
43
44 #[error("Authentication failed for {provider}")]
45 AuthError { provider: String },
46
47 #[error("Model not found: {model} on {provider}")]
48 ModelNotFound { provider: String, model: String },
49
50 #[error("Network error: {0}")]
51 Network(String),
52
53 #[error("Serialization error: {0}")]
54 Serialization(#[from] serde_json::Error),
55
56 #[error("{provider} API error: {status} - {message}")]
57 ApiError {
58 provider: String,
59 status: u16,
60 message: String,
61 },
62
63 #[error("Streaming error: {0}")]
64 Stream(String),
65
66 #[error("Provider not found: {0}")]
67 ProviderNotFound(String),
68 #[error("Missing required parameter '{parameter}' for skill '{skill}'")]
69 SkillMissingParameter { skill: String, parameter: String },
70
71 #[error("Skill not found: {0}")]
72 SkillNotFound(String),
73
74 #[error("Skill rendering failed: {0}")]
75 SkillRenderFailed(String),
76
77 #[error(
78 "primary failed (retryable), then fallback also failed: primary={primary} -> fallback={fallback}"
79 )]
80 FallbackExhausted {
81 primary: Box<LlmError>,
82 fallback: Box<LlmError>,
83 },
84}
85
86impl LlmError {
87 pub fn is_retryable(&self) -> bool {
88 match self {
89 Self::RateLimited { .. } | Self::Network(_) => true,
90 Self::ApiError { status, .. } => *status == 429 || (500..600).contains(status),
91 Self::Io(e) => matches!(
92 e.kind(),
93 std::io::ErrorKind::TimedOut
94 | std::io::ErrorKind::Interrupted
95 | std::io::ErrorKind::ConnectionReset
96 | std::io::ErrorKind::ConnectionAborted
97 | std::io::ErrorKind::BrokenPipe
98 ),
99 Self::FallbackExhausted { .. } => false,
100 _ => false,
101 }
102 }
103
104 pub fn retry_after(&self) -> Option<Duration> {
105 match self {
106 Self::RateLimited {
107 retry_after_secs, ..
108 } => retry_after_secs.map(Duration::from_secs),
109 Self::FallbackExhausted { fallback, .. } => fallback.retry_after(),
110 _ => None,
111 }
112 }
113
114 pub fn provider(&self) -> Option<&str> {
115 match self {
116 Self::RateLimited { provider, .. }
117 | Self::ContentFiltered { provider, .. }
118 | Self::AuthError { provider }
119 | Self::ModelNotFound { provider, .. }
120 | Self::ApiError { provider, .. }
121 | Self::ProviderNotFound(provider) => Some(provider),
122 Self::FallbackExhausted { fallback, .. } => fallback.provider(),
123 _ => None,
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn rate_limit_and_transport_errors_are_retryable() {
134 assert!(
135 LlmError::RateLimited {
136 provider: "p".into(),
137 retry_after_secs: Some(3),
138 }
139 .is_retryable()
140 );
141 assert!(LlmError::Network("reset".into()).is_retryable());
142 }
143
144 #[test]
145 fn api_errors_retry_on_5xx_and_429_only() {
146 let err = |status| LlmError::ApiError {
147 provider: "p".into(),
148 status,
149 message: String::new(),
150 };
151 assert!(err(429).is_retryable());
152 assert!(err(500).is_retryable());
153 assert!(err(503).is_retryable());
154 assert!(!err(400).is_retryable());
155 assert!(!err(404).is_retryable());
156 }
157
158 #[test]
159 fn deterministic_failures_are_not_retryable() {
160 assert!(
161 !LlmError::ContextLengthExceeded {
162 tokens_used: 10,
163 limit: 5,
164 }
165 .is_retryable()
166 );
167 assert!(
168 !LlmError::AuthError {
169 provider: "p".into()
170 }
171 .is_retryable()
172 );
173 assert!(
174 !LlmError::ContentFiltered {
175 provider: "p".into(),
176 reason: "x".into(),
177 }
178 .is_retryable()
179 );
180 }
181
182 #[test]
183 fn retry_after_is_exposed_as_duration() {
184 let e = LlmError::RateLimited {
185 provider: "p".into(),
186 retry_after_secs: Some(30),
187 };
188 assert_eq!(e.retry_after(), Some(Duration::from_secs(30)));
189 assert_eq!(LlmError::Network("x".into()).retry_after(), None);
190 }
191
192 #[test]
193 fn provider_is_attributed_where_known() {
194 assert_eq!(
195 LlmError::AuthError {
196 provider: "openai".into()
197 }
198 .provider(),
199 Some("openai")
200 );
201 assert_eq!(LlmError::Stream("x".into()).provider(), None);
202 }
203
204 #[test]
205 fn fallback_exhausted_is_not_retryable() {
206 let err = LlmError::FallbackExhausted {
207 primary: Box::new(LlmError::RateLimited {
208 provider: "primary".into(),
209 retry_after_secs: Some(1),
210 }),
211 fallback: Box::new(LlmError::Network("down".into())),
212 };
213 assert!(!err.is_retryable());
214 }
215
216 #[test]
217 fn fallback_exhausted_passes_through_fallback_metadata() {
218 let err = LlmError::FallbackExhausted {
219 primary: Box::new(LlmError::RateLimited {
220 provider: "primary".into(),
221 retry_after_secs: Some(1),
222 }),
223 fallback: Box::new(LlmError::ApiError {
224 provider: "fb".into(),
225 status: 503,
226 message: "down".into(),
227 }),
228 };
229 assert_eq!(err.retry_after(), None);
230 assert_eq!(err.provider(), Some("fb"));
231 }
232
233 #[test]
234 fn fallback_exhausted_propagates_fallback_retry_after() {
235 let err = LlmError::FallbackExhausted {
236 primary: Box::new(LlmError::Network("reset".into())),
237 fallback: Box::new(LlmError::RateLimited {
238 provider: "fb".into(),
239 retry_after_secs: Some(7),
240 }),
241 };
242 assert_eq!(err.retry_after(), Some(Duration::from_secs(7)));
243 }
244}
Deleted agent/llm/fallback.rs
@@ -1,450 +0,0 @@
1use crate::error::LlmError;
2use crate::provider::LlmProvider;
3use crate::types::{ChatRequest, ChatResponse, ProviderCapabilities, StreamEvent};
4use futures::StreamExt;
5use std::pin::Pin;
6use std::sync::Arc;
7use tokio_stream::Stream;
8
9type EventStream = Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>;
10
11pub struct FallbackTarget {
12 pub provider: Arc<dyn LlmProvider>,
13 pub model: String,
14}
15
16pub struct FallbackProvider {
17 primary: Arc<dyn LlmProvider>,
18 fallback: Option<FallbackTarget>,
19}
20
21impl FallbackProvider {
22 pub fn new(primary: Arc<dyn LlmProvider>, fallback: Option<FallbackTarget>) -> Self {
23 Self { primary, fallback }
24 }
25
26 fn retarget(mut request: ChatRequest, model: &str) -> ChatRequest {
27 if let Some(previous) = &request.model_override
28 && previous != model
29 {
30 tracing::debug!(
31 discarded_model_override = %previous,
32 fallback_model = %model,
33 "replacing caller model_override for fallback provider",
34 );
35 }
36 request.model_override = Some(model.to_string());
37 request
38 }
39
40 fn log_failover(&self, fb: &FallbackTarget, err: &LlmError, stage: &'static str) {
41 tracing::warn!(
42 primary = %self.primary.provider_id(),
43 primary_model = %self.primary.model_id(),
44 fallback = %fb.provider.provider_id(),
45 fallback_model = %fb.model,
46 stage,
47 error = %err,
48 "primary failed retryably; falling back",
49 );
50 }
51}
52
53#[async_trait::async_trait]
54impl LlmProvider for FallbackProvider {
55 fn provider_id(&self) -> &str {
56 self.primary.provider_id()
57 }
58
59 fn model_id(&self) -> &str {
60 self.primary.model_id()
61 }
62
63 fn capabilities(&self) -> ProviderCapabilities {
64 let Some(fb) = &self.fallback else {
65 return self.primary.capabilities();
66 };
67 let (p, f) = (self.primary.capabilities(), fb.provider.capabilities());
68 ProviderCapabilities {
69 streaming: p.streaming && f.streaming,
70 tool_calling: p.tool_calling && f.tool_calling,
71 structured_output: p.structured_output && f.structured_output,
72 vision: p.vision && f.vision,
73 reasoning: p.reasoning && f.reasoning,
74 embeddings: p.embeddings && f.embeddings,
75 max_context_tokens: p.max_context_tokens.min(f.max_context_tokens),
76 max_output_tokens: p.max_output_tokens.min(f.max_output_tokens),
77 }
78 }
79
80 async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError> {
81 let Some(fb) = &self.fallback else {
82 return self.primary.chat(request).await;
83 };
84 match self.primary.chat(request.clone()).await {
85 Err(err) if err.is_retryable() => {
86 self.log_failover(fb, &err, "request");
87 fb.provider
88 .chat(Self::retarget(request, &fb.model))
89 .await
90 .map_err(|fallback_err| LlmError::FallbackExhausted {
91 primary: Box::new(err),
92 fallback: Box::new(fallback_err),
93 })
94 }
95 other => other,
96 }
97 }
98
99 async fn chat_stream(&self, request: ChatRequest) -> Result<EventStream, LlmError> {
100 let Some(fb) = &self.fallback else {
101 return self.primary.chat_stream(request).await;
102 };
103
104 let mut stream = match self.primary.chat_stream(request.clone()).await {
105 Err(err) if err.is_retryable() => {
106 self.log_failover(fb, &err, "stream_open");
107 return fb
108 .provider
109 .chat_stream(Self::retarget(request, &fb.model))
110 .await
111 .map_err(|fallback_err| LlmError::FallbackExhausted {
112 primary: Box::new(err),
113 fallback: Box::new(fallback_err),
114 });
115 }
116 other => other?,
117 };
118
119 let Some(first) = stream.next().await else {
120 return Ok(Box::pin(tokio_stream::empty()));
121 };
122
123 match first {
124 Err(err) if err.is_retryable() => {
125 self.log_failover(fb, &err, "stream_first_event");
126 fb.provider
127 .chat_stream(Self::retarget(request, &fb.model))
128 .await
129 .map_err(|fallback_err| LlmError::FallbackExhausted {
130 primary: Box::new(err),
131 fallback: Box::new(fallback_err),
132 })
133 }
134 first => Ok(Box::pin(
135 futures::stream::once(async move { first }).chain(stream),
136 )),
137 }
138 }
139
140 fn count_tokens(&self, request: &ChatRequest) -> usize {
141 self.primary.count_tokens(request)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::types::{ChatMessage, FinishReason};
149 use std::sync::Mutex;
150 use std::sync::atomic::{AtomicUsize, Ordering};
151
152 struct Scripted {
153 id: String,
154 caps: ProviderCapabilities,
155 calls: AtomicUsize,
156 fail_with: Mutex<Option<LlmError>>,
157 fail_mid_stream: Mutex<Option<LlmError>>,
158 seen_model_override: Mutex<Option<String>>,
159 }
160
161 impl Scripted {
162 fn new(id: &str) -> Self {
163 Self {
164 id: id.to_string(),
165 caps: ProviderCapabilities::default(),
166 calls: AtomicUsize::new(0),
167 fail_with: Mutex::new(None),
168 fail_mid_stream: Mutex::new(None),
169 seen_model_override: Mutex::new(None),
170 }
171 }
172
173 fn failing(id: &str, err: LlmError) -> Self {
174 let s = Self::new(id);
175 *s.fail_with.lock().unwrap() = Some(err);
176 s
177 }
178
179 fn failing_mid_stream(id: &str, err: LlmError) -> Self {
180 let s = Self::new(id);
181 *s.fail_mid_stream.lock().unwrap() = Some(err);
182 s
183 }
184
185 fn with_caps(mut self, caps: ProviderCapabilities) -> Self {
186 self.caps = caps;
187 self
188 }
189
190 fn take_error(&self, slot: &Mutex<Option<LlmError>>) -> Option<LlmError> {
191 slot.lock().unwrap().take()
192 }
193 }
194
195 #[async_trait::async_trait]
196 impl LlmProvider for Scripted {
197 fn provider_id(&self) -> &str {
198 &self.id
199 }
200 fn model_id(&self) -> &str {
201 "m"
202 }
203 fn capabilities(&self) -> ProviderCapabilities {
204 self.caps
205 }
206
207 async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError> {
208 self.calls.fetch_add(1, Ordering::SeqCst);
209 *self.seen_model_override.lock().unwrap() = request.model_override.clone();
210 match self.take_error(&self.fail_with) {
211 Some(err) => Err(err),
212 None => Ok(ChatResponse::new("ok", &self.id, "m")),
213 }
214 }
215
216 async fn chat_stream(&self, request: ChatRequest) -> Result<EventStream, LlmError> {
217 self.calls.fetch_add(1, Ordering::SeqCst);
218 *self.seen_model_override.lock().unwrap() = request.model_override.clone();
219 if let Some(err) = self.take_error(&self.fail_with) {
220 return Err(err);
221 }
222 let id = self.id.clone();
223 if let Some(err) = self.take_error(&self.fail_mid_stream) {
224 return Ok(Box::pin(tokio_stream::iter(vec![Err(err)])));
225 }
226 Ok(Box::pin(tokio_stream::iter(vec![
227 Ok(StreamEvent::TextDelta { delta: id }),
228 Ok(StreamEvent::Done {
229 finish_reason: FinishReason::Stop,
230 }),
231 ])))
232 }
233 }
234
235 fn rate_limited() -> LlmError {
236 LlmError::RateLimited {
237 provider: "primary".into(),
238 retry_after_secs: Some(1),
239 }
240 }
241
242 fn target(provider: Arc<dyn LlmProvider>) -> FallbackTarget {
243 FallbackTarget {
244 provider,
245 model: "fallback-model".into(),
246 }
247 }
248
249 fn request() -> ChatRequest {
250 ChatRequest::new(vec![ChatMessage::user("hi")])
251 }
252
253 async fn collect(stream: EventStream) -> Vec<Result<StreamEvent, LlmError>> {
254 stream.collect::<Vec<_>>().await
255 }
256
257 #[tokio::test]
258 async fn falls_back_on_network_error_not_just_rate_limits() {
259 let secondary = Arc::new(Scripted::new("secondary"));
260 let fb = FallbackProvider::new(
261 Arc::new(Scripted::failing(
262 "primary",
263 LlmError::Network("reset".into()),
264 )),
265 Some(target(secondary.clone())),
266 );
267 let resp = fb.chat(request()).await.unwrap();
268 assert_eq!(resp.provider, "secondary");
269 assert_eq!(secondary.calls.load(Ordering::SeqCst), 1);
270 }
271
272 #[tokio::test]
273 async fn falls_back_on_server_side_api_error() {
274 let secondary = Arc::new(Scripted::new("secondary"));
275 let err = LlmError::ApiError {
276 provider: "primary".into(),
277 status: 503,
278 message: "unavailable".into(),
279 };
280 let fb = FallbackProvider::new(
281 Arc::new(Scripted::failing("primary", err)),
282 Some(target(secondary.clone())),
283 );
284 assert_eq!(fb.chat(request()).await.unwrap().provider, "secondary");
285 }
286
287 #[tokio::test]
288 async fn does_not_fall_back_on_deterministic_errors() {
289 let secondary = Arc::new(Scripted::new("secondary"));
290 let err = LlmError::ContextLengthExceeded {
291 tokens_used: 10,
292 limit: 5,
293 };
294 let fb = FallbackProvider::new(
295 Arc::new(Scripted::failing("primary", err)),
296 Some(target(secondary.clone())),
297 );
298 assert!(fb.chat(request()).await.is_err());
299 assert_eq!(
300 secondary.calls.load(Ordering::SeqCst),
301 0,
302 "a deterministic failure must not burn fallback quota"
303 );
304 }
305
306 #[tokio::test]
307 async fn retargets_the_fallback_model() {
308 let secondary = Arc::new(Scripted::new("secondary"));
309 let fb = FallbackProvider::new(
310 Arc::new(Scripted::failing("primary", rate_limited())),
311 Some(target(secondary.clone())),
312 );
313 fb.chat(request().with_model_override("primary-only-model"))
314 .await
315 .unwrap();
316 assert_eq!(
317 secondary.seen_model_override.lock().unwrap().as_deref(),
318 Some("fallback-model")
319 );
320 }
321
322 #[tokio::test]
323 async fn falls_back_when_the_stream_fails_after_opening() {
324 let secondary = Arc::new(Scripted::new("secondary"));
325 let fb = FallbackProvider::new(
326 Arc::new(Scripted::failing_mid_stream("primary", rate_limited())),
327 Some(target(secondary.clone())),
328 );
329 let events = collect(fb.chat_stream(request()).await.unwrap()).await;
330 assert!(
331 matches!(&events[0], Ok(StreamEvent::TextDelta { delta }) if delta == "secondary"),
332 "expected the fallback stream, got {events:?}"
333 );
334 }
335
336 #[tokio::test]
337 async fn healthy_primary_stream_is_passed_through_intact() {
338 let secondary = Arc::new(Scripted::new("secondary"));
339 let fb = FallbackProvider::new(
340 Arc::new(Scripted::new("primary")),
341 Some(target(secondary.clone())),
342 );
343 let events = collect(fb.chat_stream(request()).await.unwrap()).await;
344 assert_eq!(events.len(), 2, "buffered first event must not be dropped");
345 assert!(matches!(&events[0], Ok(StreamEvent::TextDelta { delta }) if delta == "primary"));
346 assert!(matches!(&events[1], Ok(StreamEvent::Done { .. })));
347 assert_eq!(secondary.calls.load(Ordering::SeqCst), 0);
348 }
349
350 #[tokio::test]
351 async fn capabilities_are_intersected_with_the_fallback() {
352 let primary = Scripted::new("primary").with_caps(ProviderCapabilities {
353 streaming: true,
354 vision: true,
355 max_context_tokens: 200_000,
356 ..Default::default()
357 });
358 let secondary = Scripted::new("secondary").with_caps(ProviderCapabilities {
359 streaming: true,
360 vision: false,
361 max_context_tokens: 8_000,
362 ..Default::default()
363 });
364 let fb = FallbackProvider::new(Arc::new(primary), Some(target(Arc::new(secondary))));
365 let caps = fb.capabilities();
366 assert!(caps.streaming);
367 assert!(
368 !caps.vision,
369 "must not advertise a capability the fallback lacks"
370 );
371 assert_eq!(caps.max_context_tokens, 8_000);
372 }
373
374 #[tokio::test]
375 async fn without_a_fallback_errors_propagate() {
376 let fb =
377 FallbackProvider::new(Arc::new(Scripted::failing("primary", rate_limited())), None);
378 assert!(matches!(
379 fb.chat(request()).await,
380 Err(LlmError::RateLimited { .. })
381 ));
382 }
383
384 #[tokio::test]
385 async fn fallback_failure_chains_primary_error() {
386 let secondary = Arc::new(Scripted::failing(
387 "secondary",
388 LlmError::ApiError {
389 provider: "secondary".into(),
390 status: 503,
391 message: "down".into(),
392 },
393 ));
394 let fb = FallbackProvider::new(
395 Arc::new(Scripted::failing("primary", rate_limited())),
396 Some(target(secondary.clone())),
397 );
398 let err = fb.chat(request()).await.expect_err("both must fail");
399 match err {
400 LlmError::FallbackExhausted { primary, fallback } => {
401 assert!(matches!(*primary, LlmError::RateLimited { .. }));
402 assert!(matches!(*fallback, LlmError::ApiError { status: 503, .. }));
403 }
404 other => panic!("expected FallbackExhausted, got {other:?}"),
405 }
406 assert_eq!(secondary.calls.load(Ordering::SeqCst), 1);
407 }
408
409 #[tokio::test]
410 async fn stream_fallback_failure_chains_primary_error() {
411 let secondary = Arc::new(Scripted::failing(
412 "secondary",
413 LlmError::Network("unreachable".into()),
414 ));
415 let fb = FallbackProvider::new(
416 Arc::new(Scripted::failing_mid_stream("primary", rate_limited())),
417 Some(target(secondary.clone())),
418 );
419 let result = fb.chat_stream(request()).await;
420 let err = match result {
421 Ok(_) => panic!("expected FallbackExhausted, got Ok"),
422 Err(e) => e,
423 };
424 assert!(
425 matches!(err, LlmError::FallbackExhausted { .. }),
426 "expected FallbackExhausted, got {err:?}"
427 );
428 }
429
430 #[tokio::test]
431 async fn deterministic_failure_is_not_wrapped_in_fallback_exhausted() {
432 let secondary = Arc::new(Scripted::new("secondary"));
433 let fb = FallbackProvider::new(
434 Arc::new(Scripted::failing(
435 "primary",
436 LlmError::ContextLengthExceeded {
437 tokens_used: 10,
438 limit: 5,
439 },
440 )),
441 Some(target(secondary.clone())),
442 );
443 let surfaced = fb.chat(request()).await.expect_err("must fail");
444 assert!(
445 !matches!(surfaced, LlmError::FallbackExhausted { .. }),
446 "non-retryable primary error must not be wrapped, got {surfaced:?}"
447 );
448 assert_eq!(secondary.calls.load(Ordering::SeqCst), 0);
449 }
450}
Deleted agent/llm/lib.rs
@@ -1,9 +0,0 @@
1pub mod embed;
2pub mod error;
3pub mod fallback;
4pub mod provider;
5pub mod reasoning;
6pub mod register;
7pub mod skill;
8pub mod token;
9pub mod types;
Deleted agent/llm/reasoning.rs
@@ -1,12 +0,0 @@
1use crate::error::LlmError;
2use crate::provider::LlmProvider;
3use crate::types::{ChatRequest, ChatResponse};
4
5#[async_trait::async_trait]
6pub trait ReasoningProvider: LlmProvider {
7 async fn chat_with_reasoning(
8 &self,
9 request: ChatRequest,
10 reasoning_budget: usize,
11 ) -> Result<(ChatResponse, String), LlmError>;
12}
Deleted agent/llm/register.rs
@@ -1,188 +0,0 @@
1use crate::provider::LlmProvider;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub struct ProviderKey {
7 pub provider_id: String,
8 pub model_id: String,
9}
10
11impl ProviderKey {
12 pub fn new(provider_id: impl Into<String>, model_id: impl Into<String>) -> Self {
13 Self {
14 provider_id: provider_id.into(),
15 model_id: model_id.into(),
16 }
17 }
18}
19
20impl std::fmt::Display for ProviderKey {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "{}/{}", self.provider_id, self.model_id)
23 }
24}
25
26#[derive(Default)]
27pub struct ProviderRegistry {
28 providers: HashMap<ProviderKey, Arc<dyn LlmProvider>>,
29}
30
31impl ProviderRegistry {
32 pub fn new() -> Self {
33 Self {
34 providers: HashMap::new(),
35 }
36 }
37
38 /// Displacement is logged at warn level: most call sites ignore the
39 pub fn register(&mut self, provider: Arc<dyn LlmProvider>) -> Option<Arc<dyn LlmProvider>> {
40 let key = ProviderKey::new(provider.provider_id(), provider.model_id());
41 let displaced = self.providers.insert(key.clone(), provider);
42 if displaced.is_some() {
43 tracing::warn!(
44 provider_id = %key.provider_id,
45 model_id = %key.model_id,
46 "provider re-registered; previous instance displaced",
47 );
48 }
49 displaced
50 }
51
52 pub fn get(&self, provider_id: &str, model_id: &str) -> Option<Arc<dyn LlmProvider>> {
53 self.providers
54 .get(&ProviderKey::new(provider_id, model_id))
55 .cloned()
56 }
57
58 pub fn any_for_provider(&self, provider_id: &str) -> Option<Arc<dyn LlmProvider>> {
59 self.providers
60 .iter()
61 .filter(|(k, _)| k.provider_id == provider_id)
62 .min_by(|(a, _), (b, _)| a.model_id.cmp(&b.model_id))
63 .map(|(_, v)| Arc::clone(v))
64 }
65
66 pub fn contains(&self, provider_id: &str, model_id: &str) -> bool {
67 self.providers
68 .contains_key(&ProviderKey::new(provider_id, model_id))
69 }
70
71 pub fn remove(&mut self, provider_id: &str, model_id: &str) -> Option<Arc<dyn LlmProvider>> {
72 self.providers
73 .remove(&ProviderKey::new(provider_id, model_id))
74 }
75
76 pub fn keys(&self) -> Vec<ProviderKey> {
77 let mut keys: Vec<_> = self.providers.keys().cloned().collect();
78 keys.sort();
79 keys
80 }
81
82 pub fn provider_ids(&self) -> Vec<String> {
83 let mut ids: Vec<_> = self
84 .providers
85 .keys()
86 .map(|k| k.provider_id.clone())
87 .collect();
88 ids.sort();
89 ids.dedup();
90 ids
91 }
92
93 pub fn len(&self) -> usize {
94 self.providers.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
98 self.providers.is_empty()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::error::LlmError;
106 use crate::types::{ChatRequest, ChatResponse, ProviderCapabilities, StreamEvent};
107 use std::pin::Pin;
108 use tokio_stream::Stream;
109
110 struct Stub {
111 provider: String,
112 model: String,
113 }
114
115 impl Stub {
116 fn arc(provider: &str, model: &str) -> Arc<dyn LlmProvider> {
117 Arc::new(Self {
118 provider: provider.to_string(),
119 model: model.to_string(),
120 })
121 }
122 }
123
124 #[async_trait::async_trait]
125 impl LlmProvider for Stub {
126 fn provider_id(&self) -> &str {
127 &self.provider
128 }
129 fn model_id(&self) -> &str {
130 &self.model
131 }
132 fn capabilities(&self) -> ProviderCapabilities {
133 ProviderCapabilities::default()
134 }
135 async fn chat(&self, _: ChatRequest) -> Result<ChatResponse, LlmError> {
136 Err(LlmError::Provider("stub".into()))
137 }
138 async fn chat_stream(
139 &self,
140 _: ChatRequest,
141 ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError>
142 {
143 Err(LlmError::Provider("stub".into()))
144 }
145 }
146
147 #[test]
148 fn models_of_same_provider_coexist() {
149 let mut reg = ProviderRegistry::new();
150 reg.register(Stub::arc("openai", "gpt-4o"));
151 reg.register(Stub::arc("openai", "o3"));
152
153 assert_eq!(reg.len(), 2);
154 assert!(reg.get("openai", "gpt-4o").is_some());
155 assert!(reg.get("openai", "o3").is_some());
156 assert_eq!(reg.provider_ids(), vec!["openai"]);
157 }
158
159 #[test]
160 fn re_registering_same_pair_reports_displacement() {
161 let mut reg = ProviderRegistry::new();
162 assert!(reg.register(Stub::arc("openai", "o3")).is_none());
163 assert!(reg.register(Stub::arc("openai", "o3")).is_some());
164 assert_eq!(reg.len(), 1);
165 }
166
167 #[test]
168 fn lookup_miss_and_removal() {
169 let mut reg = ProviderRegistry::new();
170 reg.register(Stub::arc("openai", "o3"));
171 assert!(reg.get("openai", "nope").is_none());
172 assert!(reg.get("anthropic", "o3").is_none());
173 assert!(reg.remove("openai", "o3").is_some());
174 assert!(reg.is_empty());
175 }
176
177 #[test]
178 fn any_for_provider_is_deterministic() {
179 let mut reg = ProviderRegistry::new();
180 reg.register(Stub::arc("openai", "gpt-4o"));
181 reg.register(Stub::arc("openai", "o3"));
182 for _ in 0..32 {
183 let picked = reg.any_for_provider("openai").unwrap();
184 assert_eq!(picked.model_id(), "gpt-4o");
185 }
186 assert!(reg.any_for_provider("missing").is_none());
187 }
188}
Deleted agent/llm/skill.rs
@@ -1,230 +0,0 @@
1use crate::error::LlmError;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Skill {
7 pub name: String,
8 pub description: String,
9 pub template: String,
10 #[serde(default)]
11 pub parameters: Vec<SkillParameter>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SkillParameter {
16 pub name: String,
17 pub description: String,
18 #[serde(default)]
19 pub required: bool,
20 pub default: Option<String>,
21}
22
23impl Skill {
24 pub fn render(&self, params: &HashMap<String, String>) -> Result<String, LlmError> {
25 for param in &self.parameters {
26 if param.required && !params.contains_key(&param.name) && param.default.is_none() {
27 return Err(LlmError::SkillMissingParameter {
28 skill: self.name.clone(),
29 parameter: param.name.clone(),
30 });
31 }
32 }
33
34 let mut out = String::with_capacity(self.template.len());
35 let mut rest = self.template.as_str();
36
37 while let Some(open) = rest.find("{{") {
38 let after_open = &rest[open + 2..];
39 let Some(close) = after_open.find("}}") else {
40 break;
41 };
42 let end = open + 2 + close + 2;
43
44 match self.resolve(after_open[..close].trim(), params) {
45 Some(value) => {
46 out.push_str(&rest[..open]);
47 out.push_str(value);
48 }
49 None => out.push_str(&rest[..end]),
50 }
51 rest = &rest[end..];
52 }
53
54 out.push_str(rest);
55 Ok(out)
56 }
57
58 fn resolve<'a>(&'a self, key: &str, params: &'a HashMap<String, String>) -> Option<&'a str> {
59 let declared = self.parameters.iter().find(|p| p.name == key)?;
60 Some(
61 params
62 .get(key)
63 .or(declared.default.as_ref())
64 .map_or("", String::as_str),
65 )
66 }
67}
68
69#[derive(Debug, Default)]
70pub struct SkillRegistry {
71 skills: HashMap<String, Skill>,
72}
73
74impl SkillRegistry {
75 pub fn new() -> Self {
76 Self {
77 skills: HashMap::new(),
78 }
79 }
80
81 pub fn register(&mut self, skill: Skill) -> Option<Skill> {
82 self.skills.insert(skill.name.clone(), skill)
83 }
84
85 pub fn get(&self, name: &str) -> Option<&Skill> {
86 self.skills.get(name)
87 }
88
89 pub fn render(&self, name: &str, params: &HashMap<String, String>) -> Result<String, LlmError> {
90 self.get(name)
91 .ok_or_else(|| LlmError::SkillNotFound(name.to_string()))?
92 .render(params)
93 }
94
95 pub fn names(&self) -> Vec<String> {
96 self.skills.keys().cloned().collect()
97 }
98
99 pub fn count(&self) -> usize {
100 self.skills.len()
101 }
102
103 pub fn is_empty(&self) -> bool {
104 self.skills.is_empty()
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 fn param(name: &str, required: bool, default: Option<&str>) -> SkillParameter {
113 SkillParameter {
114 name: name.to_string(),
115 description: String::new(),
116 required,
117 default: default.map(str::to_string),
118 }
119 }
120
121 fn skill(template: &str, parameters: Vec<SkillParameter>) -> Skill {
122 Skill {
123 name: "s".to_string(),
124 description: String::new(),
125 template: template.to_string(),
126 parameters,
127 }
128 }
129
130 #[test]
131 fn substitutes_declared_parameters() {
132 let s = skill("hello {{who}}!", vec![param("who", true, None)]);
133 let params = HashMap::from([("who".to_string(), "world".to_string())]);
134 assert_eq!(s.render(&params).unwrap(), "hello world!");
135 }
136
137 #[test]
138 fn does_not_expand_placeholders_injected_through_values() {
139 let s = skill(
140 "{{a}}|{{b}}",
141 vec![param("a", true, None), param("b", true, None)],
142 );
143 let params = HashMap::from([
144 ("a".to_string(), "{{b}}".to_string()),
145 ("b".to_string(), "SECRET".to_string()),
146 ]);
147 assert_eq!(s.render(&params).unwrap(), "{{b}}|SECRET");
148 }
149
150 #[test]
151 fn render_is_deterministic_regardless_of_map_order() {
152 let s = skill(
153 "{{a}}{{b}}{{c}}",
154 vec![
155 param("a", true, None),
156 param("b", true, None),
157 param("c", true, None),
158 ],
159 );
160 let params = HashMap::from([
161 ("a".to_string(), "{{c}}".to_string()),
162 ("b".to_string(), "{{a}}".to_string()),
163 ("c".to_string(), "z".to_string()),
164 ]);
165 let first = s.render(&params).unwrap();
166 for _ in 0..64 {
167 assert_eq!(s.render(&params).unwrap(), first);
168 }
169 assert_eq!(first, "{{c}}{{a}}z");
170 }
171
172 #[test]
173 fn undeclared_placeholder_is_left_verbatim() {
174 let s = skill("{{known}} {{unknown}}", vec![param("known", true, None)]);
175 let params = HashMap::from([
176 ("known".to_string(), "ok".to_string()),
177 ("unknown".to_string(), "injected".to_string()),
178 ]);
179 assert_eq!(s.render(&params).unwrap(), "ok {{unknown}}");
180 }
181
182 #[test]
183 fn falls_back_to_default_then_empty() {
184 let s = skill(
185 "[{{d}}][{{o}}]",
186 vec![param("d", false, Some("dv")), param("o", false, None)],
187 );
188 assert_eq!(s.render(&HashMap::new()).unwrap(), "[dv][]");
189 }
190
191 #[test]
192 fn missing_required_parameter_errors() {
193 let s = skill("{{x}}", vec![param("x", true, None)]);
194 assert!(matches!(
195 s.render(&HashMap::new()),
196 Err(LlmError::SkillMissingParameter { .. })
197 ));
198 }
199
200 #[test]
201 fn unclosed_placeholder_is_passed_through() {
202 let s = skill("a {{b", vec![param("b", false, None)]);
203 assert_eq!(s.render(&HashMap::new()).unwrap(), "a {{b");
204 }
205
206 #[test]
207 fn multibyte_template_does_not_split_char_boundaries() {
208 let s = skill("前缀{{名字}}后缀", vec![param("名字", true, None)]);
209 let params = HashMap::from([("名字".to_string(), "世界".to_string())]);
210 assert_eq!(s.render(&params).unwrap(), "前缀世界后缀");
211 }
212
213 #[test]
214 fn registry_render_reports_unknown_skill() {
215 let reg = SkillRegistry::new();
216 assert!(matches!(
217 reg.render("nope", &HashMap::new()),
218 Err(LlmError::SkillNotFound(_))
219 ));
220 }
221
222 #[test]
223 fn registry_register_returns_displaced_skill() {
224 let mut reg = SkillRegistry::new();
225 assert!(reg.register(skill("v1", vec![])).is_none());
226 let old = reg.register(skill("v2", vec![])).expect("displaced");
227 assert_eq!(old.template, "v1");
228 assert_eq!(reg.count(), 1);
229 }
230}
Deleted agent/llm/token.rs
@@ -1,137 +0,0 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
4pub struct TokenUsageStats {
5 pub input_tokens: usize,
6 pub output_tokens: usize,
7 pub reasoning_tokens: Option<usize>,
8}
9
10impl TokenUsageStats {
11 pub fn new(input: usize, output: usize) -> Self {
12 Self {
13 input_tokens: input,
14 output_tokens: output,
15 reasoning_tokens: None,
16 }
17 }
18
19 pub fn with_reasoning(mut self, tokens: usize) -> Self {
20 self.reasoning_tokens = Some(tokens);
21 self
22 }
23
24 pub fn total(&self) -> usize {
25 self.input_tokens
26 .saturating_add(self.output_tokens)
27 .saturating_add(self.reasoning_tokens.unwrap_or(0))
28 }
29
30 pub fn merge(&mut self, other: &TokenUsageStats) {
31 self.input_tokens = self.input_tokens.saturating_add(other.input_tokens);
32 self.output_tokens = self.output_tokens.saturating_add(other.output_tokens);
33 match (self.reasoning_tokens, other.reasoning_tokens) {
34 (Some(a), Some(b)) => self.reasoning_tokens = Some(a.saturating_add(b)),
35 (None, Some(b)) => self.reasoning_tokens = Some(b),
36 _ => {}
37 }
38 }
39}
40
41const LATIN_CHARS_PER_TOKEN: usize = 4;
42
43pub const IMAGE_TOKENS_LOW: usize = 85;
44
45pub const IMAGE_TOKENS_HIGH: usize = 765;
46
47pub const MESSAGE_OVERHEAD_TOKENS: usize = 4;
48
49pub fn estimate_text_tokens(text: &str) -> usize {
50 let (cjk, other) = text.chars().fold((0usize, 0usize), |(cjk, other), c| {
51 if is_cjk(c) {
52 (cjk + 1, other)
53 } else {
54 (cjk, other + 1)
55 }
56 });
57 cjk + other.div_ceil(LATIN_CHARS_PER_TOKEN)
58}
59
60pub fn estimate_json_tokens(value: &serde_json::Value) -> usize {
61 serde_json::to_string(value)
62 .map(|s| estimate_text_tokens(&s))
63 .unwrap_or(0)
64}
65
66fn is_cjk(c: char) -> bool {
67 matches!(c as u32,
68 0x2E80..=0x303F
69 | 0x3040..=0x30FF
70 | 0x3400..=0x4DBF
71 | 0x4E00..=0x9FFF
72 | 0xA960..=0xA97F
73 | 0xAC00..=0xD7FF
74 | 0xF900..=0xFAFF
75 | 0xFF00..=0xFFEF
76 | 0x20000..=0x3FFFF
77 )
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn total_and_merge_accumulate_reasoning() {
86 let mut a = TokenUsageStats::new(10, 5).with_reasoning(3);
87 assert_eq!(a.total(), 18);
88 a.merge(&TokenUsageStats::new(1, 1).with_reasoning(1));
89 assert_eq!(
90 a,
91 TokenUsageStats {
92 input_tokens: 11,
93 output_tokens: 6,
94 reasoning_tokens: Some(4),
95 }
96 );
97 }
98
99 #[test]
100 fn merge_adopts_reasoning_when_absent_locally() {
101 let mut a = TokenUsageStats::new(1, 1);
102 a.merge(&TokenUsageStats::new(0, 0).with_reasoning(7));
103 assert_eq!(a.reasoning_tokens, Some(7));
104 }
105
106 #[test]
107 fn merge_keeps_reasoning_when_other_has_none() {
108 let mut a = TokenUsageStats::new(1, 1).with_reasoning(7);
109 a.merge(&TokenUsageStats::new(1, 1));
110 assert_eq!(a.reasoning_tokens, Some(7));
111 }
112
113 #[test]
114 fn total_saturates_instead_of_overflowing() {
115 assert_eq!(TokenUsageStats::new(usize::MAX, 10).total(), usize::MAX);
116 }
117
118 #[test]
119 fn cjk_counts_by_character_not_byte() {
120 let zh = "这是一段中文测试文本内容";
121 assert_eq!(zh.chars().count(), 12);
122 assert_eq!(zh.len(), 36);
123 assert_eq!(estimate_text_tokens(zh), 12);
124 }
125
126 #[test]
127 fn latin_uses_four_chars_per_token() {
128 assert_eq!(estimate_text_tokens("abcdefgh"), 2);
129 assert_eq!(estimate_text_tokens("abcde"), 2);
130 assert_eq!(estimate_text_tokens(""), 0);
131 }
132
133 #[test]
134 fn mixed_script_counts_each_run_appropriately() {
135 assert_eq!(estimate_text_tokens("abcd中文"), 3);
136 }
137}
Deleted agent/llm/types.rs
@@ -1,13 +0,0 @@
1pub mod capabilities;
2pub mod message;
3pub mod request;
4pub mod response;
5pub mod stream;
6pub mod tools;
7
8pub use capabilities::*;
9pub use message::*;
10pub use request::*;
11pub use response::*;
12pub use stream::*;
13pub use tools::*;
Deleted agent/lprovider/tests/affinity.rs

Diff too large to display.

Deleted agent/lprovider/tests/billing.rs

Diff too large to display.

Deleted agent/lprovider/tests/error.rs

Diff too large to display.

Deleted agent/lprovider/tests/selector.rs

Diff too large to display.

Deleted agent/lprovider/admin.proto
@@ -1,479 +0,0 @@
1syntax = "proto3";
2package lprovider.admin.v1;
3
4import "google/protobuf/struct.proto";
5
6service LproviderAdmin {
7 rpc GetConfig(GetConfigRequest) returns (GetConfigResponse);
8 rpc UpdateConfig(UpdateConfigRequest) returns (UpdateConfigResponse);
9 rpc ReloadConfig(ReloadConfigRequest) returns (ReloadConfigResponse);
10 rpc RestartService(RestartServiceRequest) returns (RestartServiceResponse);
11
12 rpc ListUpstreams(ListUpstreamsRequest) returns (ListUpstreamsResponse);
13 rpc GetUpstream(GetUpstreamRequest) returns (Upstream);
14
15 rpc ListUpstreamKeys(ListUpstreamKeysRequest) returns (ListUpstreamKeysResponse);
16 rpc GetUpstreamKey(GetUpstreamKeyRequest) returns (UpstreamKey);
17
18 rpc ListBindings(ListBindingsRequest) returns (ListBindingsResponse);
19 rpc InvalidateBinding(InvalidateBindingRequest) returns (InvalidateBindingResponse);
20
21 rpc QueryAudit(QueryAuditRequest) returns (QueryAuditResponse);
22 rpc AggregateBilling(AggregateBillingRequest) returns (AggregateBillingResponse);
23 rpc ListTenantQuotas(ListTenantQuotasRequest) returns (ListTenantQuotasResponse);
24 rpc UpsertTenantQuota(UpsertTenantQuotaRequest) returns (TenantQuota);
25
26 rpc GetWallet(GetWalletRequest) returns (Wallet);
27 rpc ListWallets(ListWalletsRequest) returns (ListWalletsResponse);
28 rpc TopupWallet(TopupWalletRequest) returns (Wallet);
29 rpc RefundWallet(RefundWalletRequest) returns (Wallet);
30 rpc AdjustWallet(AdjustWalletRequest) returns (Wallet);
31 rpc FreezeWallet(FreezeWalletRequest) returns (Wallet);
32 rpc UnfreezeWallet(UnfreezeWalletRequest) returns (Wallet);
33 rpc DisableWallet(DisableWalletRequest) returns (Wallet);
34 rpc ListWalletLedger(ListWalletLedgerRequest) returns (ListWalletLedgerResponse);
35 rpc ListWalletFreezeLog(ListWalletFreezeLogRequest) returns (ListWalletFreezeLogResponse);
36
37 rpc GetRuntimeStatus(GetRuntimeStatusRequest) returns (RuntimeStatus);
38 rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse);
39}
40
41message Upstream {
42 string code = 1;
43 string provider_type = 2;
44 string base_url = 3;
45 string default_model = 4;
46 repeated string capabilities = 5;
47 int32 context_window = 6;
48 int32 max_output = 7;
49 bool enabled = 8;
50 int32 cost_tier = 9;
51 int32 quality_tier = 10;
52 int32 latency_tier = 11;
53 repeated string tags = 12;
54}
55
56message UpstreamKey {
57 string upstream_code = 1;
58 string label = 2;
59 string credential_ref = 3;
60 string key_fingerprint = 4;
61 string status = 5;
62 int32 qps_limit = 6;
63 int32 rpm_limit = 7;
64 int64 cooldown_until_unix = 8;
65 KeyStats stats = 9;
66}
67
68message KeyStats {
69 int64 success_count = 1;
70 int64 error_count = 2;
71 int64 rate_limited_count = 3;
72 int32 in_flight = 4;
73}
74
75message SessionBinding {
76 string model_alias = 1;
77 string session_key = 2;
78 string upstream_code = 3;
79 string key_label = 4;
80 string prefix_hash = 5;
81 int64 bound_at_unix = 6;
82 int64 expires_at_unix = 7;
83 int32 last_hit_tokens = 8;
84 int64 hit_count = 9;
85}
86
87message CallRecord {
88 int64 id = 1;
89 string request_id = 2;
90 string trace_id = 3;
91 int64 tenant_id = 4;
92 int64 api_key_id = 5;
93 string subject_type = 6;
94 int64 subject_id = 7;
95 int64 wallet_id = 8;
96 bool anonymous = 9;
97 string subject_path = 10;
98 string session_key = 11;
99 string model_alias = 12;
100 string upstream_code = 13;
101 string requested_model = 14;
102 string chosen_strategy = 15;
103 int32 tier_cost = 16;
104 int32 tier_quality = 17;
105 int32 tier_latency = 18;
106 string key_label = 19;
107 string caller = 20;
108 string caller_service = 21;
109 int32 priority = 22;
110 bool streamed = 23;
111 string status = 24;
112 string error_kind = 25;
113 string error_message = 26;
114 int32 prompt_tokens = 27;
115 int32 completion_tokens = 28;
116 int32 cache_hit_tokens = 29;
117 int32 cache_miss_tokens = 30;
118 int32 billable_input_tokens = 31;
119 int32 billable_output_tokens = 32;
120 int64 points_input = 33;
121 int64 points_output = 34;
122 int64 points_cache_hit = 35;
123 int64 points_total = 36;
124 int64 points_rate_input = 37;
125 int64 points_rate_output = 38;
126 int64 points_rate_cache_hit = 39;
127 int64 request_started_at_ms = 40;
128 int64 response_completed_at_ms = 41;
129 int32 latency_ms = 42;
130 string client_ip = 43;
131 string user_agent = 44;
132 int64 created_at_ms = 45;
133}
134
135message BillingAggregate {
136 int64 id = 1;
137 int64 tenant_id = 2;
138 string subject_type = 3;
139 int64 subject_id = 4;
140 int64 period_start_ms = 5;
141 int64 period_end_ms = 6;
142 string period_kind = 7;
143 string model_alias = 8;
144 string upstream_code = 9;
145 int64 call_count = 10;
146 int64 success_count = 11;
147 int64 error_count = 12;
148 int64 total_input_tokens = 13;
149 int64 total_output_tokens = 14;
150 int64 total_cache_hit_tokens = 15;
151 int64 total_points = 16;
152 int64 updated_at_ms = 17;
153}
154
155message Wallet {
156 int64 wallet_id = 1;
157 string owner_type = 2;
158 int64 owner_id = 3;
159 int64 tenant_id = 4;
160 string status = 5;
161 int64 balance_points = 6;
162 int64 total_topped_up = 7;
163 int64 total_consumed = 8;
164 int64 total_refunded = 9;
165 int64 created_at_unix = 10;
166 int64 updated_at_unix = 11;
167}
168
169message GetWalletRequest {
170 int64 wallet_id = 1;
171}
172
173message ListWalletsRequest {
174 int64 tenant_id = 1;
175 string owner_type = 2;
176 int64 owner_id = 3;
177}
178
179message ListWalletsResponse {
180 repeated Wallet wallets = 1;
181}
182
183message TopupWalletRequest {
184 int64 wallet_id = 1;
185 int64 delta_points = 2;
186 string reason = 3;
187 string operator = 4;
188 string idempotency_key = 5;
189}
190
191message RefundWalletRequest {
192 int64 wallet_id = 1;
193 int64 delta_points = 2;
194 string request_id = 3;
195 string reason = 4;
196 string operator = 5;
197}
198
199message AdjustWalletRequest {
200 int64 wallet_id = 1;
201 int64 delta_points = 2;
202 string reason = 3;
203 string operator = 4;
204 string idempotency_key = 5;
205}
206
207message FreezeWalletRequest {
208 int64 wallet_id = 1;
209 string reason = 2;
210 string operator = 3;
211}
212
213message UnfreezeWalletRequest {
214 int64 wallet_id = 1;
215 string reason = 2;
216 string operator = 3;
217}
218
219message DisableWalletRequest {
220 int64 wallet_id = 1;
221 string reason = 2;
222 string operator = 3;
223}
224
225message WalletLedgerEntry {
226 int64 id = 1;
227 int64 wallet_id = 2;
228 string request_id = 3;
229 int64 delta_points = 4;
230 int64 balance_after = 5;
231 string kind = 6;
232 string reason = 7;
233 string operator = 8;
234 int64 created_at_unix = 9;
235}
236
237message ListWalletLedgerRequest {
238 int64 wallet_id = 1;
239 int64 from_unix = 2;
240 int64 to_unix = 3;
241}
242
243message ListWalletLedgerResponse {
244 repeated WalletLedgerEntry entries = 1;
245}
246
247message ListWalletFreezeLogRequest {
248 int64 wallet_id = 1;
249}
250
251message WalletFreezeLogEntry {
252 int64 id = 1;
253 int64 wallet_id = 2;
254 string action = 3;
255 string reason = 4;
256 string operator = 5;
257 int64 created_at_unix = 6;
258}
259
260message ListWalletFreezeLogResponse {
261 repeated WalletFreezeLogEntry entries = 1;
262}
263
264message TenantQuota {
265 int64 tenant_id = 1;
266 string subject_type = 2;
267 int64 subject_id = 3;
268 int64 monthly_quota_points = 4;
269 int64 monthly_quota_input_tokens = 5;
270 int64 monthly_quota_output_tokens = 6;
271 bool hard_limit = 7;
272 bool enabled = 8;
273 int64 updated_at_unix = 9;
274}
275
276message AffinityStats {
277 int64 bind_total = 1;
278 int64 hit_count = 2;
279 int64 miss_count = 3;
280}
281
282message UpstreamSummary {
283 string upstream_code = 1;
284 bool any_key_available = 2;
285 int32 keys_active = 3;
286 int32 keys_cooldown = 4;
287 double error_rate_5m = 5;
288}
289
290message ListTenantQuotasRequest {
291 int64 tenant_id = 1;
292 string subject_type = 2;
293 int64 subject_id = 3;
294}
295
296message ListTenantQuotasResponse {
297 repeated TenantQuota quotas = 1;
298}
299
300message UpsertTenantQuotaRequest {
301 TenantQuota quota = 1;
302}
303
304message RuntimeStatus {
305 string snapshot_version = 1;
306 int32 upstream_total = 2;
307 int32 upstream_active = 3;
308 int32 upstream_cooldown = 4;
309 bool redis_ok = 5;
310 bool db_ok = 6;
311 AffinityStats affinity = 7;
312 UpstreamSummary upstream_summary = 8;
313}
314
315message GetConfigRequest {
316 optional bool mask_secrets = 1;
317}
318
319message GetConfigResponse {
320 string config_path = 1;
321 string sha256 = 2;
322 int64 file_size_bytes = 3;
323 int64 loaded_at_unix = 4;
324 string content_yaml = 5;
325 repeated string warnings = 6;
326}
327
328message UpdateConfigRequest {
329 oneof payload {
330 string full_content_yaml = 1;
331 ConfigPatch patch = 2;
332 ConfigSectionPatch section_patch = 3;
333 }
334 bool reload_after_write = 4;
335 bool restart_after_write = 5;
336 string operator = 6;
337}
338
339message ConfigPatch {
340 repeated ConfigSectionPatch sections = 1;
341}
342
343message ConfigSectionPatch {
344 string path = 1;
345 google.protobuf.Struct value = 2;
346}
347
348message UpdateConfigResponse {
349 string sha256 = 1;
350 string backup_path = 2;
351 bool reloaded = 3;
352 bool restarted = 4;
353 repeated string warnings = 5;
354}
355
356message ReloadConfigRequest {
357 string operator = 1;
358}
359
360message ReloadConfigResponse {
361 string previous_sha256 = 1;
362 string current_sha256 = 2;
363 repeated string warnings = 3;
364 RuntimeStatus status = 4;
365}
366
367message RestartServiceRequest {
368 string operator = 1;
369 int32 drain_timeout_seconds = 2;
370 bool self_reexec = 3;
371 bool rolling = 4;
372 string target_instance_id = 5;
373}
374
375message RestartServiceResponse {
376 bool accepted = 1;
377 string reason = 2;
378 string instance_id = 3;
379}
380
381message InstanceInfo {
382 string instance_id = 1;
383 string cluster_name = 2;
384 string listen_addr = 3;
385 int64 started_at_unix = 4;
386 int32 pid = 5;
387 string config_sha256 = 6;
388 bool is_config_writer = 7;
389}
390
391message ListInstancesRequest {
392 string cluster_name = 1;
393}
394
395message ListInstancesResponse {
396 repeated InstanceInfo instances = 1;
397}
398
399// --- 以下请求/响应消息在文档 §6.2 中未逐字定义,按语义最小化补齐 ---
400
401message ListUpstreamsRequest {
402 repeated string codes = 1; // 空 = 全部
403}
404
405message ListUpstreamsResponse {
406 repeated Upstream upstreams = 1;
407}
408
409message GetUpstreamRequest {
410 string code = 1;
411}
412
413message ListUpstreamKeysRequest {
414 string upstream_code = 1; // 空 = 全部上游
415}
416
417message ListUpstreamKeysResponse {
418 repeated UpstreamKey keys = 1;
419}
420
421message GetUpstreamKeyRequest {
422 string upstream_code = 1;
423 string label = 2;
424}
425
426message ListBindingsRequest {
427 int64 tenant_id = 1; // 0 = 全部租户
428 string model_alias = 2; // 空 = 全部别名
429 string session_key = 3; // 空 = 全部会话
430}
431
432message ListBindingsResponse {
433 repeated SessionBinding bindings = 1;
434}
435
436message InvalidateBindingRequest {
437 int64 tenant_id = 1;
438 string model_alias = 2;
439 string session_key = 3;
440}
441
442message InvalidateBindingResponse {
443 bool invalidated = 1;
444}
445
446message QueryAuditRequest {
447 int64 tenant_id = 1; // 0 = 全部
448 string subject_type = 2;
449 int64 subject_id = 3; // 0 = 全部
450 string model_alias = 4;
451 string upstream_code = 5;
452 string status = 6; // 空 = 全部
453 int64 from_ms = 7;
454 int64 to_ms = 8;
455 int32 limit = 9; // 默认 100
456 int32 offset = 10;
457}
458
459message QueryAuditResponse {
460 repeated CallRecord records = 1;
461 int64 total = 2;
462}
463
464message AggregateBillingRequest {
465 int64 tenant_id = 1; // 0 = 全部
466 string subject_type = 2;
467 int64 subject_id = 3; // 0 = 全部
468 string period_kind = 4; // hour / day / month
469 int64 period_start_ms = 5;
470 int64 period_end_ms = 6;
471 string model_alias = 7;
472 string upstream_code = 8;
473}
474
475message AggregateBillingResponse {
476 repeated BillingAggregate aggregates = 1;
477}
478
479message GetRuntimeStatusRequest {}
Deleted agent/lprovider/admin_grpc.rs
@@ -1,1531 +0,0 @@
1use std::sync::Arc;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
4
5use redis::AsyncCommands;
6use time::OffsetDateTime;
7use tokio_stream::StreamExt;
8use tonic::{Request, Response, Status};
9use tracing::warn;
10
11use crate::affinity::AffinityManager;
12use crate::ai_grpc::status_from_err;
13use crate::audit::AuditService;
14use crate::config::RuntimeConfig;
15use crate::error::LproviderError;
16use crate::lprovider::admin::v1 as pb;
17use crate::quota::{QuotaService, TenantQuota};
18use crate::state::AppState;
19use crate::upstream::UpstreamManager;
20use crate::wallet::{WalletInfo, WalletService};
21
22const RELEASE_LOCK_SCRIPT: &str = r#"
23if redis.call('GET', KEYS[1]) == ARGV[1] then
24 return redis.call('DEL', KEYS[1])
25end
26return 0
27"#;
28
29const RENEW_LOCK_SCRIPT: &str = r#"
30if redis.call('GET', KEYS[1]) == ARGV[1] then
31 return redis.call('EXPIRE', KEYS[1], tonumber(ARGV[2]))
32end
33return 0
34"#;
35
36#[derive(Clone)]
37pub struct AdminGrpc {
38 pub state: Arc<AppState>,
39 pub manager: Arc<UpstreamManager>,
40 pub affinity: Arc<AffinityManager>,
41 pub wallets: Arc<WalletService>,
42 pub quotas: Arc<QuotaService>,
43 pub audit: Arc<AuditService>,
44}
45
46struct ConfigLockGuard {
47 key: String,
48 token: String,
49 redis: Arc<cache::RedisCache>,
50 released: Arc<AtomicBool>,
51}
52
53impl Drop for ConfigLockGuard {
54 fn drop(&mut self) {
55 self.released.store(true, Ordering::Relaxed);
56 let redis = self.redis.clone();
57 let key = self.key.clone();
58 let token = self.token.clone();
59 tokio::spawn(async move {
60 let mut c = redis.connection();
61 let script = redis::Script::new(RELEASE_LOCK_SCRIPT);
62 let _: redis::RedisResult<i64> =
63 script.key(&key).arg(&token).invoke_async(&mut c).await;
64 });
65 }
66}
67
68impl AdminGrpc {
69 pub fn new(
70 state: Arc<AppState>,
71 manager: Arc<UpstreamManager>,
72 affinity: Arc<AffinityManager>,
73 ) -> Self {
74 let db = state.db.clone().map(Arc::new);
75 Self {
76 state,
77 manager,
78 affinity,
79 wallets: Arc::new(WalletService::new(db.clone())),
80 quotas: Arc::new(QuotaService::new(db.clone())),
81 audit: Arc::new(AuditService::new(db)),
82 }
83 }
84
85 async fn acquire_config_lock(&self) -> Result<Option<ConfigLockGuard>, LproviderError> {
86 let Some(redis) = &self.state.redis else {
87 return Ok(None);
88 };
89 let key = format!("{}:lock:config_writer", self.affinity.key_prefix());
90 let token = format!("{}:{:016x}", self.state.instance_id, rand::random::<u64>());
91 let mut conn = redis.connection();
92 let ok: bool = conn.set_nx(&key, &token).await.map_err(redis_err)?;
93 if !ok {
94 return Err(LproviderError::ResourceLocked(
95 "config_writer lock held by another instance".into(),
96 ));
97 }
98 let _: () = conn.expire(&key, 30).await.map_err(redis_err)?;
99 let released = Arc::new(AtomicBool::new(false));
100 let renew_flag = released.clone();
101 let renew_redis = redis.clone();
102 let renew_key = key.clone();
103 let renew_token = token.clone();
104 tokio::spawn(async move {
105 loop {
106 tokio::time::sleep(Duration::from_secs(10)).await;
107 if renew_flag.load(Ordering::Relaxed) {
108 break;
109 }
110 let mut c = renew_redis.connection();
111 let script = redis::Script::new(RENEW_LOCK_SCRIPT);
112 let res: redis::RedisResult<i64> = script
113 .key(&renew_key)
114 .arg(&renew_token)
115 .arg(30i64)
116 .invoke_async(&mut c)
117 .await;
118 if !matches!(res, Ok(1)) {
119 break;
120 }
121 }
122 });
123 Ok(Some(ConfigLockGuard {
124 key,
125 token,
126 redis: redis.clone(),
127 released,
128 }))
129 }
130
131 #[allow(clippy::too_many_arguments)]
132 async fn audit_write(
133 &self,
134 operator: &str,
135 rpc: &str,
136 target: Option<&str>,
137 summary: Option<&str>,
138 effect_mode: Option<&str>,
139 result: &str,
140 error: Option<&str>,
141 ) {
142 if let Err(e) = self
143 .audit
144 .admin_audit(
145 operator,
146 rpc,
147 Some(&self.state.instance_id),
148 target,
149 summary,
150 None,
151 effect_mode,
152 result,
153 error,
154 )
155 .await
156 {
157 warn!(rpc, error = %e, "admin audit write failed");
158 }
159 }
160
161 async fn build_runtime_status(&self) -> pb::RuntimeStatus {
162 let snapshot = self.manager.snapshot().await;
163 let mut active = 0i32;
164 let mut cooldown = 0i32;
165 for up in &snapshot.upstreams {
166 for k in &up.keys {
167 let in_cooldown = match *k.cooldown_until.read().await {
168 Some(i) => !i.saturating_duration_since(Instant::now()).is_zero(),
169 None => false,
170 };
171 if in_cooldown {
172 cooldown += 1;
173 } else {
174 active += 1;
175 }
176 }
177 }
178 pb::RuntimeStatus {
179 snapshot_version: self.state.snapshot_version(),
180 upstream_total: snapshot.upstreams.len() as i32,
181 upstream_active: active,
182 upstream_cooldown: cooldown,
183 redis_ok: self.state.redis_ok().await,
184 db_ok: self.state.db_writable().await,
185 affinity: Some(pb::AffinityStats {
186 bind_total: self.state.affinity.bind_total.load(Ordering::Relaxed),
187 hit_count: self.state.affinity.hit_count.load(Ordering::Relaxed),
188 miss_count: self.state.affinity.miss_count.load(Ordering::Relaxed),
189 }),
190 upstream_summary: Some(pb::UpstreamSummary {
191 upstream_code: "<all>".into(),
192 any_key_available: active > 0,
193 keys_active: active,
194 keys_cooldown: cooldown,
195 error_rate_5m: 0.0,
196 }),
197 }
198 }
199
200 async fn reload_runtime(&self) -> Result<(), LproviderError> {
201 let mut cli = self.state.cli.clone();
202 cli.config = self.state.runtime.read().await.config_path.clone();
203 let new_rt = RuntimeConfig::load(&cli)?;
204 self.manager.reload(new_rt.lp())?;
205 *self.state.runtime.write().await = new_rt;
206 Ok(())
207 }
208}
209
210fn redis_err(e: redis::RedisError) -> LproviderError {
211 LproviderError::Redis(e.to_string())
212}
213
214fn redis_status(e: redis::RedisError) -> Status {
215 status_from_err(&LproviderError::Redis(e.to_string()))
216}
217
218fn operator_of(req_operator: &str) -> String {
219 let t = req_operator.trim();
220 if t.is_empty() {
221 "unknown".to_string()
222 } else {
223 t.to_string()
224 }
225}
226
227fn now_unix() -> i64 {
228 SystemTime::now()
229 .duration_since(UNIX_EPOCH)
230 .map(|d| d.as_secs() as i64)
231 .unwrap_or(0)
232}
233
234fn unix_ms(dt: OffsetDateTime) -> i64 {
235 dt.unix_timestamp() * 1000 + dt.millisecond() as i64
236}
237
238fn mask_secrets(content: &str) -> String {
239 content
240 .lines()
241 .map(|line| {
242 if line.contains("bearer_token") || line.contains("api_key") {
243 match line.find(':') {
244 Some(idx) => format!("{}: \"***\"", &line[..idx]),
245 None => line.to_string(),
246 }
247 } else {
248 line.to_string()
249 }
250 })
251 .collect::<Vec<_>>()
252 .join("\n")
253}
254
255fn infra_changed(old: &config::AppConfig, new: &config::AppConfig) -> bool {
256 let o = &old.lprovider;
257 let n = &new.lprovider;
258 o.bind != n.bind
259 || o.port != n.port
260 || o.admin_bind != n.admin_bind
261 || o.admin_port != n.admin_port
262 || o.enabled != n.enabled
263 || o.max_context_tokens != n.max_context_tokens
264 || o.max_output_tokens != n.max_output_tokens
265 || o.request_timeout_ms != n.request_timeout_ms
266 || o.redis.key_prefix != n.redis.key_prefix
267 || o.redis.session_affinity_ttl_seconds != n.redis.session_affinity_ttl_seconds
268 || o.redis.min_prefix_tokens != n.redis.min_prefix_tokens
269 || o.redis.prefix_token_budget != n.redis.prefix_token_budget
270 || o.redis.cooldown_seconds != n.redis.cooldown_seconds
271 || o.secret.envelope_key_env != n.secret.envelope_key_env
272 || serde_json::to_string(&old.db).ok() != serde_json::to_string(&new.db).ok()
273}
274
275fn set_dotted(
276 doc: &mut serde_yaml::Value,
277 path: &str,
278 value: serde_yaml::Value,
279) -> Result<(), LproviderError> {
280 let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
281 if parts.is_empty() {
282 return Err(LproviderError::InvalidRequest("empty patch path".into()));
283 }
284 let mut cur: &mut serde_yaml::Value = doc;
285 for part in &parts[..parts.len() - 1] {
286 if let Ok(idx) = part.parse::<usize>() {
287 cur = {
288 let seq = cur.as_sequence_mut().ok_or_else(|| {
289 LproviderError::InvalidRequest(format!(
290 "patch segment '{part}' is not a sequence"
291 ))
292 })?;
293 if idx >= seq.len() {
294 seq.resize(idx + 1, serde_yaml::Value::Null);
295 }
296 &mut seq[idx]
297 };
298 } else {
299 cur = {
300 let map = cur.as_mapping_mut().ok_or_else(|| {
301 LproviderError::InvalidRequest(format!(
302 "patch segment '{part}' is not a mapping"
303 ))
304 })?;
305 let key = serde_yaml::Value::String((*part).to_string());
306 if !map.contains_key(&key) {
307 map.insert(key.clone(), serde_yaml::Value::Null);
308 }
309 map.get_mut(&key).expect("inserted")
310 };
311 }
312 }
313 let last = parts[parts.len() - 1];
314 if let Ok(idx) = last.parse::<usize>() {
315 let seq = cur.as_sequence_mut().ok_or_else(|| {
316 LproviderError::InvalidRequest("patch parent is not a sequence".into())
317 })?;
318 if idx >= seq.len() {
319 seq.resize(idx + 1, serde_yaml::Value::Null);
320 }
321 seq[idx] = value;
322 } else {
323 let map = cur.as_mapping_mut().ok_or_else(|| {
324 LproviderError::InvalidRequest("patch parent is not a mapping".into())
325 })?;
326 map.insert(serde_yaml::Value::String(last.to_string()), value);
327 }
328 Ok(())
329}
330
331async fn compose_yaml(
332 req: &pb::UpdateConfigRequest,
333 config_path: &std::path::Path,
334) -> Result<String, LproviderError> {
335 match &req.payload {
336 Some(pb::update_config_request::Payload::FullContentYaml(y)) => Ok(y.clone()),
337 Some(pb::update_config_request::Payload::SectionPatch(p)) => {
338 let current = tokio::fs::read_to_string(config_path)
339 .await
340 .map_err(|e| LproviderError::Internal(format!("read config: {e}")))?;
341 let doc: serde_yaml::Value = serde_yaml::from_str(&current).map_err(|e| {
342 LproviderError::InvalidRequest(format!("current config not valid yaml: {e}"))
343 })?;
344 let value = p.value.as_ref().ok_or_else(|| {
345 LproviderError::InvalidRequest("section patch value missing".into())
346 })?;
347 let patched = apply_patch(doc, &p.path, value)?;
348 serde_yaml::to_string(&patched)
349 .map_err(|e| LproviderError::InvalidRequest(format!("serialize yaml: {e}")))
350 }
351 Some(pb::update_config_request::Payload::Patch(p)) => {
352 let current = tokio::fs::read_to_string(config_path)
353 .await
354 .map_err(|e| LproviderError::Internal(format!("read config: {e}")))?;
355 let mut doc: serde_yaml::Value = serde_yaml::from_str(&current).map_err(|e| {
356 LproviderError::InvalidRequest(format!("current config not valid yaml: {e}"))
357 })?;
358 for s in &p.sections {
359 let value = s.value.as_ref().ok_or_else(|| {
360 LproviderError::InvalidRequest("patch section value missing".into())
361 })?;
362 doc = apply_patch(doc, &s.path, value)?;
363 }
364 serde_yaml::to_string(&doc)
365 .map_err(|e| LproviderError::InvalidRequest(format!("serialize yaml: {e}")))
366 }
367 None => Err(LproviderError::InvalidRequest("no payload provided".into())),
368 }
369}
370
371fn apply_patch(
372 mut doc: serde_yaml::Value,
373 path: &str,
374 value: &prost_types::Struct,
375) -> Result<serde_yaml::Value, LproviderError> {
376 let json = crate::ai_grpc::prost_struct_to_json(value);
377 let yaml_val: serde_yaml::Value = serde_yaml::to_value(json)
378 .map_err(|e| LproviderError::InvalidRequest(format!("convert patch value: {e}")))?;
379 set_dotted(&mut doc, path, yaml_val)?;
380 Ok(doc)
381}
382
383fn wallet_to_proto(w: &WalletInfo) -> pb::Wallet {
384 pb::Wallet {
385 wallet_id: w.wallet_id,
386 owner_type: w.owner_type.clone(),
387 owner_id: w.owner_id,
388 tenant_id: w.tenant_id,
389 status: w.status.clone(),
390 balance_points: w.balance_points,
391 total_topped_up: w.total_topped_up,
392 total_consumed: w.total_consumed,
393 total_refunded: w.total_refunded,
394 created_at_unix: w.created_at.unix_timestamp(),
395 updated_at_unix: w.updated_at.unix_timestamp(),
396 }
397}
398
399fn quota_to_proto(q: &TenantQuota) -> pb::TenantQuota {
400 pb::TenantQuota {
401 tenant_id: q.tenant_id,
402 subject_type: q.subject_type.clone(),
403 subject_id: q.subject_id,
404 monthly_quota_points: q.monthly_quota_points,
405 monthly_quota_input_tokens: q.monthly_quota_input_tokens,
406 monthly_quota_output_tokens: q.monthly_quota_output_tokens,
407 hard_limit: q.hard_limit,
408 enabled: q.enabled,
409 updated_at_unix: 0,
410 }
411}
412
413fn proto_to_quota(q: &pb::TenantQuota) -> TenantQuota {
414 TenantQuota {
415 tenant_id: q.tenant_id,
416 subject_type: q.subject_type.clone(),
417 subject_id: q.subject_id,
418 monthly_quota_points: q.monthly_quota_points,
419 monthly_quota_input_tokens: q.monthly_quota_input_tokens,
420 monthly_quota_output_tokens: q.monthly_quota_output_tokens,
421 hard_limit: q.hard_limit,
422 enabled: q.enabled,
423 }
424}
425
426fn upstream_to_proto(u: &config::lprovider::LproviderUpstream) -> pb::Upstream {
427 pb::Upstream {
428 code: u.code.clone(),
429 provider_type: u.provider_type.clone(),
430 base_url: u.base_url.clone(),
431 default_model: u.default_model.clone(),
432 capabilities: u.capabilities.clone(),
433 context_window: u.context_window.min(i32::MAX as usize) as i32,
434 max_output: u.max_output.min(i32::MAX as usize) as i32,
435 enabled: u.enabled,
436 cost_tier: u.cost_tier,
437 quality_tier: u.quality_tier,
438 latency_tier: u.latency_tier,
439 tags: u.tags.clone(),
440 }
441}
442
443fn call_log_to_proto(m: &db::lprovider::llm_call_log::Model) -> pb::CallRecord {
444 pb::CallRecord {
445 id: m.id,
446 request_id: m.request_id.to_string(),
447 trace_id: m.trace_id.map(|u| u.to_string()).unwrap_or_default(),
448 tenant_id: m.tenant_id,
449 api_key_id: m.api_key_id.unwrap_or(0),
450 subject_type: m.subject_type.clone().unwrap_or_default(),
451 subject_id: m.subject_id.unwrap_or(0),
452 wallet_id: m.wallet_id.unwrap_or(0),
453 anonymous: m.anonymous,
454 subject_path: m.subject_path.clone().unwrap_or_default(),
455 session_key: m.session_key.clone().unwrap_or_default(),
456 model_alias: m.model_alias.clone(),
457 upstream_code: m.upstream_code.clone(),
458 requested_model: m.requested_model.clone().unwrap_or_default(),
459 chosen_strategy: m.chosen_strategy.clone().unwrap_or_default(),
460 tier_cost: m.tier_cost,
461 tier_quality: m.tier_quality,
462 tier_latency: m.tier_latency,
463 key_label: m.key_label.clone().unwrap_or_default(),
464 caller: m.caller.clone().unwrap_or_default(),
465 caller_service: m.caller_service.clone().unwrap_or_default(),
466 priority: m.priority,
467 streamed: m.streamed,
468 status: m.status.clone(),
469 error_kind: m.error_kind.clone().unwrap_or_default(),
470 error_message: m.error_message.clone().unwrap_or_default(),
471 prompt_tokens: m.prompt_tokens,
472 completion_tokens: m.completion_tokens,
473 cache_hit_tokens: m.cache_hit_tokens,
474 cache_miss_tokens: m.cache_miss_tokens,
475 billable_input_tokens: m.billable_input_tokens,
476 billable_output_tokens: m.billable_output_tokens,
477 points_input: m.points_input,
478 points_output: m.points_output,
479 points_cache_hit: m.points_cache_hit,
480 points_total: m.points_total,
481 points_rate_input: m.points_rate_input,
482 points_rate_output: m.points_rate_output,
483 points_rate_cache_hit: m.points_rate_cache_hit,
484 request_started_at_ms: unix_ms(m.request_started_at),
485 response_completed_at_ms: m.response_completed_at.map(unix_ms).unwrap_or(0),
486 latency_ms: m.latency_ms,
487 client_ip: m.client_ip.clone().unwrap_or_default(),
488 user_agent: m.user_agent.clone().unwrap_or_default(),
489 created_at_ms: unix_ms(m.created_at),
490 }
491}
492
493fn aggregate_to_proto(m: &db::lprovider::llm_billing_aggregate::Model) -> pb::BillingAggregate {
494 pb::BillingAggregate {
495 id: m.id,
496 tenant_id: m.tenant_id,
497 subject_type: m.subject_type.clone(),
498 subject_id: m.subject_id,
499 period_start_ms: unix_ms(m.period_start),
500 period_end_ms: unix_ms(m.period_end),
501 period_kind: m.period_kind.clone(),
502 model_alias: m.model_alias.clone(),
503 upstream_code: m.upstream_code.clone(),
504 call_count: m.call_count,
505 success_count: m.success_count,
506 error_count: m.error_count,
507 total_input_tokens: m.total_input_tokens,
508 total_output_tokens: m.total_output_tokens,
509 total_cache_hit_tokens: m.total_cache_hit_tokens,
510 total_points: m.total_points,
511 updated_at_ms: unix_ms(m.updated_at),
512 }
513}
514
515async fn build_upstream_key(
516 up: &crate::upstream::UpstreamEntry,
517 k: &crate::upstream::KeyEntry,
518 cfg_credential_ref: Option<String>,
519 cfg_qps: i64,
520 cfg_rpm: i64,
521) -> pb::UpstreamKey {
522 let (status, cd_until) = match *k.cooldown_until.read().await {
523 Some(inst) => {
524 let rem = inst.saturating_duration_since(Instant::now());
525 if rem.is_zero() {
526 ("active".to_string(), 0)
527 } else {
528 ("cooldown".to_string(), now_unix() + rem.as_secs() as i64)
529 }
530 }
531 None => ("active".to_string(), 0),
532 };
533 pb::UpstreamKey {
534 upstream_code: up.code.clone(),
535 label: k.label.clone(),
536 credential_ref: cfg_credential_ref.unwrap_or_default(),
537 key_fingerprint: k.fingerprint.clone(),
538 status,
539 qps_limit: cfg_qps as i32,
540 rpm_limit: cfg_rpm as i32,
541 cooldown_until_unix: cd_until,
542 stats: Some(pb::KeyStats {
543 success_count: 0,
544 error_count: 0,
545 rate_limited_count: 0,
546 in_flight: k.in_flight.load(Ordering::Relaxed) as i32,
547 }),
548 }
549}
550
551fn self_instance_info(state: &AppState) -> pb::InstanceInfo {
552 let lp = state.runtime.blocking_read().lp().clone();
553 pb::InstanceInfo {
554 instance_id: state.instance_id.clone(),
555 cluster_name: state.cluster_name.clone(),
556 listen_addr: format!("{}:{}", lp.bind, lp.port),
557 started_at_unix: state.started_at.unix_timestamp(),
558 pid: state.pid as i32,
559 config_sha256: state.runtime.blocking_read().config_sha256.clone(),
560 is_config_writer: false,
561 }
562}
563
564fn instance_from_json(v: &serde_json::Value) -> Option<pb::InstanceInfo> {
565 Some(pb::InstanceInfo {
566 instance_id: v.get("instance_id")?.as_str()?.to_string(),
567 cluster_name: v.get("cluster_name")?.as_str()?.to_string(),
568 listen_addr: v.get("listen_addr")?.as_str()?.to_string(),
569 started_at_unix: v.get("started_at_unix")?.as_i64()?,
570 pid: v.get("pid")?.as_i64()? as i32,
571 config_sha256: v.get("config_sha256")?.as_str()?.to_string(),
572 is_config_writer: v.get("is_config_writer")?.as_bool()?,
573 })
574}
575
576#[tonic::async_trait]
577impl pb::lprovider_admin_server::LproviderAdmin for AdminGrpc {
578 async fn get_config(
579 &self,
580 request: Request<pb::GetConfigRequest>,
581 ) -> Result<Response<pb::GetConfigResponse>, Status> {
582 let req = request.into_inner();
583 let runtime = self.state.runtime.read().await;
584 let path = runtime.config_path.clone();
585 let sha = runtime.config_sha256.clone();
586 drop(runtime);
587 let content = tokio::fs::read_to_string(&path)
588 .await
589 .map_err(|e| status_from_err(&LproviderError::Internal(format!("read config: {e}"))))?;
590 let size = tokio::fs::metadata(&path)
591 .await
592 .map(|m| m.len() as i64)
593 .unwrap_or(0);
594 let masked = req.mask_secrets.unwrap_or(false);
595 let content_yaml = if masked {
596 mask_secrets(&content)
597 } else {
598 content
599 };
600 Ok(Response::new(pb::GetConfigResponse {
601 config_path: path.to_string_lossy().into_owned(),
602 sha256: sha,
603 file_size_bytes: size,
604 loaded_at_unix: self.state.started_at.unix_timestamp(),
605 content_yaml,
606 warnings: vec![],
607 }))
608 }
609
610 async fn update_config(
611 &self,
612 request: Request<pb::UpdateConfigRequest>,
613 ) -> Result<Response<pb::UpdateConfigResponse>, Status> {
614 let req = request.into_inner();
615 let operator = operator_of(&req.operator);
616 const RPC: &str = "UpdateConfig";
617 let _guard = match self.acquire_config_lock().await {
618 Ok(g) => g,
619 Err(e) => {
620 self.audit_write(
621 &operator,
622 RPC,
623 None,
624 None,
625 None,
626 "error",
627 Some(&e.to_string()),
628 )
629 .await;
630 return Err(status_from_err(&e));
631 }
632 };
633 let config_path = self.state.runtime.read().await.config_path.clone();
634 let new_yaml = match compose_yaml(&req, &config_path).await {
635 Ok(y) => y,
636 Err(e) => {
637 self.audit_write(
638 &operator,
639 RPC,
640 None,
641 None,
642 None,
643 "error",
644 Some(&e.to_string()),
645 )
646 .await;
647 return Err(status_from_err(&e));
648 }
649 };
650
651 let result = async {
652 let tmp = config_path.with_extension("tmp");
653 tokio::fs::write(&tmp, &new_yaml)
654 .await
655 .map_err(|e| LproviderError::Internal(format!("write tmp: {e}")))?;
656 let tmp_file = std::fs::File::open(&tmp)
657 .map_err(|e| LproviderError::Internal(format!("open tmp: {e}")))?;
658 tmp_file
659 .sync_all()
660 .map_err(|e| LproviderError::Internal(format!("fsync tmp: {e}")))?;
661 drop(tmp_file);
662 config::ConfigLoader::new()
663 .config_file(&tmp)
664 .load()
665 .map_err(|e| {
666 LproviderError::InvalidRequest(format!("config validation failed: {e}"))
667 })?;
668
669 let bak = config_path.with_extension("bak");
670 let _ = tokio::fs::copy(&config_path, &bak).await;
671 tokio::fs::rename(&tmp, &config_path)
672 .await
673 .map_err(|e| LproviderError::Internal(format!("rename: {e}")))?;
674
675 let mut cli = self.state.cli.clone();
676 cli.config = config_path.clone();
677 let new_rt = RuntimeConfig::load(&cli)?;
678 let sha256 = new_rt.config_sha256.clone();
679 let old_app = self.state.runtime.read().await.app.clone();
680 let warnings = if infra_changed(&old_app, &new_rt.app) {
681 vec!["infrastructure section changed (bind/port/redis/db/secret/context/timeout); restart required".to_string()]
682 } else {
683 vec![]
684 };
685
686 let mut reloaded = false;
687 let mut restarted = false;
688 if req.restart_after_write {
689 restarted = true;
690 let state = self.state.clone();
691 tokio::spawn(async move {
692 tokio::time::sleep(Duration::from_secs(30)).await;
693 state.shutdown.cancel();
694 });
695 } else {
696 reloaded = true;
697 self.manager.reload(new_rt.lp())?;
698 *self.state.runtime.write().await = new_rt;
699 }
700
701 Ok::<pb::UpdateConfigResponse, LproviderError>(pb::UpdateConfigResponse {
702 sha256,
703 backup_path: bak.to_string_lossy().into_owned(),
704 reloaded,
705 restarted,
706 warnings,
707 })
708 }
709 .await;
710
711 match result {
712 Ok(resp) => {
713 self.audit_write(
714 &operator,
715 RPC,
716 Some(
717 &self
718 .state
719 .runtime
720 .blocking_read()
721 .config_path
722 .to_string_lossy(),
723 ),
724 Some("config updated"),
725 Some("reload"),
726 "success",
727 None,
728 )
729 .await;
730 Ok(Response::new(resp))
731 }
732 Err(e) => {
733 self.audit_write(
734 &operator,
735 RPC,
736 None,
737 None,
738 None,
739 "error",
740 Some(&e.to_string()),
741 )
742 .await;
743 Err(status_from_err(&e))
744 }
745 }
746 }
747
748 async fn reload_config(
749 &self,
750 request: Request<pb::ReloadConfigRequest>,
751 ) -> Result<Response<pb::ReloadConfigResponse>, Status> {
752 let req = request.into_inner();
753 let operator = operator_of(&req.operator);
754 const RPC: &str = "ReloadConfig";
755 let previous = self.state.runtime.read().await.config_sha256.clone();
756 match self.reload_runtime().await {
757 Ok(()) => {
758 let current = self.state.runtime.read().await.config_sha256.clone();
759 let status = self.build_runtime_status().await;
760 self.audit_write(
761 &operator,
762 RPC,
763 None,
764 Some("config reloaded"),
765 Some("reload"),
766 "success",
767 None,
768 )
769 .await;
770 Ok(Response::new(pb::ReloadConfigResponse {
771 previous_sha256: previous,
772 current_sha256: current,
773 warnings: vec![],
774 status: Some(status),
775 }))
776 }
777 Err(e) => {
778 self.audit_write(
779 &operator,
780 RPC,
781 None,
782 None,
783 None,
784 "error",
785 Some(&e.to_string()),
786 )
787 .await;
788 Err(status_from_err(&e))
789 }
790 }
791 }
792
793 async fn restart_service(
794 &self,
795 request: Request<pb::RestartServiceRequest>,
796 ) -> Result<Response<pb::RestartServiceResponse>, Status> {
797 let req = request.into_inner();
798 let operator = operator_of(&req.operator);
799 const RPC: &str = "RestartService";
800 let drain = if req.drain_timeout_seconds <= 0 {
801 30
802 } else {
803 req.drain_timeout_seconds
804 } as u64;
805 let state = self.state.clone();
806 let instance_id = self.state.instance_id.clone();
807 let self_reexec = req.self_reexec;
808 tokio::spawn(async move {
809 tokio::time::sleep(Duration::from_secs(drain)).await;
810 if self_reexec && let Ok(exe) = std::env::current_exe() {
811 let args: Vec<String> = std::env::args().skip(1).collect();
812 let _ = std::process::Command::new(exe).args(args).spawn();
813 }
814 state.shutdown.cancel();
815 });
816 self.audit_write(
817 &operator,
818 RPC,
819 None,
820 Some("restart scheduled"),
821 Some("restart"),
822 "success",
823 None,
824 )
825 .await;
826 Ok(Response::new(pb::RestartServiceResponse {
827 accepted: true,
828 reason: format!("draining {}s then restart", drain),
829 instance_id,
830 }))
831 }
832
833 async fn list_upstreams(
834 &self,
835 request: Request<pb::ListUpstreamsRequest>,
836 ) -> Result<Response<pb::ListUpstreamsResponse>, Status> {
837 let req = request.into_inner();
838 let lp = self.state.runtime.blocking_read().lp().clone();
839 let upstreams = lp
840 .upstreams
841 .iter()
842 .filter(|u| req.codes.is_empty() || req.codes.contains(&u.code))
843 .map(upstream_to_proto)
844 .collect();
845 Ok(Response::new(pb::ListUpstreamsResponse { upstreams }))
846 }
847
848 async fn get_upstream(
849 &self,
850 request: Request<pb::GetUpstreamRequest>,
851 ) -> Result<Response<pb::Upstream>, Status> {
852 let req = request.into_inner();
853 let lp = self.state.runtime.blocking_read().lp().clone();
854 match lp.upstreams.iter().find(|u| u.code == req.code) {
855 Some(u) => Ok(Response::new(upstream_to_proto(u))),
856 None => Err(status_from_err(&LproviderError::NotFound(format!(
857 "upstream {}",
858 req.code
859 )))),
860 }
861 }
862
863 async fn list_upstream_keys(
864 &self,
865 request: Request<pb::ListUpstreamKeysRequest>,
866 ) -> Result<Response<pb::ListUpstreamKeysResponse>, Status> {
867 let req = request.into_inner();
868 let snapshot = self.manager.snapshot().await;
869 let lp = self.state.runtime.blocking_read().lp().clone();
870 let mut keys = Vec::new();
871 for up in &snapshot.upstreams {
872 if !req.upstream_code.is_empty() && up.code != req.upstream_code {
873 continue;
874 }
875 let cfg_up = lp.upstreams.iter().find(|u| u.code == up.code);
876 for k in &up.keys {
877 let cred = cfg_up.and_then(|u| u.credentials.iter().find(|c| c.label == k.label));
878 keys.push(
879 build_upstream_key(
880 up,
881 k,
882 cred.map(|c| c.credential_ref.clone()),
883 cred.map(|c| c.qps_limit).unwrap_or(0),
884 cred.map(|c| c.rpm_limit).unwrap_or(0),
885 )
886 .await,
887 );
888 }
889 }
890 Ok(Response::new(pb::ListUpstreamKeysResponse { keys }))
891 }
892
893 async fn get_upstream_key(
894 &self,
895 request: Request<pb::GetUpstreamKeyRequest>,
896 ) -> Result<Response<pb::UpstreamKey>, Status> {
897 let req = request.into_inner();
898 let snapshot = self.manager.snapshot().await;
899 let lp = self.state.runtime.blocking_read().lp().clone();
900 for up in &snapshot.upstreams {
901 if up.code != req.upstream_code {
902 continue;
903 }
904 if let Some(k) = up.keys.iter().find(|k| k.label == req.label) {
905 let cred = lp
906 .upstreams
907 .iter()
908 .find(|u| u.code == up.code)
909 .and_then(|u| u.credentials.iter().find(|c| c.label == k.label));
910 return Ok(Response::new(
911 build_upstream_key(
912 up,
913 k,
914 cred.map(|c| c.credential_ref.clone()),
915 cred.map(|c| c.qps_limit).unwrap_or(0),
916 cred.map(|c| c.rpm_limit).unwrap_or(0),
917 )
918 .await,
919 ));
920 }
921 }
922 Err(status_from_err(&LproviderError::NotFound(format!(
923 "key {}/{}",
924 req.upstream_code, req.label
925 ))))
926 }
927
928 async fn list_bindings(
929 &self,
930 request: Request<pb::ListBindingsRequest>,
931 ) -> Result<Response<pb::ListBindingsResponse>, Status> {
932 let req = request.into_inner();
933 let Some(redis) = &self.state.redis else {
934 return Ok(Response::new(pb::ListBindingsResponse { bindings: vec![] }));
935 };
936 let prefix = self.affinity.key_prefix().to_string();
937 let pattern = format!("{prefix}:bind:*");
938 let mut conn = redis.connection();
939 let mut raw_keys: Vec<String> = Vec::new();
940 {
941 let mut iter = conn.scan_match(pattern).await.map_err(redis_status)?;
942 while let Some(item) = iter.next().await {
943 if let Ok(k) = item {
944 raw_keys.push(k);
945 }
946 }
947 }
948 let mut bindings = Vec::new();
949 for key in raw_keys {
950 let Some(suffix) = key.strip_prefix(&format!("{prefix}:bind:")) else {
951 continue;
952 };
953 let mut it = suffix.splitn(3, ':');
954 let (Some(tenant_s), Some(alias), Some(skey)) = (it.next(), it.next(), it.next())
955 else {
956 continue;
957 };
958 let Ok(tenant) = tenant_s.parse::<i64>() else {
959 continue;
960 };
961 if req.tenant_id != 0 && tenant != req.tenant_id {
962 continue;
963 }
964 if !req.model_alias.is_empty() && alias != req.model_alias {
965 continue;
966 }
967 if !req.session_key.is_empty() && skey != req.session_key {
968 continue;
969 }
970 let val: Option<String> = conn.get(&key).await.ok().flatten();
971 let ttl: i64 = conn.ttl(&key).await.ok().unwrap_or(0);
972 if let Some(v) = val
973 && let Ok(b) = serde_json::from_str::<crate::affinity::SessionBinding>(&v)
974 {
975 bindings.push(pb::SessionBinding {
976 model_alias: alias.to_string(),
977 session_key: skey.to_string(),
978 upstream_code: b.upstream_code,
979 key_label: b.key_label,
980 prefix_hash: b.prefix_hash,
981 bound_at_unix: b.bound_at,
982 expires_at_unix: if ttl > 0 { b.bound_at + ttl } else { 0 },
983 last_hit_tokens: b.last_hit_tokens as i32,
984 hit_count: b.hit_count,
985 });
986 }
987 }
988 Ok(Response::new(pb::ListBindingsResponse { bindings }))
989 }
990
991 async fn invalidate_binding(
992 &self,
993 request: Request<pb::InvalidateBindingRequest>,
994 ) -> Result<Response<pb::InvalidateBindingResponse>, Status> {
995 let req = request.into_inner();
996 let operator = "unknown".to_string();
997 const RPC: &str = "InvalidateBinding";
998 match self
999 .affinity
1000 .invalidate(req.tenant_id, &req.model_alias, &req.session_key)
1001 .await
1002 {
1003 Ok(()) => {
1004 self.audit_write(
1005 &operator,
1006 RPC,
1007 Some(&format!("{}:{}", req.model_alias, req.session_key)),
1008 Some("binding invalidated"),
1009 None,
1010 "success",
1011 None,
1012 )
1013 .await;
1014 Ok(Response::new(pb::InvalidateBindingResponse {
1015 invalidated: true,
1016 }))
1017 }
1018 Err(e) => Err(status_from_err(&e)),
1019 }
1020 }
1021
1022 async fn query_audit(
1023 &self,
1024 request: Request<pb::QueryAuditRequest>,
1025 ) -> Result<Response<pb::QueryAuditResponse>, Status> {
1026 let req = request.into_inner();
1027 let st = (!req.subject_type.is_empty()).then(|| req.subject_type.clone());
1028 let ma = (!req.model_alias.is_empty()).then(|| req.model_alias.clone());
1029 let uc = (!req.upstream_code.is_empty()).then(|| req.upstream_code.clone());
1030 let status = (!req.status.is_empty()).then(|| req.status.clone());
1031 let limit = if req.limit <= 0 {
1032 100
1033 } else {
1034 req.limit as u64
1035 };
1036 let (models, total) = self
1037 .audit
1038 .query_audit(
1039 (req.tenant_id != 0).then_some(req.tenant_id),
1040 st.as_deref(),
1041 (req.subject_id != 0).then_some(req.subject_id),
1042 ma.as_deref(),
1043 uc.as_deref(),
1044 status.as_deref(),
1045 (req.from_ms > 0).then_some(req.from_ms),
1046 (req.to_ms > 0).then_some(req.to_ms),
1047 limit,
1048 req.offset as u64,
1049 )
1050 .await
1051 .map_err(|e| status_from_err(&e))?;
1052 Ok(Response::new(pb::QueryAuditResponse {
1053 records: models.iter().map(call_log_to_proto).collect(),
1054 total: total as i64,
1055 }))
1056 }
1057
1058 async fn aggregate_billing(
1059 &self,
1060 request: Request<pb::AggregateBillingRequest>,
1061 ) -> Result<Response<pb::AggregateBillingResponse>, Status> {
1062 let req = request.into_inner();
1063 let st = (!req.subject_type.is_empty()).then(|| req.subject_type.clone());
1064 let pk = (!req.period_kind.is_empty()).then(|| req.period_kind.clone());
1065 let ma = (!req.model_alias.is_empty()).then(|| req.model_alias.clone());
1066 let uc = (!req.upstream_code.is_empty()).then(|| req.upstream_code.clone());
1067 let (models, _total) = self
1068 .audit
1069 .aggregate_billing(
1070 (req.tenant_id != 0).then_some(req.tenant_id),
1071 st.as_deref(),
1072 (req.subject_id != 0).then_some(req.subject_id),
1073 pk.as_deref(),
1074 (req.period_start_ms > 0).then_some(req.period_start_ms),
1075 (req.period_end_ms > 0).then_some(req.period_end_ms),
1076 ma.as_deref(),
1077 uc.as_deref(),
1078 500,
1079 0,
1080 )
1081 .await
1082 .map_err(|e| status_from_err(&e))?;
1083 Ok(Response::new(pb::AggregateBillingResponse {
1084 aggregates: models.iter().map(aggregate_to_proto).collect(),
1085 }))
1086 }
1087
1088 async fn list_tenant_quotas(
1089 &self,
1090 request: Request<pb::ListTenantQuotasRequest>,
1091 ) -> Result<Response<pb::ListTenantQuotasResponse>, Status> {
1092 let req = request.into_inner();
1093 let (quotas, _total) = self
1094 .quotas
1095 .list_quotas(
1096 (req.tenant_id != 0).then_some(req.tenant_id),
1097 (!req.subject_type.is_empty()).then(|| req.subject_type.clone()),
1098 (req.subject_id != 0).then_some(req.subject_id),
1099 100,
1100 0,
1101 )
1102 .await
1103 .map_err(|e| status_from_err(&e))?;
1104 Ok(Response::new(pb::ListTenantQuotasResponse {
1105 quotas: quotas.iter().map(quota_to_proto).collect(),
1106 }))
1107 }
1108
1109 async fn upsert_tenant_quota(
1110 &self,
1111 request: Request<pb::UpsertTenantQuotaRequest>,
1112 ) -> Result<Response<pb::TenantQuota>, Status> {
1113 let req = request.into_inner();
1114 let operator = "unknown".to_string();
1115 const RPC: &str = "UpsertTenantQuota";
1116 let Some(q) = &req.quota else {
1117 return Err(status_from_err(&LproviderError::InvalidRequest(
1118 "quota missing".into(),
1119 )));
1120 };
1121 let quota = proto_to_quota(q);
1122 match self.quotas.upsert_quota(&quota).await {
1123 Ok(()) => {
1124 self.audit_write(
1125 &operator,
1126 RPC,
1127 Some(&format!(
1128 "{}:{}:{}",
1129 quota.tenant_id, quota.subject_type, quota.subject_id
1130 )),
1131 Some("quota upserted"),
1132 None,
1133 "success",
1134 None,
1135 )
1136 .await;
1137 Ok(Response::new(quota_to_proto(&quota)))
1138 }
1139 Err(e) => Err(status_from_err(&e)),
1140 }
1141 }
1142
1143 async fn get_wallet(
1144 &self,
1145 request: Request<pb::GetWalletRequest>,
1146 ) -> Result<Response<pb::Wallet>, Status> {
1147 let req = request.into_inner();
1148 let wallet = self
1149 .wallets
1150 .get_wallet(req.wallet_id)
1151 .await
1152 .map_err(|e| status_from_err(&e))?;
1153 Ok(Response::new(wallet_to_proto(&wallet)))
1154 }
1155
1156 async fn list_wallets(
1157 &self,
1158 request: Request<pb::ListWalletsRequest>,
1159 ) -> Result<Response<pb::ListWalletsResponse>, Status> {
1160 let req = request.into_inner();
1161 let (wallets, _total) = self
1162 .wallets
1163 .list_wallets(
1164 (req.tenant_id != 0).then_some(req.tenant_id),
1165 (!req.owner_type.is_empty()).then(|| req.owner_type.clone()),
1166 (req.owner_id != 0).then_some(req.owner_id),
1167 None,
1168 100,
1169 0,
1170 )
1171 .await
1172 .map_err(|e| status_from_err(&e))?;
1173 Ok(Response::new(pb::ListWalletsResponse {
1174 wallets: wallets.iter().map(wallet_to_proto).collect(),
1175 }))
1176 }
1177
1178 async fn topup_wallet(
1179 &self,
1180 request: Request<pb::TopupWalletRequest>,
1181 ) -> Result<Response<pb::Wallet>, Status> {
1182 let req = request.into_inner();
1183 let operator = operator_of(&req.operator);
1184 const RPC: &str = "TopupWallet";
1185 if req.idempotency_key.trim().is_empty() {
1186 return Err(status_from_err(&LproviderError::InvalidRequest(
1187 "idempotency_key is required for topup".into(),
1188 )));
1189 }
1190 if req.delta_points <= 0 {
1191 return Err(status_from_err(&LproviderError::InvalidRequest(
1192 "delta_points must be positive".into(),
1193 )));
1194 }
1195 let reason = (!req.reason.is_empty()).then(|| req.reason.clone());
1196 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1197 match self
1198 .wallets
1199 .topup(
1200 req.wallet_id,
1201 req.delta_points,
1202 &req.idempotency_key,
1203 reason,
1204 op,
1205 )
1206 .await
1207 {
1208 Ok(_) => {
1209 let wallet = self
1210 .wallets
1211 .get_wallet(req.wallet_id)
1212 .await
1213 .map_err(|e| status_from_err(&e))?;
1214 self.audit_write(
1215 &operator,
1216 RPC,
1217 Some(&format!("wallet {}", req.wallet_id)),
1218 Some(&format!("+{} points", req.delta_points)),
1219 None,
1220 "success",
1221 None,
1222 )
1223 .await;
1224 Ok(Response::new(wallet_to_proto(&wallet)))
1225 }
1226 Err(e) => Err(status_from_err(&e)),
1227 }
1228 }
1229
1230 async fn refund_wallet(
1231 &self,
1232 request: Request<pb::RefundWalletRequest>,
1233 ) -> Result<Response<pb::Wallet>, Status> {
1234 let req = request.into_inner();
1235 let operator = operator_of(&req.operator);
1236 const RPC: &str = "RefundWallet";
1237 let request_id = match uuid::Uuid::parse_str(&req.request_id) {
1238 Ok(id) => id,
1239 Err(_) => {
1240 return Err(status_from_err(&LproviderError::InvalidRequest(
1241 "request_id is required and must be a valid uuid".into(),
1242 )));
1243 }
1244 };
1245 if req.delta_points <= 0 {
1246 return Err(status_from_err(&LproviderError::InvalidRequest(
1247 "delta_points must be positive".into(),
1248 )));
1249 }
1250 let reason = (!req.reason.is_empty()).then(|| req.reason.clone());
1251 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1252 match self
1253 .wallets
1254 .refund(req.wallet_id, req.delta_points, request_id, reason, op)
1255 .await
1256 {
1257 Ok(_) => {
1258 let wallet = self
1259 .wallets
1260 .get_wallet(req.wallet_id)
1261 .await
1262 .map_err(|e| status_from_err(&e))?;
1263 self.audit_write(
1264 &operator,
1265 RPC,
1266 Some(&format!("wallet {}", req.wallet_id)),
1267 Some(&format!("refund {} points", req.delta_points)),
1268 None,
1269 "success",
1270 None,
1271 )
1272 .await;
1273 Ok(Response::new(wallet_to_proto(&wallet)))
1274 }
1275 Err(e) => Err(status_from_err(&e)),
1276 }
1277 }
1278
1279 async fn adjust_wallet(
1280 &self,
1281 request: Request<pb::AdjustWalletRequest>,
1282 ) -> Result<Response<pb::Wallet>, Status> {
1283 let req = request.into_inner();
1284 let operator = operator_of(&req.operator);
1285 const RPC: &str = "AdjustWallet";
1286 if req.idempotency_key.trim().is_empty() {
1287 return Err(status_from_err(&LproviderError::InvalidRequest(
1288 "idempotency_key is required for adjust".into(),
1289 )));
1290 }
1291 if req.delta_points == 0 {
1292 return Err(status_from_err(&LproviderError::InvalidRequest(
1293 "delta_points must not be zero".into(),
1294 )));
1295 }
1296 let reason = (!req.reason.is_empty()).then(|| req.reason.clone());
1297 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1298 match self
1299 .wallets
1300 .adjust(
1301 req.wallet_id,
1302 req.delta_points,
1303 &req.idempotency_key,
1304 reason,
1305 op,
1306 )
1307 .await
1308 {
1309 Ok(_) => {
1310 let wallet = self
1311 .wallets
1312 .get_wallet(req.wallet_id)
1313 .await
1314 .map_err(|e| status_from_err(&e))?;
1315 self.audit_write(
1316 &operator,
1317 RPC,
1318 Some(&format!("wallet {}", req.wallet_id)),
1319 Some(&format!("adjust {} points", req.delta_points)),
1320 None,
1321 "success",
1322 None,
1323 )
1324 .await;
1325 Ok(Response::new(wallet_to_proto(&wallet)))
1326 }
1327 Err(e) => Err(status_from_err(&e)),
1328 }
1329 }
1330
1331 async fn freeze_wallet(
1332 &self,
1333 request: Request<pb::FreezeWalletRequest>,
1334 ) -> Result<Response<pb::Wallet>, Status> {
1335 let req = request.into_inner();
1336 let operator = operator_of(&req.operator);
1337 const RPC: &str = "FreezeWallet";
1338 let reason = if req.reason.is_empty() {
1339 "admin action".to_string()
1340 } else {
1341 req.reason.clone()
1342 };
1343 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1344 match self.wallets.freeze(req.wallet_id, reason, op).await {
1345 Ok(()) => {
1346 let wallet = self
1347 .wallets
1348 .get_wallet(req.wallet_id)
1349 .await
1350 .map_err(|e| status_from_err(&e))?;
1351 self.audit_write(
1352 &operator,
1353 RPC,
1354 Some(&format!("wallet {}", req.wallet_id)),
1355 None,
1356 None,
1357 "success",
1358 None,
1359 )
1360 .await;
1361 Ok(Response::new(wallet_to_proto(&wallet)))
1362 }
1363 Err(e) => Err(status_from_err(&e)),
1364 }
1365 }
1366
1367 async fn unfreeze_wallet(
1368 &self,
1369 request: Request<pb::UnfreezeWalletRequest>,
1370 ) -> Result<Response<pb::Wallet>, Status> {
1371 let req = request.into_inner();
1372 let operator = operator_of(&req.operator);
1373 const RPC: &str = "UnfreezeWallet";
1374 let reason = if req.reason.is_empty() {
1375 "admin action".to_string()
1376 } else {
1377 req.reason.clone()
1378 };
1379 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1380 match self.wallets.unfreeze(req.wallet_id, reason, op).await {
1381 Ok(()) => {
1382 let wallet = self
1383 .wallets
1384 .get_wallet(req.wallet_id)
1385 .await
1386 .map_err(|e| status_from_err(&e))?;
1387 self.audit_write(
1388 &operator,
1389 RPC,
1390 Some(&format!("wallet {}", req.wallet_id)),
1391 None,
1392 None,
1393 "success",
1394 None,
1395 )
1396 .await;
1397 Ok(Response::new(wallet_to_proto(&wallet)))
1398 }
1399 Err(e) => Err(status_from_err(&e)),
1400 }
1401 }
1402
1403 async fn disable_wallet(
1404 &self,
1405 request: Request<pb::DisableWalletRequest>,
1406 ) -> Result<Response<pb::Wallet>, Status> {
1407 let req = request.into_inner();
1408 let operator = operator_of(&req.operator);
1409 const RPC: &str = "DisableWallet";
1410 let reason = if req.reason.is_empty() {
1411 "admin action".to_string()
1412 } else {
1413 req.reason.clone()
1414 };
1415 let op = (!req.operator.is_empty()).then(|| req.operator.clone());
1416 match self.wallets.disable(req.wallet_id, reason, op).await {
1417 Ok(()) => {
1418 let wallet = self
1419 .wallets
1420 .get_wallet(req.wallet_id)
1421 .await
1422 .map_err(|e| status_from_err(&e))?;
1423 self.audit_write(
1424 &operator,
1425 RPC,
1426 Some(&format!("wallet {}", req.wallet_id)),
1427 None,
1428 None,
1429 "success",
1430 None,
1431 )
1432 .await;
1433 Ok(Response::new(wallet_to_proto(&wallet)))
1434 }
1435 Err(e) => Err(status_from_err(&e)),
1436 }
1437 }
1438
1439 async fn list_wallet_ledger(
1440 &self,
1441 request: Request<pb::ListWalletLedgerRequest>,
1442 ) -> Result<Response<pb::ListWalletLedgerResponse>, Status> {
1443 let req = request.into_inner();
1444 let (entries, _total) = self
1445 .wallets
1446 .list_ledger((req.wallet_id != 0).then_some(req.wallet_id), None, 100, 0)
1447 .await
1448 .map_err(|e| status_from_err(&e))?;
1449 let from = req.from_unix;
1450 let to = req.to_unix;
1451 let entries = entries
1452 .into_iter()
1453 .filter(|e| {
1454 let t = e.created_at.unix_timestamp();
1455 (from <= 0 || t >= from) && (to <= 0 || t <= to)
1456 })
1457 .map(|e| pb::WalletLedgerEntry {
1458 id: 0,
1459 wallet_id: e.wallet_id,
1460 request_id: e.request_id.map(|u| u.to_string()).unwrap_or_default(),
1461 delta_points: e.delta_points,
1462 balance_after: e.balance_after,
1463 kind: e.kind,
1464 reason: e.reason.unwrap_or_default(),
1465 operator: e.operator.unwrap_or_default(),
1466 created_at_unix: e.created_at.unix_timestamp(),
1467 })
1468 .collect();
1469 Ok(Response::new(pb::ListWalletLedgerResponse { entries }))
1470 }
1471
1472 async fn list_wallet_freeze_log(
1473 &self,
1474 request: Request<pb::ListWalletFreezeLogRequest>,
1475 ) -> Result<Response<pb::ListWalletFreezeLogResponse>, Status> {
1476 let req = request.into_inner();
1477 let (models, _total) = self
1478 .wallets
1479 .list_freeze_log((req.wallet_id != 0).then_some(req.wallet_id), 100, 0)
1480 .await
1481 .map_err(|e| status_from_err(&e))?;
1482 let entries = models
1483 .into_iter()
1484 .map(|m| pb::WalletFreezeLogEntry {
1485 id: m.id,
1486 wallet_id: m.wallet_id,
1487 action: m.action,
1488 reason: m.reason,
1489 operator: m.operator.unwrap_or_default(),
1490 created_at_unix: m.created_at.unix_timestamp(),
1491 })
1492 .collect();
1493 Ok(Response::new(pb::ListWalletFreezeLogResponse { entries }))
1494 }
1495
1496 async fn get_runtime_status(
1497 &self,
1498 _request: Request<pb::GetRuntimeStatusRequest>,
1499 ) -> Result<Response<pb::RuntimeStatus>, Status> {
1500 Ok(Response::new(self.build_runtime_status().await))
1501 }
1502
1503 async fn list_instances(
1504 &self,
1505 request: Request<pb::ListInstancesRequest>,
1506 ) -> Result<Response<pb::ListInstancesResponse>, Status> {
1507 let req = request.into_inner();
1508 let mut instances = vec![self_instance_info(&self.state)];
1509 if let Some(redis) = &self.state.redis {
1510 let cluster = if req.cluster_name.is_empty() {
1511 self.state.cluster_name.clone()
1512 } else {
1513 req.cluster_name.clone()
1514 };
1515 let key = format!("{}:instances:{}", self.affinity.key_prefix(), cluster);
1516 let mut conn = redis.connection();
1517 let members: redis::RedisResult<Vec<String>> = conn.smembers(&key).await;
1518 if let Ok(members) = members {
1519 for m in members {
1520 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&m)
1521 && let Some(info) = instance_from_json(&v)
1522 && !instances.iter().any(|i| i.instance_id == info.instance_id)
1523 {
1524 instances.push(info);
1525 }
1526 }
1527 }
1528 }
1529 Ok(Response::new(pb::ListInstancesResponse { instances }))
1530 }
1531}
Deleted agent/lprovider/affinity.rs
@@ -1,312 +0,0 @@
1use std::sync::Arc;
2
3use cache::RedisCache;
4use config::lprovider::LproviderRedisSettings;
5use llm::provider::LlmProvider;
6use llm::types::{ChatMessage, ChatRequest, MessageRole};
7use redis::AsyncCommands;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use tokio_stream::StreamExt;
11
12use crate::error::LproviderError;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SessionBinding {
16 pub upstream_code: String,
17 pub key_label: String,
18 pub model: String,
19 pub prefix_hash: String,
20 pub bound_at: i64,
21 pub last_hit_tokens: i64,
22 pub hit_count: i64,
23 #[serde(default)]
24 pub miss_count: i64,
25}
26
27pub struct AffinityManager {
28 redis: Option<Arc<RedisCache>>,
29 key_prefix: String,
30 ttl_seconds: i64,
31 min_prefix_tokens: usize,
32 prefix_token_budget: usize,
33 no_hit_max_age_seconds: i64,
34}
35
36const BIND_SCRIPT: &str = r#"
37redis.call('SET', KEYS[1], ARGV[1], 'EX', tonumber(ARGV[2]))
38redis.call('SADD', KEYS[2], KEYS[1])
39redis.call('SADD', KEYS[3], KEYS[1])
40return 1
41"#;
42
43const UNBIND_SCRIPT: &str = r#"
44redis.call('DEL', KEYS[1])
45redis.call('SREM', KEYS[2], KEYS[1])
46redis.call('SREM', KEYS[3], KEYS[1])
47return 1
48"#;
49
50impl AffinityManager {
51 pub fn new(redis: Option<Arc<RedisCache>>, cfg: &LproviderRedisSettings) -> Self {
52 Self {
53 redis,
54 key_prefix: cfg.key_prefix.clone(),
55 ttl_seconds: cfg.session_affinity_ttl_seconds,
56 min_prefix_tokens: cfg.min_prefix_tokens,
57 prefix_token_budget: cfg.prefix_token_budget,
58 no_hit_max_age_seconds: cfg.session_affinity_ttl_seconds,
59 }
60 }
61
62 pub fn key_prefix(&self) -> &str {
63 &self.key_prefix
64 }
65
66 pub fn ttl_seconds(&self) -> i64 {
67 self.ttl_seconds
68 }
69
70 pub fn bind_key(&self, tenant_id: i64, model_alias: &str, session_key: &str) -> String {
71 format!(
72 "{}:bind:{}:{}:{}",
73 self.key_prefix, tenant_id, model_alias, session_key
74 )
75 }
76
77 fn idx_upstream_key(&self, code: &str) -> String {
78 format!("{}:bind_idx:upstream:{}", self.key_prefix, code)
79 }
80
81 fn idx_key_key(&self, code: &str, label: &str) -> String {
82 format!("{}:bind_idx:key:{}:{}", self.key_prefix, code, label)
83 }
84
85 pub fn compute_session_key(
86 &self,
87 model_alias: &str,
88 messages: &[ChatMessage],
89 estimator: &Arc<dyn LlmProvider>,
90 ) -> Option<(String, usize)> {
91 let mut prefix: Vec<ChatMessage> = Vec::new();
92 for m in messages {
93 match m.role {
94 MessageRole::System | MessageRole::User => prefix.push(m.clone()),
95 _ => break,
96 }
97 }
98 while prefix.len() > 1
99 && estimator.count_tokens(&ChatRequest::new(prefix.clone())) > self.prefix_token_budget
100 {
101 prefix.pop();
102 }
103 let tokens = estimator.count_tokens(&ChatRequest::new(prefix.clone()));
104 if tokens < self.min_prefix_tokens {
105 return None;
106 }
107 let text = prefix
108 .iter()
109 .map(|m| m.text_content())
110 .collect::<Vec<_>>()
111 .join("\n");
112 let mut hasher = Sha256::new();
113 hasher.update(model_alias.as_bytes());
114 hasher.update(b"\n");
115 hasher.update(text.as_bytes());
116 Some((hex::encode(hasher.finalize()), tokens))
117 }
118
119 pub fn validate_session_key_override(&self, key: &str) -> Result<(), LproviderError> {
120 if key.is_empty() || key.len() > 128 {
121 return Err(LproviderError::InvalidRequest(
122 "X-Session-Key 长度须在 1..=128 之间".into(),
123 ));
124 }
125 if !key
126 .chars()
127 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
128 {
129 return Err(LproviderError::InvalidRequest(
130 "X-Session-Key 仅允许 [A-Za-z0-9._-] 字符".into(),
131 ));
132 }
133 Ok(())
134 }
135
136 pub async fn lookup(
137 &self,
138 tenant_id: i64,
139 model_alias: &str,
140 session_key: &str,
141 ) -> Result<Option<SessionBinding>, LproviderError> {
142 let Some(cache) = &self.redis else {
143 return Ok(None);
144 };
145 let mut conn = cache.connection();
146 let key = self.bind_key(tenant_id, model_alias, session_key);
147 let raw: Option<String> = conn
148 .get(&key)
149 .await
150 .map_err(|e| LproviderError::Redis(e.to_string()))?;
151 match raw {
152 Some(raw) => serde_json::from_str(&raw)
153 .map(Some)
154 .map_err(|e| LproviderError::Internal(format!("绑定 JSON 解析失败: {e}"))),
155 None => Ok(None),
156 }
157 }
158
159 pub async fn bind(
160 &self,
161 tenant_id: i64,
162 model_alias: &str,
163 session_key: &str,
164 binding: &SessionBinding,
165 ) -> Result<(), LproviderError> {
166 let Some(cache) = &self.redis else {
167 return Ok(());
168 };
169 let mut conn = cache.connection();
170 let json = serde_json::to_string(binding)
171 .map_err(|e| LproviderError::Internal(format!("绑定 JSON 序列化失败: {e}")))?;
172 redis::Script::new(BIND_SCRIPT)
173 .key(self.bind_key(tenant_id, model_alias, session_key))
174 .key(self.idx_upstream_key(&binding.upstream_code))
175 .key(self.idx_key_key(&binding.upstream_code, &binding.key_label))
176 .arg(json)
177 .arg(self.ttl_seconds)
178 .invoke_async::<()>(&mut conn)
179 .await
180 .map_err(|e| LproviderError::Redis(e.to_string()))?;
181 Ok(())
182 }
183
184 pub async fn record_feedback(
185 &self,
186 tenant_id: i64,
187 model_alias: &str,
188 session_key: &str,
189 hit: bool,
190 hit_tokens: i64,
191 ) -> Result<(), LproviderError> {
192 let Some(cache) = &self.redis else {
193 return Ok(());
194 };
195 let mut conn = cache.connection();
196 let key = self.bind_key(tenant_id, model_alias, session_key);
197 let raw: Option<String> = conn
198 .get(&key)
199 .await
200 .map_err(|e| LproviderError::Redis(e.to_string()))?;
201 let Some(raw) = raw else {
202 return Ok(());
203 };
204 let mut b: SessionBinding = serde_json::from_str(&raw)
205 .map_err(|e| LproviderError::Internal(format!("绑定 JSON 解析失败: {e}")))?;
206 if hit {
207 b.hit_count += 1;
208 b.last_hit_tokens = hit_tokens;
209 } else {
210 b.miss_count += 1;
211 }
212 let total = b.hit_count + b.miss_count;
213 let ratio = if total > 0 {
214 b.hit_count as f64 / total as f64
215 } else {
216 0.0
217 };
218 let new_ttl = if hit && ratio >= 0.5 {
219 self.ttl_seconds
220 } else if b.hit_count == 0 && b.miss_count > self.min_prefix_tokens as i64 {
221 (self.ttl_seconds / 4).max(1)
222 } else {
223 let rem: i64 = conn
224 .ttl(&key)
225 .await
226 .map_err(|e| LproviderError::Redis(e.to_string()))?;
227 if rem > 0 { rem } else { self.ttl_seconds }
228 };
229 let json = serde_json::to_string(&b)
230 .map_err(|e| LproviderError::Internal(format!("绑定 JSON 序列化失败: {e}")))?;
231 let _: () = conn
232 .set_ex(&key, json, new_ttl as u64)
233 .await
234 .map_err(|e| LproviderError::Redis(e.to_string()))?;
235 Ok(())
236 }
237
238 pub async fn invalidate(
239 &self,
240 tenant_id: i64,
241 model_alias: &str,
242 session_key: &str,
243 ) -> Result<(), LproviderError> {
244 let Some(cache) = &self.redis else {
245 return Ok(());
246 };
247 let mut conn = cache.connection();
248 let key = self.bind_key(tenant_id, model_alias, session_key);
249 let raw: Option<String> = conn
250 .get(&key)
251 .await
252 .map_err(|e| LproviderError::Redis(e.to_string()))?;
253 let Some(raw) = raw else {
254 return Ok(());
255 };
256 let b: SessionBinding = serde_json::from_str(&raw)
257 .map_err(|e| LproviderError::Internal(format!("绑定 JSON 解析失败: {e}")))?;
258 redis::Script::new(UNBIND_SCRIPT)
259 .key(&key)
260 .key(self.idx_upstream_key(&b.upstream_code))
261 .key(self.idx_key_key(&b.upstream_code, &b.key_label))
262 .invoke_async::<()>(&mut conn)
263 .await
264 .map_err(|e| LproviderError::Redis(e.to_string()))?;
265 Ok(())
266 }
267
268 pub async fn cleanup_once(&self) -> Result<usize, LproviderError> {
269 let Some(cache) = &self.redis else {
270 return Ok(0);
271 };
272 let mut conn = cache.connection();
273 let pattern = format!("{}:bind:*", self.key_prefix);
274 let mut keys: Vec<String> = Vec::new();
275 {
276 let mut iter = conn
277 .scan_match(pattern)
278 .await
279 .map_err(|e| LproviderError::Redis(e.to_string()))?;
280 loop {
281 match iter.next().await {
282 Some(Ok(k)) => keys.push(k),
283 Some(Err(e)) => return Err(LproviderError::Redis(e.to_string())),
284 None => break,
285 }
286 }
287 }
288 let now = time::OffsetDateTime::now_utc().unix_timestamp();
289 let mut removed = 0usize;
290 for key in keys {
291 let raw: Option<String> = conn
292 .get(&key)
293 .await
294 .map_err(|e| LproviderError::Redis(e.to_string()))?;
295 let Some(raw) = raw else { continue };
296 let Ok(b) = serde_json::from_str::<SessionBinding>(&raw) else {
297 continue;
298 };
299 if b.hit_count == 0 && now - b.bound_at > self.no_hit_max_age_seconds {
300 redis::Script::new(UNBIND_SCRIPT)
301 .key(&key)
302 .key(self.idx_upstream_key(&b.upstream_code))
303 .key(self.idx_key_key(&b.upstream_code, &b.key_label))
304 .invoke_async::<()>(&mut conn)
305 .await
306 .map_err(|e| LproviderError::Redis(e.to_string()))?;
307 removed += 1;
308 }
309 }
310 Ok(removed)
311 }
312}
Deleted agent/lprovider/ai.proto
@@ -1,179 +0,0 @@
1syntax = "proto3";
2package lprovider.ai.v1;
3
4import "google/protobuf/struct.proto";
5
6service LproviderAI {
7 rpc Chat(ChatRequestMsg) returns (ChatResponseMsg);
8
9 rpc ChatStream(ChatRequestMsg) returns (stream StreamEventMsg);
10
11 // request_id 语义:与整个流绑定(取流中第一条消息的 request_id),
12 // 同一流内后续消息不得变更 request_id;Cancel 按该 request_id 终止整个流。
13 rpc ChatStreamBidirectional(stream ChatRequestMsg) returns (stream StreamEventMsg);
14
15 rpc CountTokens(CountTokensRequest) returns (CountTokensResponse);
16
17 rpc Cancel(CancelRequest) returns (CancelResponse);
18}
19
20message ChatRequestMsg {
21 string model_alias = 1;
22 repeated ChatMessageMsg messages = 2;
23 repeated ToolMsg tools = 3;
24 optional int32 max_output_tokens = 4;
25 optional double temperature = 5;
26 optional double top_p = 6;
27 optional ResponseFormatMsg response_format = 7;
28 repeated string stop_sequences = 8;
29 google.protobuf.Struct provider_options = 9;
30
31 fixed64 tenant_id = 20;
32 int64 api_key_id = 21;
33 string caller_service = 22;
34 string request_id = 23;
35 int32 priority = 24;
36 string trace_id = 25;
37 map<string, string> labels = 26;
38 WalletSelector wallet_selector = 27;
39 ModelSelection model_selection = 28;
40 string session_key_override = 30;
41}
42
43message ModelSelection {
44 enum Strategy {
45 STRATEGY_UNSPECIFIED = 0;
46 BALANCED = 1;
47 COST = 2;
48 QUALITY = 3;
49 LATENCY = 4;
50 EXPLICIT = 5;
51 }
52 Strategy strategy = 1;
53 repeated string allowed_upstreams = 2;
54 repeated string excluded_upstreams = 3;
55 int32 balance_bias = 4;
56 repeated string required_capabilities = 5;
57}
58
59message WalletSelector {
60 oneof mode {
61 WalletRef wallet = 1;
62 AnonymousRef anonymous = 2;
63 }
64}
65
66message WalletRef {
67 int64 wallet_id = 1;
68}
69
70message AnonymousRef {}
71
72message ChatMessageMsg {
73 string role = 1;
74 oneof content {
75 string text = 2;
76 ContentPartsMsg parts = 3;
77 }
78 string tool_call_id = 4;
79 repeated ToolCallMsg tool_calls = 5;
80 string name = 6;
81 string reasoning_text = 7;
82}
83
84message ContentPartsMsg {
85 message Part {
86 string type = 1;
87 string text = 2;
88 string url = 3;
89 bytes data = 4;
90 string mime_type = 5;
91 }
92 repeated Part parts = 1;
93}
94
95message ToolMsg {
96 string tool_type = 1;
97 google.protobuf.Struct function = 2;
98}
99
100message ToolCallMsg {
101 string id = 1;
102 string tool_type = 2;
103 google.protobuf.Struct function = 3;
104}
105
106message ResponseFormatMsg {
107 string type = 1;
108 string json_schema = 2;
109}
110
111message ChatResponseMsg {
112 string id = 1;
113 string model_alias = 2;
114 string upstream_code = 3;
115 string key_label = 4;
116 string content = 5;
117 string reasoning_text = 6;
118 repeated ToolCallMsg tool_calls = 7;
119 string finish_reason = 8;
120 UsageMsg usage = 9;
121 google.protobuf.Struct raw_provider_response = 10;
122 int64 latency_ms = 11;
123 string request_id = 12;
124}
125
126message StreamEventMsg {
127 string event_type = 1;
128 string text_delta = 2;
129 string reasoning_delta = 3;
130 ToolCallDeltaMsg tool_call_delta = 4;
131 UsageMsg usage = 5;
132 string finish_reason = 6;
133 string error_kind = 7;
134 string error_message = 8;
135 string request_id = 9;
136 string session_key = 10;
137}
138
139message ToolCallDeltaMsg {
140 string id = 1;
141 int32 index = 2;
142 string tool_type = 3;
143 string name_delta = 4;
144 string arguments_delta = 5;
145}
146
147message UsageMsg {
148 int32 prompt_tokens = 1;
149 int32 completion_tokens = 2;
150 int32 total_tokens = 3;
151 int32 cache_hit_tokens = 4;
152 int32 cache_miss_tokens = 5;
153 int64 points_input = 6;
154 int64 points_output = 7;
155 int64 points_cache_hit = 8;
156 int64 points_total = 9;
157}
158
159message CountTokensRequest {
160 string model_alias = 1;
161 repeated ChatMessageMsg messages = 2;
162 repeated ToolMsg tools = 3;
163}
164
165message CountTokensResponse {
166 int32 estimated_input_tokens = 1;
167 int32 estimated_output_tokens = 2;
168 int32 max_context_tokens = 3;
169 int32 max_output_tokens = 4;
170 bool fits_context = 5;
171}
172
173message CancelRequest {
174 string request_id = 1;
175}
176
177message CancelResponse {
178 bool cancelled = 1;
179}
Deleted agent/lprovider/ai_grpc.rs
@@ -1,658 +0,0 @@
1use std::collections::{BTreeMap, HashMap};
2use std::sync::{Arc, Mutex};
3
4use futures::StreamExt;
5use llm::types::{
6 ChatMessage, ChatRequest, ContentPart, FinishReason, ImageDetail, MessageContent, MessageRole,
7 ResponseFormat, StreamEvent, ToolCall, ToolDefinition,
8};
9use tokio::sync::mpsc;
10use tokio_stream::wrappers::ReceiverStream;
11use tonic::{Request, Response, Status};
12
13use crate::error::{LproviderError, WalletError};
14use crate::lprovider::ai::v1::{self, lprovider_ai_server::LproviderAi};
15use crate::router::{ChatContext, ChatSelectionSpec, Router};
16use crate::state::AppState;
17use crate::wallet::WalletService;
18
19#[derive(Clone)]
20pub struct AiGrpc {
21 pub router: Arc<Router>,
22 pub state: Arc<AppState>,
23 pub wallets: Arc<WalletService>,
24 cancels: Arc<Mutex<HashMap<String, mpsc::Sender<()>>>>,
25}
26
27impl AiGrpc {
28 pub fn new(router: Arc<Router>, state: Arc<AppState>) -> Self {
29 let wallets = Arc::new(WalletService::new(state.db.clone().map(Arc::new)));
30 Self {
31 router,
32 state,
33 wallets,
34 cancels: Arc::new(Mutex::new(HashMap::new())),
35 }
36 }
37
38 fn register_cancel(&self, request_id: &str) -> mpsc::Receiver<()> {
39 let (tx, rx) = mpsc::channel::<()>(1);
40 self.cancels
41 .lock()
42 .unwrap()
43 .insert(request_id.to_string(), tx);
44 rx
45 }
46
47 fn tenant_id(msg: &v1::ChatRequestMsg) -> Result<i64, Status> {
48 if msg.tenant_id == 0 {
49 return Err(status_from_err(&LproviderError::InvalidRequest(
50 "tenant_id is required".into(),
51 )));
52 }
53 Ok(msg.tenant_id as i64)
54 }
55
56 fn request_id(msg: &v1::ChatRequestMsg) -> Result<uuid::Uuid, Status> {
57 if msg.request_id.is_empty() {
58 return Ok(uuid::Uuid::now_v7());
59 }
60 uuid::Uuid::parse_str(&msg.request_id).map_err(|_| {
61 status_from_err(&LproviderError::InvalidRequest(
62 "request_id is not a valid UUID".into(),
63 ))
64 })
65 }
66
67 async fn build_context(
68 &self,
69 msg: v1::ChatRequestMsg,
70 stream: bool,
71 ) -> Result<ChatContext, Status> {
72 let tenant_id = Self::tenant_id(&msg)?;
73 let request_id = Self::request_id(&msg)?;
74 let trace_id = (!msg.trace_id.is_empty())
75 .then(|| uuid::Uuid::parse_str(&msg.trace_id).ok())
76 .flatten();
77
78 let (wallet_id, anonymous, subject_type, subject_id) =
79 match msg.wallet_selector.and_then(|s| s.mode) {
80 None => {
81 return Err(status_from_err(&LproviderError::Wallet(
82 WalletError::WalletRequired,
83 )));
84 }
85 Some(v1::wallet_selector::Mode::Wallet(w)) => {
86 let wallet = self
87 .wallets
88 .get_wallet(w.wallet_id)
89 .await
90 .map_err(|e| status_from_err(&e))?;
91 (
92 Some(wallet.wallet_id),
93 false,
94 wallet.owner_type,
95 wallet.owner_id,
96 )
97 }
98 Some(v1::wallet_selector::Mode::Anonymous(_)) => {
99 (None, true, "tenant".to_string(), tenant_id)
100 }
101 };
102
103 let (request, _alias, max_output) = proto_to_chat_request(&msg)?;
104 let lp = self.state.runtime.read().await.lp().clone();
105 let max_output = max_output.min(lp.max_output_tokens);
106
107 let selection = msg.model_selection.map(|m| ChatSelectionSpec {
108 strategy: strategy_name(m.strategy),
109 allowed: m.allowed_upstreams,
110 excluded: m.excluded_upstreams,
111 balance_bias: m.balance_bias as f64,
112 required_capabilities: m.required_capabilities,
113 });
114
115 let session_key_override =
116 (!msg.session_key_override.is_empty()).then_some(msg.session_key_override);
117
118 Ok(ChatContext {
119 request_id,
120 trace_id,
121 tenant_id,
122 wallet_id,
123 subject_type,
124 subject_id,
125 anonymous,
126 model_alias: if msg.model_alias.is_empty() {
127 "auto".into()
128 } else {
129 msg.model_alias
130 },
131 request,
132 max_output,
133 stream,
134 priority: msg.priority,
135 caller: None,
136 caller_service: (!msg.caller_service.is_empty()).then_some(msg.caller_service),
137 session_key_override,
138 selection: selection.unwrap_or_default(),
139 client_ip: None,
140 user_agent: None,
141 })
142 }
143}
144
145fn strategy_name(raw: i32) -> Option<String> {
146 match v1::model_selection::Strategy::try_from(raw) {
147 Ok(v1::model_selection::Strategy::Balanced) => Some("BALANCED".into()),
148 Ok(v1::model_selection::Strategy::Cost) => Some("COST".into()),
149 Ok(v1::model_selection::Strategy::Quality) => Some("QUALITY".into()),
150 Ok(v1::model_selection::Strategy::Latency) => Some("LATENCY".into()),
151 Ok(v1::model_selection::Strategy::Explicit) => Some("EXPLICIT".into()),
152 _ => None,
153 }
154}
155
156pub fn status_from_err(err: &LproviderError) -> Status {
157 match err.status_code() {
158 http::StatusCode::BAD_REQUEST => Status::invalid_argument(err.to_string()),
159 http::StatusCode::UNAUTHORIZED => Status::unauthenticated(err.to_string()),
160 http::StatusCode::PAYMENT_REQUIRED => Status::failed_precondition(err.to_string()),
161 http::StatusCode::FORBIDDEN => Status::permission_denied(err.to_string()),
162 http::StatusCode::NOT_FOUND => Status::not_found(err.to_string()),
163 http::StatusCode::CONFLICT => Status::aborted(err.to_string()),
164 http::StatusCode::TOO_MANY_REQUESTS => Status::resource_exhausted(err.to_string()),
165 http::StatusCode::BAD_GATEWAY => Status::unavailable(err.to_string()),
166 http::StatusCode::SERVICE_UNAVAILABLE => Status::unavailable(err.to_string()),
167 _ => Status::internal(err.to_string()),
168 }
169}
170
171fn proto_to_chat_request(msg: &v1::ChatRequestMsg) -> Result<(ChatRequest, String, usize), Status> {
172 let messages = msg
173 .messages
174 .iter()
175 .map(proto_message_to_llm)
176 .collect::<Result<Vec<_>, Status>>()?;
177 let tools = msg
178 .tools
179 .iter()
180 .map(proto_tool_to_llm)
181 .collect::<Result<Vec<_>, Status>>()?;
182
183 let mut request = ChatRequest::new(messages);
184 if !tools.is_empty() {
185 request = request.with_tools(tools);
186 }
187 if let Some(m) = msg.max_output_tokens
188 && m > 0
189 {
190 request = request.with_max_tokens(m as usize);
191 }
192 if let Some(t) = msg.temperature {
193 request = request.with_temperature(t as f32);
194 }
195 if let Some(p) = msg.top_p {
196 request = request.with_top_p(p as f32);
197 }
198 if let Some(rf) = &msg.response_format
199 && (rf.r#type == "json_object" || rf.r#type == "json_schema")
200 {
201 request = request.with_response_format(ResponseFormat::Json);
202 }
203 if !msg.stop_sequences.is_empty() {
204 request = request.with_stop_sequences(msg.stop_sequences.clone());
205 }
206 if let Some(opts) = &msg.provider_options {
207 request = request.with_provider_options(prost_struct_to_json(opts));
208 }
209 Ok((
210 request,
211 msg.model_alias.clone(),
212 msg.max_output_tokens.unwrap_or(0).max(0) as usize,
213 ))
214}
215
216fn proto_message_to_llm(m: &v1::ChatMessageMsg) -> Result<ChatMessage, Status> {
217 let role = match m.role.to_ascii_lowercase().as_str() {
218 "system" => MessageRole::System,
219 "user" => MessageRole::User,
220 "assistant" => MessageRole::Assistant,
221 "tool" => MessageRole::Tool,
222 other => {
223 return Err(status_from_err(&LproviderError::InvalidRequest(format!(
224 "unknown role: {other}"
225 ))));
226 }
227 };
228 let content = match &m.content {
229 None => MessageContent::Text(String::new()),
230 Some(v1::chat_message_msg::Content::Text(t)) => MessageContent::Text(t.clone()),
231 Some(v1::chat_message_msg::Content::Parts(p)) => {
232 let parts = p
233 .parts
234 .iter()
235 .map(|part| {
236 if part.r#type == "text" {
237 ContentPart::Text(part.text.clone())
238 } else {
239 ContentPart::Image {
240 url: part.url.clone(),
241 detail: ImageDetail::Auto,
242 }
243 }
244 })
245 .collect();
246 MessageContent::Parts(parts)
247 }
248 };
249 let tool_calls = m
250 .tool_calls
251 .iter()
252 .map(|tc| {
253 let (name, arguments) = tool_call_fields(&tc.function);
254 Ok::<_, Status>(ToolCall {
255 id: tc.id.clone(),
256 name,
257 arguments,
258 })
259 })
260 .collect::<Result<Vec<_>, _>>()?;
261 Ok(ChatMessage {
262 role,
263 content,
264 name: (!m.name.is_empty()).then(|| m.name.clone()),
265 tool_calls,
266 tool_call_id: (!m.tool_call_id.is_empty()).then(|| m.tool_call_id.clone()),
267 })
268}
269
270fn tool_call_fields(function: &Option<prost_types::Struct>) -> (String, serde_json::Value) {
271 let fields = function.as_ref().map(|s| &s.fields).unwrap_or_else(|| {
272 static EMPTY: std::sync::OnceLock<BTreeMap<String, prost_types::Value>> =
273 std::sync::OnceLock::new();
274 EMPTY.get_or_init(BTreeMap::new)
275 });
276 let name = fields
277 .get("name")
278 .and_then(|v| v.kind.as_ref())
279 .and_then(|k| match k {
280 prost_types::value::Kind::StringValue(s) => Some(s.clone()),
281 _ => None,
282 })
283 .unwrap_or_default();
284 let arguments = fields
285 .get("arguments")
286 .and_then(|v| v.kind.as_ref())
287 .and_then(|k| match k {
288 prost_types::value::Kind::StringValue(s) => serde_json::from_str(s).ok(),
289 _ => None,
290 })
291 .or_else(|| function.as_ref().map(prost_struct_to_json))
292 .unwrap_or(serde_json::Value::Object(Default::default()));
293 (name, arguments)
294}
295
296pub fn prost_value_to_json(v: &prost_types::Value) -> serde_json::Value {
297 match &v.kind {
298 Some(prost_types::value::Kind::NullValue(_)) => serde_json::Value::Null,
299 Some(prost_types::value::Kind::NumberValue(n)) => serde_json::Value::from(*n),
300 Some(prost_types::value::Kind::StringValue(s)) => serde_json::Value::String(s.clone()),
301 Some(prost_types::value::Kind::BoolValue(b)) => serde_json::Value::Bool(*b),
302 Some(prost_types::value::Kind::StructValue(s)) => prost_struct_to_json(s),
303 Some(prost_types::value::Kind::ListValue(l)) => {
304 serde_json::Value::Array(l.values.iter().map(prost_value_to_json).collect())
305 }
306 None => serde_json::Value::Null,
307 }
308}
309
310pub fn prost_struct_to_json(s: &prost_types::Struct) -> serde_json::Value {
311 let mut map = serde_json::Map::new();
312 for (k, v) in &s.fields {
313 map.insert(k.clone(), prost_value_to_json(v));
314 }
315 serde_json::Value::Object(map)
316}
317
318fn proto_tool_to_llm(t: &v1::ToolMsg) -> Result<ToolDefinition, Status> {
319 let fields = t.function.as_ref().map(|s| &s.fields).unwrap_or_else(|| {
320 static EMPTY: std::sync::OnceLock<BTreeMap<String, prost_types::Value>> =
321 std::sync::OnceLock::new();
322 EMPTY.get_or_init(BTreeMap::new)
323 });
324 let name = fields
325 .get("name")
326 .and_then(|v| v.kind.as_ref())
327 .and_then(|k| match k {
328 prost_types::value::Kind::StringValue(s) => Some(s.clone()),
329 _ => None,
330 })
331 .ok_or_else(|| Status::invalid_argument("tool missing name"))?;
332 let description = fields
333 .get("description")
334 .and_then(|v| v.kind.as_ref())
335 .and_then(|k| match k {
336 prost_types::value::Kind::StringValue(s) => Some(s.clone()),
337 _ => None,
338 })
339 .unwrap_or_default();
340 let parameters = fields
341 .get("parameters")
342 .and_then(|v| v.kind.as_ref())
343 .and_then(|k| match k {
344 prost_types::value::Kind::StructValue(s) => Some(prost_struct_to_json(s)),
345 _ => None,
346 })
347 .unwrap_or_else(|| serde_json::json!({"type": "object", "properties": {}}));
348 Ok(ToolDefinition::new(name, description, parameters))
349}
350
351fn finish_reason_str(r: FinishReason) -> &'static str {
352 match r {
353 FinishReason::Stop => "stop",
354 FinishReason::ToolUse => "tool_use",
355 FinishReason::MaxTokens => "max_tokens",
356 FinishReason::ContentFilter => "content_filter",
357 FinishReason::Error => "error",
358 }
359}
360
361fn tool_calls_to_proto(calls: &[ToolCall]) -> Vec<v1::ToolCallMsg> {
362 calls
363 .iter()
364 .map(|c| v1::ToolCallMsg {
365 id: c.id.clone(),
366 tool_type: "function".into(),
367 function: {
368 let mut fields = BTreeMap::new();
369 fields.insert(
370 "name".into(),
371 prost_types::Value {
372 kind: Some(prost_types::value::Kind::StringValue(c.name.clone())),
373 },
374 );
375 fields.insert(
376 "arguments".into(),
377 prost_types::Value {
378 kind: Some(prost_types::value::Kind::StringValue(
379 c.arguments.to_string(),
380 )),
381 },
382 );
383 Some(prost_types::Struct { fields })
384 },
385 })
386 .collect()
387}
388
389fn stream_event_to_proto(ev: StreamEvent) -> v1::StreamEventMsg {
390 let mut out = v1::StreamEventMsg::default();
391 match ev {
392 StreamEvent::TextDelta { delta } => {
393 out.event_type = "text_delta".into();
394 out.text_delta = delta;
395 }
396 StreamEvent::ReasoningDelta { delta } => {
397 out.event_type = "reasoning_delta".into();
398 out.reasoning_delta = delta;
399 }
400 StreamEvent::ToolCallDelta {
401 index,
402 id,
403 name,
404 args_delta,
405 } => {
406 out.event_type = "tool_call_delta".into();
407 out.tool_call_delta = Some(v1::ToolCallDeltaMsg {
408 id,
409 index: index.unwrap_or(0) as i32,
410 tool_type: "function".into(),
411 name_delta: name.unwrap_or_default(),
412 arguments_delta: args_delta,
413 });
414 }
415 StreamEvent::Usage(u) => {
416 out.event_type = "usage".into();
417 out.usage = Some(v1::UsageMsg {
418 prompt_tokens: u.input_tokens as i32,
419 completion_tokens: u.output_tokens as i32,
420 total_tokens: u.total() as i32,
421 cache_hit_tokens: 0,
422 cache_miss_tokens: 0,
423 points_input: 0,
424 points_output: 0,
425 points_cache_hit: 0,
426 points_total: 0,
427 });
428 }
429 StreamEvent::Done { finish_reason } => {
430 out.event_type = "done".into();
431 out.finish_reason = finish_reason_str(finish_reason).into();
432 }
433 }
434 out
435}
436
437#[tonic::async_trait]
438impl LproviderAi for AiGrpc {
439 type ChatStreamStream = ReceiverStream<Result<v1::StreamEventMsg, Status>>;
440 type ChatStreamBidirectionalStream = ReceiverStream<Result<v1::StreamEventMsg, Status>>;
441
442 async fn chat(
443 &self,
444 request: Request<v1::ChatRequestMsg>,
445 ) -> Result<Response<v1::ChatResponseMsg>, Status> {
446 let msg = request.into_inner();
447 let ctx = self.build_context(msg, false).await?;
448 let request_id = ctx.request_id;
449 let started = std::time::Instant::now();
450 let outcome = self
451 .router
452 .handle_chat(ctx)
453 .await
454 .map_err(|e| status_from_err(&e))?;
455 let latency_ms = started.elapsed().as_millis() as i64;
456
457 let bill = outcome.bill;
458 let usage = v1::UsageMsg {
459 prompt_tokens: outcome.response.usage.input_tokens as i32,
460 completion_tokens: outcome.response.usage.output_tokens as i32,
461 total_tokens: outcome.response.usage.total() as i32,
462 cache_hit_tokens: 0,
463 cache_miss_tokens: 0,
464 points_input: bill.points_input,
465 points_output: bill.points_output,
466 points_cache_hit: bill.points_cache_hit,
467 points_total: bill.points_total,
468 };
469 Ok(Response::new(v1::ChatResponseMsg {
470 id: request_id.to_string(),
471 model_alias: outcome.model,
472 upstream_code: outcome.upstream_code,
473 key_label: outcome.key_label,
474 content: outcome.response.content,
475 reasoning_text: String::new(),
476 tool_calls: tool_calls_to_proto(&outcome.response.tool_calls),
477 finish_reason: finish_reason_str(outcome.response.finish_reason).into(),
478 usage: Some(usage),
479 raw_provider_response: None,
480 latency_ms,
481 request_id: request_id.to_string(),
482 }))
483 }
484
485 async fn chat_stream(
486 &self,
487 request: Request<v1::ChatRequestMsg>,
488 ) -> Result<Response<Self::ChatStreamStream>, Status> {
489 let msg = request.into_inner();
490 let ctx = self.build_context(msg, true).await?;
491 let request_id = ctx.request_id.to_string();
492 let cancel = self.register_cancel(&request_id);
493
494 let mut billed = self
495 .router
496 .handle_stream(ctx)
497 .await
498 .map_err(|e| status_from_err(&e))?;
499
500 let (tx, rx) = mpsc::channel::<Result<v1::StreamEventMsg, Status>>(64);
501 let unregister = self.cancels.clone();
502 let rid = request_id.clone();
503 tokio::spawn(async move {
504 let mut cancel = cancel;
505 loop {
506 tokio::select! {
507 _ = cancel.recv() => {
508 let _ = tx.send(Err(Status::cancelled("cancelled"))).await;
509 break;
510 }
511 item = billed.next() => {
512 match item {
513 Some(Ok(ev)) => {
514 if tx.send(Ok(stream_event_to_proto(ev))).await.is_err() {
515 break;
516 }
517 }
518 Some(Err(e)) => {
519 let _ = tx.send(Err(Status::internal(e.to_string()))).await;
520 break;
521 }
522 None => break,
523 }
524 }
525 }
526 }
527 unregister.lock().unwrap().remove(&rid);
528 });
529 Ok(Response::new(ReceiverStream::new(rx)))
530 }
531
532 async fn chat_stream_bidirectional(
533 &self,
534 request: Request<tonic::Streaming<v1::ChatRequestMsg>>,
535 ) -> Result<Response<Self::ChatStreamBidirectionalStream>, Status> {
536 let mut inbound = request.into_inner();
537 let first = inbound
538 .next()
539 .await
540 .ok_or_else(|| Status::invalid_argument("empty stream"))??;
541 let request_id = Self::request_id(&first)?;
542 let ctx = self.build_context(first, true).await?;
543 let rid = request_id.to_string();
544 let cancel = self.register_cancel(&rid);
545 let cancels = self.cancels.clone();
546
547 let mut billed = self
548 .router
549 .handle_stream(ctx)
550 .await
551 .map_err(|e| status_from_err(&e))?;
552
553 let (tx, rx) = mpsc::channel::<Result<v1::StreamEventMsg, Status>>(64);
554 tokio::spawn(async move {
555 let out_tx = tx.clone();
556 let guard_task = tokio::spawn(async move {
557 while let Some(msg) = inbound.next().await {
558 match msg {
559 Ok(m) => {
560 let new_rid = (!m.request_id.is_empty())
561 .then(|| uuid::Uuid::parse_str(&m.request_id).ok())
562 .flatten();
563 if let Some(new_rid) = new_rid
564 && new_rid != request_id
565 {
566 let _ = out_tx
567 .send(Err(Status::invalid_argument(
568 "request_id must not change mid-stream",
569 )))
570 .await;
571 }
572 }
573 Err(e) => {
574 let _ = out_tx.send(Err(e)).await;
575 return;
576 }
577 }
578 }
579 });
580 let mut cancel = cancel;
581 loop {
582 tokio::select! {
583 _ = cancel.recv() => {
584 let _ = tx.send(Err(Status::cancelled("cancelled"))).await;
585 break;
586 }
587 item = billed.next() => {
588 match item {
589 Some(Ok(ev)) => {
590 if tx.send(Ok(stream_event_to_proto(ev))).await.is_err() {
591 break;
592 }
593 }
594 Some(Err(e)) => {
595 let _ = tx.send(Err(Status::internal(e.to_string()))).await;
596 break;
597 }
598 None => break,
599 }
600 }
601 }
602 }
603 guard_task.abort();
604 cancels.lock().unwrap().remove(&rid);
605 });
606 Ok(Response::new(ReceiverStream::new(rx)))
607 }
608
609 async fn count_tokens(
610 &self,
611 request: Request<v1::CountTokensRequest>,
612 ) -> Result<Response<v1::CountTokensResponse>, Status> {
613 let req = request.into_inner();
614 let (chat_request, _alias, _) = {
615 let msg = v1::ChatRequestMsg {
616 model_alias: req.model_alias,
617 messages: req.messages,
618 tools: req.tools,
619 ..Default::default()
620 };
621 proto_to_chat_request(&msg)?
622 };
623 let lp = self.state.runtime.read().await.lp().clone();
624 let input = self
625 .router
626 .count_tokens(&chat_request)
627 .await
628 .map_err(|e| status_from_err(&e))?;
629 let max_output = chat_request
630 .max_tokens
631 .unwrap_or(lp.max_output_tokens)
632 .min(lp.max_output_tokens);
633 let max_context = lp.max_context_tokens;
634 let fits = input.saturating_add(max_output) <= max_context;
635 Ok(Response::new(v1::CountTokensResponse {
636 estimated_input_tokens: input as i32,
637 estimated_output_tokens: max_output as i32,
638 max_context_tokens: max_context as i32,
639 max_output_tokens: max_output as i32,
640 fits_context: fits,
641 }))
642 }
643
644 async fn cancel(
645 &self,
646 request: Request<v1::CancelRequest>,
647 ) -> Result<Response<v1::CancelResponse>, Status> {
648 let req = request.into_inner();
649 let tx = self.cancels.lock().unwrap().get(&req.request_id).cloned();
650 let cancelled = if let Some(tx) = tx {
651 let _ = tx.send(()).await;
652 true
653 } else {
654 false
655 };
656 Ok(Response::new(v1::CancelResponse { cancelled }))
657 }
658}
Deleted agent/lprovider/audit.rs
@@ -1,496 +0,0 @@
1use std::sync::Arc;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use db::conn::{Db, DbError};
5use sea_orm::{
6 ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseBackend, EntityTrait, PaginatorTrait,
7 QueryFilter, QueryOrder, QuerySelect, Statement, Value,
8};
9use time::OffsetDateTime;
10use tracing;
11use uuid::Uuid;
12
13use crate::error::LproviderError;
14use db::lprovider::llm_call_log::{self, Model as CallLogModel};
15use db::lprovider::lprovider_admin_audit;
16
17const ERROR_MAX_LEN: usize = 512;
18
19fn now() -> OffsetDateTime {
20 OffsetDateTime::from_unix_timestamp(
21 SystemTime::now()
22 .duration_since(UNIX_EPOCH)
23 .unwrap_or_default()
24 .as_secs() as i64,
25 )
26 .unwrap_or(OffsetDateTime::UNIX_EPOCH)
27}
28
29pub fn sanitize_error(raw: &str) -> String {
30 let mut s: String = raw
31 .chars()
32 .map(|c| if c.is_control() { ' ' } else { c })
33 .take(ERROR_MAX_LEN)
34 .collect();
35 redact_marker(&mut s, "sk-", "sk-***");
36 redact_marker(&mut s, "Bearer ", "Bearer ***");
37 redact_marker(&mut s, "api_key", "api_key=***");
38 redact_marker(&mut s, "api-key", "api-key=***");
39 redact_marker(&mut s, "apikey", "apikey=***");
40 s
41}
42
43fn redact_marker(s: &mut String, marker: &str, repl: &str) {
44 let mut search_from = 0usize;
45 while let Some(rel) = s[search_from..].find(marker) {
46 let pos = search_from + rel;
47 let mut end = pos + marker.len();
48 let bytes = s.as_bytes();
49 if end < bytes.len() && (bytes[end] == b'=' || bytes[end] == b':') {
50 end += 1;
51 }
52 while end < bytes.len() {
53 let c = bytes[end];
54 if c.is_ascii_alphanumeric() || c == b'_' || c == b'-' || c == b'.' {
55 end += 1;
56 } else {
57 break;
58 }
59 }
60 if end <= pos + marker.len() {
61 search_from = pos + marker.len();
62 continue;
63 }
64 s.replace_range(pos..end, repl);
65 search_from = pos + repl.len();
66 }
67}
68
69#[derive(Clone, Debug)]
70pub struct CallLogEntry {
71 pub request_id: Uuid,
72 pub trace_id: Option<Uuid>,
73 pub tenant_id: i64,
74 pub api_key_id: Option<i64>,
75 pub subject_type: Option<String>,
76 pub subject_id: Option<i64>,
77 pub wallet_id: Option<i64>,
78 pub anonymous: bool,
79 pub subject_path: Option<String>,
80 pub session_key: Option<String>,
81 pub model_alias: String,
82 pub upstream_code: String,
83 pub requested_model: Option<String>,
84 pub chosen_strategy: Option<String>,
85 pub tier_cost: i32,
86 pub tier_quality: i32,
87 pub tier_latency: i32,
88 pub key_label: Option<String>,
89 pub caller: Option<String>,
90 pub caller_service: Option<String>,
91 pub priority: i32,
92 pub streamed: bool,
93 pub status: String,
94 pub error_kind: Option<String>,
95 pub error_message: Option<String>,
96 pub prompt_tokens: i32,
97 pub completion_tokens: i32,
98 pub cache_hit_tokens: i32,
99 pub cache_miss_tokens: i32,
100 pub billable_input_tokens: i32,
101 pub billable_output_tokens: i32,
102 pub points_input: i64,
103 pub points_output: i64,
104 pub points_cache_hit: i64,
105 pub points_total: i64,
106 pub points_rate_input: i64,
107 pub points_rate_output: i64,
108 pub points_rate_cache_hit: i64,
109 pub request_started_at: OffsetDateTime,
110 pub response_completed_at: Option<OffsetDateTime>,
111 pub latency_ms: i32,
112 pub client_ip: Option<String>,
113 pub user_agent: Option<String>,
114}
115
116impl Default for CallLogEntry {
117 fn default() -> Self {
118 Self {
119 request_id: Uuid::nil(),
120 trace_id: None,
121 tenant_id: 0,
122 api_key_id: None,
123 subject_type: None,
124 subject_id: None,
125 wallet_id: None,
126 anonymous: false,
127 subject_path: None,
128 session_key: None,
129 model_alias: String::new(),
130 upstream_code: String::new(),
131 requested_model: None,
132 chosen_strategy: None,
133 tier_cost: 0,
134 tier_quality: 0,
135 tier_latency: 0,
136 key_label: None,
137 caller: None,
138 caller_service: None,
139 priority: 0,
140 streamed: false,
141 status: String::new(),
142 error_kind: None,
143 error_message: None,
144 prompt_tokens: 0,
145 completion_tokens: 0,
146 cache_hit_tokens: 0,
147 cache_miss_tokens: 0,
148 billable_input_tokens: 0,
149 billable_output_tokens: 0,
150 points_input: 0,
151 points_output: 0,
152 points_cache_hit: 0,
153 points_total: 0,
154 points_rate_input: 0,
155 points_rate_output: 0,
156 points_rate_cache_hit: 0,
157 request_started_at: OffsetDateTime::UNIX_EPOCH,
158 response_completed_at: None,
159 latency_ms: 0,
160 client_ip: None,
161 user_agent: None,
162 }
163 }
164}
165
166#[derive(Clone)]
167pub struct AuditService {
168 db: Option<Arc<Db>>,
169}
170
171impl AuditService {
172 pub fn new(db: Option<Arc<Db>>) -> Self {
173 Self { db }
174 }
175
176 fn conn(&self) -> Result<&Db, LproviderError> {
177 self.db
178 .as_deref()
179 .ok_or_else(|| LproviderError::Db(DbError::InvalidConfig("db not configured".into())))
180 }
181
182 pub async fn insert_call_log(&self, e: &CallLogEntry) -> Result<(), LproviderError> {
183 let db = self.conn()?;
184 let am: llm_call_log::ActiveModel = llm_call_log::ActiveModel::builder()
185 .set_request_id(e.request_id)
186 .set_trace_id(e.trace_id)
187 .set_tenant_id(e.tenant_id)
188 .set_api_key_id(e.api_key_id)
189 .set_subject_type(e.subject_type.clone())
190 .set_subject_id(e.subject_id)
191 .set_wallet_id(e.wallet_id)
192 .set_anonymous(e.anonymous)
193 .set_subject_path(e.subject_path.clone())
194 .set_session_key(e.session_key.clone())
195 .set_model_alias(e.model_alias.clone())
196 .set_upstream_code(e.upstream_code.clone())
197 .set_requested_model(e.requested_model.clone())
198 .set_chosen_strategy(e.chosen_strategy.clone())
199 .set_tier_cost(e.tier_cost)
200 .set_tier_quality(e.tier_quality)
201 .set_tier_latency(e.tier_latency)
202 .set_key_label(e.key_label.clone())
203 .set_caller(e.caller.clone())
204 .set_caller_service(e.caller_service.clone())
205 .set_priority(e.priority)
206 .set_streamed(e.streamed)
207 .set_status(e.status.clone())
208 .set_error_kind(e.error_kind.clone())
209 .set_error_message(e.error_message.clone())
210 .set_prompt_tokens(e.prompt_tokens)
211 .set_completion_tokens(e.completion_tokens)
212 .set_cache_hit_tokens(e.cache_hit_tokens)
213 .set_cache_miss_tokens(e.cache_miss_tokens)
214 .set_billable_input_tokens(e.billable_input_tokens)
215 .set_billable_output_tokens(e.billable_output_tokens)
216 .set_points_input(e.points_input)
217 .set_points_output(e.points_output)
218 .set_points_cache_hit(e.points_cache_hit)
219 .set_points_total(e.points_total)
220 .set_points_rate_input(e.points_rate_input)
221 .set_points_rate_output(e.points_rate_output)
222 .set_points_rate_cache_hit(e.points_rate_cache_hit)
223 .set_request_started_at(e.request_started_at)
224 .set_response_completed_at(e.response_completed_at)
225 .set_latency_ms(e.latency_ms)
226 .set_client_ip(e.client_ip.clone())
227 .set_user_agent(e.user_agent.clone())
228 .set_created_at(now())
229 .into();
230 am.insert(db.primary_connection()).await.map_err(|err| {
231 LproviderError::Db(DbError::InvalidConfig(format!(
232 "insert llm_call_log failed: {err}"
233 )))
234 })?;
235 Ok(())
236 }
237
238 #[allow(clippy::too_many_arguments)]
239 pub async fn admin_audit(
240 &self,
241 operator: &str,
242 rpc_method: &str,
243 instance_id: Option<&str>,
244 target: Option<&str>,
245 change_summary: Option<&str>,
246 config_sha256: Option<&str>,
247 effect_mode: Option<&str>,
248 result: &str,
249 error: Option<&str>,
250 ) -> Result<(), LproviderError> {
251 let db = self.conn()?;
252 let am: lprovider_admin_audit::ActiveModel = lprovider_admin_audit::ActiveModel::builder()
253 .set_operator(operator.to_string())
254 .set_rpc_method(rpc_method.to_string())
255 .set_instance_id(instance_id.map(|s| s.to_string()))
256 .set_target(target.map(|s| s.to_string()))
257 .set_change_summary(change_summary.map(|s| s.to_string()))
258 .set_config_sha256(config_sha256.map(|s| s.to_string()))
259 .set_effect_mode(effect_mode.map(|s| s.to_string()))
260 .set_result(result.to_string())
261 .set_error_message(error.map(sanitize_error))
262 .set_created_at(now())
263 .into();
264 am.insert(db.primary_connection()).await.map_err(|err| {
265 LproviderError::Db(DbError::InvalidConfig(format!(
266 "insert lprovider_admin_audit failed: {err}"
267 )))
268 })?;
269 Ok(())
270 }
271
272 #[allow(clippy::too_many_arguments)]
273 pub async fn query_audit(
274 &self,
275 tenant_id: Option<i64>,
276 subject_type: Option<&str>,
277 subject_id: Option<i64>,
278 model_alias: Option<&str>,
279 upstream_code: Option<&str>,
280 status: Option<&str>,
281 from_ms: Option<i64>,
282 to_ms: Option<i64>,
283 limit: u64,
284 offset: u64,
285 ) -> Result<(Vec<CallLogModel>, u64), LproviderError> {
286 let db = self.conn()?;
287 let mut select = llm_call_log::Entity::find();
288 if let Some(v) = tenant_id {
289 select = select.filter(llm_call_log::Column::TenantId.eq(v));
290 }
291 if let Some(v) = subject_type {
292 select = select.filter(llm_call_log::Column::SubjectType.eq(v.to_string()));
293 }
294 if let Some(v) = subject_id {
295 select = select.filter(llm_call_log::Column::SubjectId.eq(v));
296 }
297 if let Some(v) = model_alias {
298 select = select.filter(llm_call_log::Column::ModelAlias.eq(v.to_string()));
299 }
300 if let Some(v) = upstream_code {
301 select = select.filter(llm_call_log::Column::UpstreamCode.eq(v.to_string()));
302 }
303 if let Some(v) = status {
304 select = select.filter(llm_call_log::Column::Status.eq(v.to_string()));
305 }
306 if let Some(ms) = from_ms {
307 let ts = OffsetDateTime::from_unix_timestamp(ms / 1000)
308 .unwrap_or(OffsetDateTime::UNIX_EPOCH);
309 select = select.filter(llm_call_log::Column::CreatedAt.gte(ts));
310 }
311 if let Some(ms) = to_ms {
312 let ts = OffsetDateTime::from_unix_timestamp(ms / 1000)
313 .unwrap_or(OffsetDateTime::UNIX_EPOCH);
314 select = select.filter(llm_call_log::Column::CreatedAt.lte(ts));
315 }
316 let total = select
317 .clone()
318 .count(db.primary_connection())
319 .await
320 .map_err(db_err)?;
321 let rows = select
322 .order_by_desc(llm_call_log::Column::CreatedAt)
323 .offset(offset)
324 .limit(limit)
325 .all(db.primary_connection())
326 .await
327 .map_err(db_err)?;
328 Ok((rows, total))
329 }
330
331 #[allow(clippy::too_many_arguments)]
332 pub async fn aggregate_billing(
333 &self,
334 tenant_id: Option<i64>,
335 subject_type: Option<&str>,
336 subject_id: Option<i64>,
337 period_kind: Option<&str>,
338 period_start_ms: Option<i64>,
339 period_end_ms: Option<i64>,
340 model_alias: Option<&str>,
341 upstream_code: Option<&str>,
342 limit: u64,
343 offset: u64,
344 ) -> Result<(Vec<db::lprovider::llm_billing_aggregate::Model>, u64), LproviderError> {
345 use db::lprovider::llm_billing_aggregate;
346 let db = self.conn()?;
347 let mut select = llm_billing_aggregate::Entity::find();
348 if let Some(v) = tenant_id {
349 select = select.filter(llm_billing_aggregate::Column::TenantId.eq(v));
350 }
351 if let Some(v) = subject_type {
352 select = select.filter(llm_billing_aggregate::Column::SubjectType.eq(v.to_string()));
353 }
354 if let Some(v) = subject_id {
355 select = select.filter(llm_billing_aggregate::Column::SubjectId.eq(v));
356 }
357 if let Some(v) = period_kind {
358 select = select.filter(llm_billing_aggregate::Column::PeriodKind.eq(v.to_string()));
359 }
360 if let Some(ms) = period_start_ms {
361 let ts = OffsetDateTime::from_unix_timestamp(ms / 1000)
362 .unwrap_or(OffsetDateTime::UNIX_EPOCH);
363 select = select.filter(llm_billing_aggregate::Column::PeriodStart.gte(ts));
364 }
365 if let Some(ms) = period_end_ms {
366 let ts = OffsetDateTime::from_unix_timestamp(ms / 1000)
367 .unwrap_or(OffsetDateTime::UNIX_EPOCH);
368 select = select.filter(llm_billing_aggregate::Column::PeriodEnd.lte(ts));
369 }
370 if let Some(v) = model_alias {
371 select = select.filter(llm_billing_aggregate::Column::ModelAlias.eq(v.to_string()));
372 }
373 if let Some(v) = upstream_code {
374 select = select.filter(llm_billing_aggregate::Column::UpstreamCode.eq(v.to_string()));
375 }
376 let total = select
377 .clone()
378 .count(db.primary_connection())
379 .await
380 .map_err(db_err)?;
381 let rows = select
382 .order_by_desc(llm_billing_aggregate::Column::PeriodStart)
383 .offset(offset)
384 .limit(limit)
385 .all(db.primary_connection())
386 .await
387 .map_err(db_err)?;
388 Ok((rows, total))
389 }
390
391 pub async fn aggregate_incremental(&self) -> Result<u64, LproviderError> {
392 let db = self.conn()?;
393 let last: Option<time::OffsetDateTime> = {
394 let row = db
395 .writer()
396 .query_one_raw(Statement::from_string(
397 DatabaseBackend::Postgres,
398 "SELECT MAX(period_end) AS max_end FROM llm_billing_aggregate",
399 ))
400 .await
401 .map_err(db_err)?;
402 row.and_then(|r| r.try_get("", "max_end").ok())
403 };
404 let since = last.unwrap_or_else(|| now() - time::Duration::hours(1));
405 let sql = r#"
406INSERT INTO llm_billing_aggregate
407 (tenant_id, subject_type, subject_id, period_start, period_end, period_kind,
408 model_alias, upstream_code, call_count, success_count, error_count,
409 total_input_tokens, total_output_tokens, total_cache_hit_tokens, total_points,
410 created_at, updated_at)
411SELECT tenant_id, subject_type, subject_id,
412 date_trunc('hour', created_at),
413 date_trunc('hour', created_at) + interval '1 hour',
414 'hour', model_alias, upstream_code,
415 count(*) AS c, count(*) FILTER (WHERE status = 'success'),
416 count(*) FILTER (WHERE status = 'error'),
417 COALESCE(sum(prompt_tokens), 0), COALESCE(sum(completion_tokens), 0),
418 COALESCE(sum(cache_hit_tokens), 0), COALESCE(sum(points_total), 0),
419 now(), now()
420FROM llm_call_log
421WHERE created_at >= $1 AND subject_type IS NOT NULL AND subject_id IS NOT NULL
422GROUP BY tenant_id, subject_type, subject_id, date_trunc('hour', created_at),
423 model_alias, upstream_code
424ON CONFLICT (tenant_id, subject_type, subject_id, period_start, period_kind,
425 model_alias, upstream_code)
426DO UPDATE SET
427 call_count = llm_billing_aggregate.call_count + EXCLUDED.call_count,
428 success_count = llm_billing_aggregate.success_count + EXCLUDED.success_count,
429 error_count = llm_billing_aggregate.error_count + EXCLUDED.error_count,
430 total_input_tokens = llm_billing_aggregate.total_input_tokens + EXCLUDED.total_input_tokens,
431 total_output_tokens = llm_billing_aggregate.total_output_tokens + EXCLUDED.total_output_tokens,
432 total_cache_hit_tokens = llm_billing_aggregate.total_cache_hit_tokens + EXCLUDED.total_cache_hit_tokens,
433 total_points = llm_billing_aggregate.total_points + EXCLUDED.total_points,
434 updated_at = now()
435"#;
436 let since_iso = since
437 .format(&time::format_description::well_known::Rfc3339)
438 .map_err(|e| LproviderError::Internal(format!("format since: {e}")))?;
439 let sql = sql.replace("created_at >= $1", "created_at >= $1::timestamptz");
440 let res = db
441 .writer()
442 .execute_raw(Statement::from_sql_and_values(
443 DatabaseBackend::Postgres,
444 &sql,
445 [Value::String(Some(since_iso))],
446 ))
447 .await
448 .map_err(db_err)?;
449 Ok(res.rows_affected())
450 }
451
452 pub async fn reconcile_daily(&self) -> Result<f64, LproviderError> {
453 let db = self.conn()?;
454 let a: i64 = db
455 .writer()
456 .query_one_raw(Statement::from_string(
457 DatabaseBackend::Postgres,
458 "SELECT COALESCE(SUM(points_total), 0) AS a FROM llm_call_log
459 WHERE created_at >= date_trunc('day', now())
460 AND subject_type IS NOT NULL AND subject_id IS NOT NULL",
461 ))
462 .await
463 .map_err(db_err)?
464 .and_then(|r| r.try_get("", "a").ok())
465 .unwrap_or(0);
466 let b: i64 = db
467 .writer()
468 .query_one_raw(Statement::from_string(
469 DatabaseBackend::Postgres,
470 "SELECT COALESCE(SUM(total_points), 0) AS b FROM llm_billing_aggregate
471 WHERE period_start >= date_trunc('day', now())",
472 ))
473 .await
474 .map_err(db_err)?
475 .and_then(|r| r.try_get("", "b").ok())
476 .unwrap_or(0);
477 let ratio = if a == 0 {
478 0.0
479 } else {
480 (b as f64 - a as f64).abs() / a as f64
481 };
482 if ratio > 0.001 {
483 tracing::error!(
484 ratio,
485 call_log_points = a,
486 aggregate_points = b,
487 "lprovider billing reconcile drift exceeds 0.1%"
488 );
489 }
490 Ok(ratio)
491 }
492}
493
494fn db_err(err: sea_orm::DbErr) -> LproviderError {
495 LproviderError::Db(DbError::InvalidConfig(format!("sea-orm: {err}")))
496}
Deleted agent/lprovider/billing.rs
@@ -1,72 +0,0 @@
1use config::lprovider::{LproviderPricingSettings, PointsPer1k};
2
3#[derive(Clone, Copy, Debug, Default)]
4pub struct Bill {
5 pub billable_input_tokens: i64,
6 pub billable_output_tokens: i64,
7 pub billable_cache_hit_tokens: i64,
8 pub points_input: i64,
9 pub points_output: i64,
10 pub points_cache_hit: i64,
11 pub points_total: i64,
12}
13
14pub fn ceil_div(numerator: i64, denominator: i64) -> i64 {
15 if numerator <= 0 {
16 return 0;
17 }
18 (numerator + denominator - 1) / denominator
19}
20
21pub fn points_for_tokens(tokens: i64, points_per_1k: i64) -> i64 {
22 if points_per_1k <= 0 || tokens <= 0 {
23 return 0;
24 }
25 ceil_div(tokens * points_per_1k, 1000)
26}
27
28pub fn rates_for<'a>(
29 upstream_code: &str,
30 pricing: &'a LproviderPricingSettings,
31) -> &'a PointsPer1k {
32 pricing
33 .rates
34 .get(upstream_code)
35 .unwrap_or(&pricing.default_per_1k)
36}
37
38pub fn compute_bill(
39 prompt_tokens: i64,
40 completion_tokens: i64,
41 cache_hit_tokens: i64,
42 rate: &PointsPer1k,
43) -> Bill {
44 let billable_input = (prompt_tokens - cache_hit_tokens).max(0);
45 let billable_output = completion_tokens.max(0);
46 let billable_cache_hit = cache_hit_tokens.max(0);
47 let points_input = points_for_tokens(billable_input, rate.points_per_1k_input_tokens);
48 let points_output = points_for_tokens(billable_output, rate.points_per_1k_output_tokens);
49 let points_cache_hit =
50 points_for_tokens(billable_cache_hit, rate.points_per_1k_cache_hit_tokens);
51 Bill {
52 billable_input_tokens: billable_input,
53 billable_output_tokens: billable_output,
54 billable_cache_hit_tokens: billable_cache_hit,
55 points_input,
56 points_output,
57 points_cache_hit,
58 points_total: points_input + points_output + points_cache_hit,
59 }
60}
61
62pub fn worst_case_estimate(
63 estimated_input_tokens: i64,
64 max_output_tokens: i64,
65 rate: &PointsPer1k,
66) -> i64 {
67 let worst_rate = rate
68 .points_per_1k_input_tokens
69 .max(rate.points_per_1k_output_tokens)
70 .max(rate.points_per_1k_cache_hit_tokens);
71 points_for_tokens(estimated_input_tokens + max_output_tokens, worst_rate)
72}
Deleted agent/lprovider/build.rs
@@ -1,11 +0,0 @@
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2 println!("cargo:rerun-if-changed=ai.proto");
3 println!("cargo:rerun-if-changed=admin.proto");
4
5 tonic_prost_build::configure()
6 .build_client(true)
7 .build_server(true)
8 .compile_protos(&["ai.proto", "admin.proto"], &["."])?;
9
10 Ok(())
11}
Deleted agent/lprovider/Cargo.toml
@@ -1,61 +0,0 @@
1[package]
2name = "lprovider"
3version.workspace = true
4edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
10
11[lib]
12path = "lib.rs"
13name = "lprovider"
14
15[[bin]]
16name = "lprovider"
17path = "main.rs"
18
19[build-dependencies]
20tonic-prost-build.workspace = true
21
22[dependencies]
23aes-gcm.workspace = true
24async-trait.workspace = true
25axum.workspace = true
26base64.workspace = true
27bytes.workspace = true
28cache.workspace = true
29clap.workspace = true
30config.workspace = true
31db.workspace = true
32futures.workspace = true
33hex.workspace = true
34http.workspace = true
35llm = { path = "../llm" }
36metrics.workspace = true
37prost.workspace = true
38prost-types.workspace = true
39rand.workspace = true
40redis.workspace = true
41reqwest.workspace = true
42sea-orm.workspace = true
43secret.workspace = true
44serde.workspace = true
45serde_json.workspace = true
46serde_yaml.workspace = true
47sha2.workspace = true
48thiserror.workspace = true
49time.workspace = true
50tokio.workspace = true
51tokio-stream.workspace = true
52tokio-util.workspace = true
53tonic.workspace = true
54tonic-health.workspace = true
55tonic-prost.workspace = true
56tracing.workspace = true
57tracing-subscriber.workspace = true
58uuid.workspace = true
59
60[dev-dependencies]
61tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
Deleted agent/lprovider/config.rs
@@ -1,72 +0,0 @@
1use std::path::PathBuf;
2
3use config::{AppConfig, ConfigLoader};
4use sha2::{Digest, Sha256};
5
6use crate::error::LproviderError;
7
8#[derive(Debug, Clone, Default)]
9pub struct CliOverrides {
10 pub config: PathBuf,
11 pub bind: Option<String>,
12 pub port: Option<u16>,
13 pub admin_bind: Option<String>,
14 pub admin_port: Option<u16>,
15 pub admin_cert: Option<PathBuf>,
16 pub admin_key: Option<PathBuf>,
17 pub instance_id: Option<String>,
18 pub cluster_name: Option<String>,
19}
20
21#[derive(Debug)]
22pub struct RuntimeConfig {
23 pub app: AppConfig,
24 pub config_path: PathBuf,
25 pub config_sha256: String,
26}
27
28impl RuntimeConfig {
29 pub fn load(cli: &CliOverrides) -> Result<Self, LproviderError> {
30 let mut app = ConfigLoader::new()
31 .config_file(cli.config.clone())
32 .load()
33 .map_err(|e| LproviderError::Config(e.to_string()))?;
34
35 if let Some(bind) = &cli.bind {
36 app.lprovider.bind = bind.clone();
37 }
38 if let Some(port) = cli.port {
39 app.lprovider.port = port;
40 }
41 if let Some(bind) = &cli.admin_bind {
42 app.lprovider.admin_bind = bind.clone();
43 }
44 if let Some(port) = cli.admin_port {
45 app.lprovider.admin_port = port;
46 }
47
48 app.lprovider.is_valid().map_err(LproviderError::Config)?;
49
50 let config_sha256 = sha256_file(&cli.config)?;
51
52 Ok(Self {
53 app,
54 config_path: cli.config.clone(),
55 config_sha256,
56 })
57 }
58
59 pub fn lp(&self) -> &config::lprovider::LproviderConfig {
60 &self.app.lprovider
61 }
62}
63
64pub fn sha256_file(path: &std::path::Path) -> Result<String, LproviderError> {
65 let bytes = std::fs::read(path).map_err(|e| LproviderError::Config(e.to_string()))?;
66 Ok(sha256_hex(&bytes))
67}
68
69pub fn sha256_hex(bytes: &[u8]) -> String {
70 let digest = Sha256::digest(bytes);
71 hex::encode(digest)
72}
Deleted agent/lprovider/credentials.rs
@@ -1,46 +0,0 @@
1use config::lprovider::{LproviderConfig, LproviderUpstream, LproviderUpstreamCredential};
2use llm::provider::openai::SecretString;
3use secret::{
4 SecretError, envelope_key_from_env, is_envelope, key_fingerprint, resolve,
5 resolve_credential_ref,
6};
7
8pub struct ResolvedCredential {
9 pub upstream_code: String,
10 pub label: String,
11 pub api_key: SecretString,
12 pub fingerprint: String,
13}
14
15pub fn resolve_all(cfg: &LproviderConfig) -> Result<Vec<ResolvedCredential>, SecretError> {
16 let mut out = Vec::new();
17 for upstream in &cfg.upstreams {
18 if !upstream.enabled {
19 continue;
20 }
21 for cred in &upstream.credentials {
22 out.push(resolve_one(upstream, cred)?);
23 }
24 }
25 Ok(out)
26}
27
28fn resolve_one(
29 upstream: &LproviderUpstream,
30 cred: &LproviderUpstreamCredential,
31) -> Result<ResolvedCredential, SecretError> {
32 let raw = resolve_credential_ref(&cred.credential_ref)?;
33 let api_key = if is_envelope(&raw) {
34 let key = envelope_key_from_env()?;
35 resolve(&key, &raw)?
36 } else {
37 raw
38 };
39 let fingerprint = key_fingerprint(&api_key);
40 Ok(ResolvedCredential {
41 upstream_code: upstream.code.clone(),
42 label: cred.label.clone(),
43 api_key: SecretString::new(api_key),
44 fingerprint,
45 })
46}
Deleted agent/lprovider/error.rs
@@ -1,165 +0,0 @@
1use axum::Json;
2use axum::http::StatusCode;
3use axum::response::{IntoResponse, Response};
4use serde_json::json;
5
6#[derive(Debug, thiserror::Error)]
7pub enum LproviderError {
8 #[error("config error: {0}")]
9 Config(String),
10
11 #[error("database error: {0}")]
12 Db(#[from] db::conn::DbError),
13
14 #[error("redis error: {0}")]
15 Redis(String),
16
17 #[error("invalid request: {0}")]
18 InvalidRequest(String),
19
20 #[error("auth: {0}")]
21 Auth(AuthError),
22
23 #[error("wallet: {0}")]
24 Wallet(WalletError),
25
26 #[error("quota exceeded: {0}")]
27 QuotaExceeded(String),
28
29 #[error("upstream: {0}")]
30 Upstream(UpstreamError),
31
32 #[error("resource locked: {0}")]
33 ResourceLocked(String),
34
35 #[error("not found: {0}")]
36 NotFound(String),
37
38 #[error("conflict: {0}")]
39 Conflict(String),
40
41 #[error("internal error: {0}")]
42 Internal(String),
43}
44
45#[derive(Debug, thiserror::Error)]
46pub enum AuthError {
47 #[error("missing bearer token")]
48 MissingToken,
49
50 #[error("invalid bearer token")]
51 InvalidToken,
52
53 #[error("permission denied: {0}")]
54 PermissionDenied(String),
55}
56
57#[derive(Debug, thiserror::Error)]
58pub enum WalletError {
59 #[error("wallet id required")]
60 WalletRequired,
61
62 #[error("wallet not found: {0}")]
63 WalletNotFound(i64),
64
65 #[error("wallet not active: {0}")]
66 WalletNotActive(i64),
67
68 #[error("insufficient balance")]
69 InsufficientBalance,
70
71 #[error("wallet debit failed")]
72 DebitFailed,
73
74 #[error("wallet owner mismatch")]
75 OwnerMismatch,
76}
77
78#[derive(Debug, thiserror::Error)]
79pub enum UpstreamError {
80 #[error("no upstream available")]
81 NoUpstreamAvailable,
82
83 #[error("no key available for upstream {0}")]
84 NoKeyAvailable(String),
85
86 #[error("context length exceeded")]
87 ContextLengthExceeded,
88
89 #[error("all upstream attempts failed: {0}")]
90 AllFailed(String),
91}
92
93impl LproviderError {
94 pub fn status_code(&self) -> StatusCode {
95 match self {
96 LproviderError::Config(_) | LproviderError::Db(_) | LproviderError::Internal(_) => {
97 StatusCode::INTERNAL_SERVER_ERROR
98 }
99 LproviderError::Redis(_)
100 | LproviderError::Upstream(UpstreamError::NoUpstreamAvailable)
101 | LproviderError::Upstream(UpstreamError::NoKeyAvailable(_)) => {
102 StatusCode::SERVICE_UNAVAILABLE
103 }
104 LproviderError::Upstream(UpstreamError::AllFailed(_)) => StatusCode::BAD_GATEWAY,
105 LproviderError::InvalidRequest(_)
106 | LproviderError::Upstream(UpstreamError::ContextLengthExceeded) => {
107 StatusCode::BAD_REQUEST
108 }
109 LproviderError::Auth(AuthError::MissingToken)
110 | LproviderError::Auth(AuthError::InvalidToken) => StatusCode::UNAUTHORIZED,
111 LproviderError::Auth(AuthError::PermissionDenied(_))
112 | LproviderError::Wallet(WalletError::WalletNotActive(_))
113 | LproviderError::Wallet(WalletError::OwnerMismatch) => StatusCode::FORBIDDEN,
114 LproviderError::Wallet(WalletError::WalletRequired)
115 | LproviderError::Wallet(WalletError::InsufficientBalance)
116 | LproviderError::Wallet(WalletError::DebitFailed) => StatusCode::PAYMENT_REQUIRED,
117 LproviderError::Wallet(WalletError::WalletNotFound(_)) => StatusCode::NOT_FOUND,
118 LproviderError::NotFound(_) => StatusCode::NOT_FOUND,
119 LproviderError::ResourceLocked(_) | LproviderError::Conflict(_) => StatusCode::CONFLICT,
120 LproviderError::QuotaExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
121 }
122 }
123}
124
125impl IntoResponse for LproviderError {
126 fn into_response(self) -> Response {
127 let status = self.status_code();
128 let body = json!({
129 "error": {
130 "message": self.to_string(),
131 "type": error_type(&self),
132 "param": null,
133 "code": status.as_u16(),
134 }
135 });
136 (status, Json(body)).into_response()
137 }
138}
139
140fn error_type(err: &LproviderError) -> &'static str {
141 match err {
142 LproviderError::Config(_) => "config_error",
143 LproviderError::Db(_) => "database_error",
144 LproviderError::Redis(_) => "redis_error",
145 LproviderError::InvalidRequest(_) => "invalid_request_error",
146 LproviderError::Auth(AuthError::MissingToken) => "missing_token",
147 LproviderError::Auth(AuthError::InvalidToken) => "invalid_token",
148 LproviderError::Auth(AuthError::PermissionDenied(_)) => "permission_denied",
149 LproviderError::Wallet(WalletError::WalletRequired) => "wallet_required",
150 LproviderError::Wallet(WalletError::WalletNotFound(_)) => "wallet_not_found",
151 LproviderError::Wallet(WalletError::WalletNotActive(_)) => "wallet_not_active",
152 LproviderError::Wallet(WalletError::InsufficientBalance) => "insufficient_balance",
153 LproviderError::Wallet(WalletError::DebitFailed) => "wallet_debit_failed",
154 LproviderError::Wallet(WalletError::OwnerMismatch) => "wallet_owner_mismatch",
155 LproviderError::QuotaExceeded(_) => "quota_exceeded",
156 LproviderError::Upstream(UpstreamError::NoUpstreamAvailable) => "no_upstream_available",
157 LproviderError::Upstream(UpstreamError::NoKeyAvailable(_)) => "no_key_available",
158 LproviderError::Upstream(UpstreamError::ContextLengthExceeded) => "context_length_exceeded",
159 LproviderError::Upstream(UpstreamError::AllFailed(_)) => "all_upstreams_failed",
160 LproviderError::ResourceLocked(_) => "resource_locked",
161 LproviderError::NotFound(_) => "not_found",
162 LproviderError::Conflict(_) => "conflict",
163 LproviderError::Internal(_) => "internal_error",
164 }
165}
Deleted agent/lprovider/http.rs
@@ -1,591 +0,0 @@
1use std::convert::Infallible;
2use std::sync::Arc;
3
4use axum::extract::State;
5use axum::http::{HeaderMap, StatusCode};
6use axum::response::sse::{Event, KeepAlive, Sse};
7use axum::response::{IntoResponse, Response};
8use axum::routing::{get, post};
9use axum::{Json, Router as AxumRouter};
10use futures::StreamExt;
11use llm::types::{
12 ChatMessage, ChatRequest, ContentPart, FinishReason, ImageDetail, MessageContent, MessageRole,
13 ResponseFormat, StreamEvent, ToolCall, ToolDefinition,
14};
15use serde::Deserialize;
16use serde_json::{Value, json};
17use uuid::Uuid;
18
19use crate::affinity::AffinityManager;
20use crate::error::{AuthError, LproviderError, WalletError};
21use crate::router::Router;
22use crate::router::{CallOutcomeFull, ChatContext, ChatSelectionSpec};
23use crate::state::AppState;
24use crate::wallet::WalletService;
25
26pub struct RouterContext {
27 pub state: Arc<AppState>,
28 pub router: Arc<Router>,
29 pub wallets: Arc<WalletService>,
30 pub affinity: Arc<AffinityManager>,
31}
32
33pub fn router(
34 state: Arc<AppState>,
35 router: Arc<Router>,
36 affinity: Arc<AffinityManager>,
37) -> AxumRouter {
38 let wallets = Arc::new(WalletService::new(state.db.clone().map(Arc::new)));
39 let ctx = Arc::new(RouterContext {
40 state,
41 router,
42 wallets,
43 affinity,
44 });
45 AxumRouter::new()
46 .route("/health/live", get(health_live))
47 .route("/health/ready", get(health_ready))
48 .route("/v1/models", get(list_models))
49 .route("/v1/chat/completions", post(chat_completions))
50 .with_state(ctx)
51}
52
53async fn health_live() -> impl IntoResponse {
54 (StatusCode::OK, Json(json!({ "status": "ok" })))
55}
56
57async fn health_ready(State(ctx): State<Arc<RouterContext>>) -> impl IntoResponse {
58 match ctx.state.ready().await {
59 Ok(()) => (
60 StatusCode::OK,
61 Json(json!({
62 "status": "ready",
63 "snapshot_version": ctx.state.snapshot_version(),
64 })),
65 ),
66 Err(reason) => (
67 StatusCode::SERVICE_UNAVAILABLE,
68 Json(json!({
69 "status": "not_ready",
70 "reason": reason,
71 })),
72 ),
73 }
74}
75
76async fn list_models(State(ctx): State<Arc<RouterContext>>) -> impl IntoResponse {
77 let runtime = ctx.state.runtime.read().await;
78 let lp = runtime.lp();
79
80 let created = ctx.state.started_at.unix_timestamp();
81
82 let mut capability_union: Vec<&str> = Vec::new();
83 let mut ctx_window = lp.max_context_tokens;
84 let mut max_out = lp.max_output_tokens;
85 for upstream in lp.upstreams.iter().filter(|u| u.enabled) {
86 for cap in &upstream.capabilities {
87 if !capability_union.contains(&cap.as_str()) {
88 capability_union.push(cap.as_str());
89 }
90 }
91 ctx_window = ctx_window.min(upstream.context_window);
92 max_out = max_out.min(upstream.max_output);
93 }
94
95 let data: Vec<_> = lp
96 .models
97 .iter()
98 .map(|m| {
99 json!({
100 "id": m.alias,
101 "object": "model",
102 "created": created,
103 "owned_by": "lprovider",
104 "description": m.description,
105 "default_strategy": m.default_strategy,
106 "context_window": ctx_window,
107 "max_output_tokens": max_out,
108 "capabilities": capability_union,
109 })
110 })
111 .collect();
112
113 (
114 StatusCode::OK,
115 Json(json!({ "object": "list", "data": data })),
116 )
117}
118
119#[derive(Deserialize)]
120struct ChatBody {
121 model: Option<String>,
122 messages: Vec<InMessage>,
123 #[serde(default)]
124 tools: Vec<InTool>,
125 max_tokens: Option<usize>,
126 temperature: Option<f32>,
127 top_p: Option<f32>,
128 response_format: Option<InResponseFormat>,
129 stop: Option<Vec<String>>,
130 #[serde(default)]
131 stream: bool,
132}
133
134#[derive(Deserialize)]
135struct InMessage {
136 role: String,
137 #[serde(default)]
138 content: Option<Value>,
139 #[serde(default)]
140 tool_calls: Option<Vec<InToolCall>>,
141 tool_call_id: Option<String>,
142 name: Option<String>,
143}
144
145#[derive(Deserialize)]
146struct InToolCall {
147 id: String,
148 function: InFunctionCall,
149}
150
151#[derive(Deserialize)]
152struct InFunctionCall {
153 name: String,
154 #[serde(default)]
155 arguments: Value,
156}
157
158#[derive(Deserialize)]
159struct InTool {
160 function: InToolDef,
161}
162
163#[derive(Deserialize)]
164struct InToolDef {
165 name: String,
166 description: Option<String>,
167 parameters: Option<Value>,
168}
169
170#[derive(Deserialize)]
171struct InResponseFormat {
172 #[serde(rename = "type")]
173 kind: Option<String>,
174}
175
176async fn chat_completions(
177 State(ctx): State<Arc<RouterContext>>,
178 headers: HeaderMap,
179 Json(body): Json<ChatBody>,
180) -> Response {
181 match build_chat_context(&ctx, &headers, &body).await {
182 Ok(chat_ctx) => {
183 let request_id = chat_ctx.request_id;
184 let model_alias = chat_ctx.model_alias.clone();
185 if body.stream {
186 match ctx.router.handle_stream(chat_ctx).await {
187 Ok(stream) => {
188 let request_id_str = request_id.to_string();
189 let sse_stream = stream.map(move |item| {
190 let data = match item {
191 Ok(ev) => sse_chunk(&ev, &request_id_str, &model_alias),
192 Err(e) => format!(
193 "data: {}\n\n",
194 json!({
195 "error": {
196 "message": e.to_string(),
197 "type": "stream_error",
198 }
199 })
200 ),
201 };
202 Ok::<Event, Infallible>(Event::default().data(data))
203 });
204 let mut resp = Sse::new(sse_stream)
205 .keep_alive(KeepAlive::default())
206 .into_response();
207 resp.headers_mut()
208 .insert("X-Request-Id", request_id.to_string().parse().unwrap());
209 resp.headers_mut().insert(
210 axum::http::header::CONTENT_TYPE,
211 "text/event-stream".parse().unwrap(),
212 );
213 resp
214 }
215 Err(e) => e.into_response(),
216 }
217 } else {
218 match ctx.router.handle_chat(chat_ctx).await {
219 Ok(outcome) => {
220 let mut resp =
221 (StatusCode::OK, Json(openai_response(&outcome))).into_response();
222 resp.headers_mut().insert(
223 "X-Request-Id",
224 outcome.request_id.to_string().parse().unwrap(),
225 );
226 resp
227 }
228 Err(e) => e.into_response(),
229 }
230 }
231 }
232 Err(e) => e.into_response(),
233 }
234}
235
236async fn build_chat_context(
237 ctx: &RouterContext,
238 headers: &HeaderMap,
239 body: &ChatBody,
240) -> Result<ChatContext, LproviderError> {
241 let lp = ctx.state.runtime.read().await.lp().clone();
242
243 if let Some(expected) = &lp.auth.bearer_token {
244 let provided = headers
245 .get(axum::http::header::AUTHORIZATION)
246 .and_then(|v| v.to_str().ok())
247 .and_then(|v| v.strip_prefix("Bearer "))
248 .unwrap_or("");
249 if provided != expected {
250 return Err(LproviderError::Auth(AuthError::InvalidToken));
251 }
252 }
253
254 let tenant_id = headers
255 .get("x-tenant-id")
256 .and_then(|v| v.to_str().ok())
257 .and_then(|v| v.trim().parse::<i64>().ok())
258 .ok_or_else(|| LproviderError::InvalidRequest("X-Tenant-Id header required".into()))?;
259
260 let wallet_id = headers
261 .get("x-wallet-id")
262 .and_then(|v| v.to_str().ok())
263 .and_then(|v| v.trim().parse::<i64>().ok())
264 .ok_or(LproviderError::Wallet(WalletError::WalletRequired))?;
265
266 let wallet = ctx.wallets.get_wallet(wallet_id).await?;
267 let subject_type = wallet.owner_type;
268 let subject_id = wallet.owner_id;
269
270 let session_key_override = match headers.get("x-session-key").and_then(|v| v.to_str().ok()) {
271 Some(key) => {
272 ctx.affinity.validate_session_key_override(key)?;
273 Some(key.to_string())
274 }
275 None => None,
276 };
277
278 let request_id = headers
279 .get("x-request-id")
280 .and_then(|v| v.to_str().ok())
281 .and_then(|v| Uuid::parse_str(v).ok())
282 .unwrap_or_else(Uuid::now_v7);
283
284 let client_ip = headers
285 .get("x-forwarded-for")
286 .and_then(|v| v.to_str().ok())
287 .and_then(|v| v.split(',').next())
288 .map(|s| s.trim().to_string());
289
290 let user_agent = headers
291 .get(axum::http::header::USER_AGENT)
292 .and_then(|v| v.to_str().ok())
293 .map(|s| s.to_string());
294
295 let model_alias = body.model.clone().unwrap_or_else(|| "auto".to_string());
296 let request = map_chat_request(body)?;
297 let max_output = body
298 .max_tokens
299 .unwrap_or(lp.max_output_tokens)
300 .min(lp.max_output_tokens);
301
302 Ok(ChatContext {
303 request_id,
304 trace_id: None,
305 tenant_id,
306 wallet_id: Some(wallet_id),
307 subject_type,
308 subject_id,
309 anonymous: false,
310 model_alias,
311 request,
312 max_output,
313 stream: body.stream,
314 priority: 0,
315 caller: None,
316 caller_service: None,
317 session_key_override,
318 selection: ChatSelectionSpec::default(),
319 client_ip,
320 user_agent,
321 })
322}
323
324fn map_chat_request(body: &ChatBody) -> Result<ChatRequest, LproviderError> {
325 let mut messages = Vec::with_capacity(body.messages.len());
326 for m in &body.messages {
327 let content = map_content(m.content.clone())?;
328 let tool_calls = map_tool_calls(m.tool_calls.as_ref());
329 let msg = match m.role.as_str() {
330 "system" => ChatMessage {
331 role: MessageRole::System,
332 content,
333 name: m.name.clone(),
334 tool_calls: Vec::new(),
335 tool_call_id: None,
336 },
337 "user" => ChatMessage {
338 role: MessageRole::User,
339 content,
340 name: m.name.clone(),
341 tool_calls: Vec::new(),
342 tool_call_id: None,
343 },
344 "assistant" => ChatMessage {
345 role: MessageRole::Assistant,
346 content,
347 name: m.name.clone(),
348 tool_calls,
349 tool_call_id: None,
350 },
351 "tool" => ChatMessage {
352 role: MessageRole::Tool,
353 content,
354 name: m.name.clone(),
355 tool_calls: Vec::new(),
356 tool_call_id: m.tool_call_id.clone(),
357 },
358 other => {
359 return Err(LproviderError::InvalidRequest(format!(
360 "unsupported message role: {other}"
361 )));
362 }
363 };
364 messages.push(msg);
365 }
366
367 let tools: Vec<ToolDefinition> = body
368 .tools
369 .iter()
370 .map(|t| ToolDefinition {
371 name: t.function.name.clone(),
372 description: t.function.description.clone().unwrap_or_default(),
373 parameters: t
374 .function
375 .parameters
376 .clone()
377 .unwrap_or_else(|| json!({ "type": "object", "properties": {} })),
378 concurrency: Default::default(),
379 })
380 .collect();
381
382 let mut request = ChatRequest::new(messages);
383 if !tools.is_empty() {
384 request = request.with_tools(tools);
385 }
386 if let Some(mt) = body.max_tokens {
387 request = request.with_max_tokens(mt);
388 }
389 if let Some(t) = body.temperature {
390 request = request.with_temperature(t);
391 }
392 if let Some(p) = body.top_p {
393 request = request.with_top_p(p);
394 }
395 if body
396 .response_format
397 .as_ref()
398 .and_then(|rf| rf.kind.as_deref())
399 == Some("json_object")
400 {
401 request = request.with_response_format(ResponseFormat::Json);
402 }
403 if let Some(stop) = &body.stop {
404 request = request.with_stop_sequences(stop.clone());
405 }
406 Ok(request)
407}
408
409fn map_content(v: Option<Value>) -> Result<MessageContent, LproviderError> {
410 match v {
411 None => Ok(MessageContent::Text(String::new())),
412 Some(Value::String(s)) => Ok(MessageContent::Text(s)),
413 Some(Value::Array(parts)) => {
414 let mut out = Vec::with_capacity(parts.len());
415 for p in parts {
416 match p.get("type").and_then(|t| t.as_str()) {
417 Some("text") => {
418 if let Some(t) = p.get("text").and_then(|t| t.as_str()) {
419 out.push(ContentPart::Text(t.to_string()));
420 }
421 }
422 Some("image_url") => {
423 let url = p
424 .get("image_url")
425 .and_then(|i| i.get("url"))
426 .and_then(|u| u.as_str())
427 .unwrap_or_default()
428 .to_string();
429 let detail = match p
430 .get("image_url")
431 .and_then(|i| i.get("detail"))
432 .and_then(|d| d.as_str())
433 .unwrap_or("auto")
434 {
435 "low" => ImageDetail::Low,
436 "high" => ImageDetail::High,
437 _ => ImageDetail::Auto,
438 };
439 out.push(ContentPart::Image { url, detail });
440 }
441 _ => {}
442 }
443 }
444 Ok(MessageContent::Parts(out))
445 }
446 Some(_) => Err(LproviderError::InvalidRequest(
447 "invalid message content".into(),
448 )),
449 }
450}
451
452fn map_tool_calls(tc: Option<&Vec<InToolCall>>) -> Vec<ToolCall> {
453 tc.map(|list| {
454 list.iter()
455 .map(|t| ToolCall {
456 id: t.id.clone(),
457 name: t.function.name.clone(),
458 arguments: match &t.function.arguments {
459 Value::String(s) => serde_json::from_str::<Value>(s).unwrap_or(Value::Null),
460 other => other.clone(),
461 },
462 })
463 .collect()
464 })
465 .unwrap_or_default()
466}
467
468fn finish_reason_str(fr: &FinishReason) -> &'static str {
469 match fr {
470 FinishReason::Stop => "stop",
471 FinishReason::ToolUse => "tool_calls",
472 FinishReason::MaxTokens => "length",
473 FinishReason::ContentFilter => "content_filter",
474 FinishReason::Error => "error",
475 }
476}
477
478fn created_now() -> u64 {
479 std::time::SystemTime::now()
480 .duration_since(std::time::UNIX_EPOCH)
481 .unwrap_or_default()
482 .as_secs()
483}
484
485fn openai_response(o: &CallOutcomeFull) -> Value {
486 let mut message = json!({
487 "role": "assistant",
488 "content": o.response.content,
489 });
490 if !o.response.tool_calls.is_empty() {
491 message["tool_calls"] = Value::Array(
492 o.response
493 .tool_calls
494 .iter()
495 .map(|t| {
496 json!({
497 "id": t.id,
498 "type": "function",
499 "function": {
500 "name": t.name,
501 "arguments": t.arguments.to_string(),
502 },
503 })
504 })
505 .collect(),
506 );
507 }
508 json!({
509 "id": format!("chatcmpl-{}", o.request_id),
510 "object": "chat.completion",
511 "created": created_now(),
512 "model": o.model,
513 "choices": [{
514 "index": 0,
515 "message": message,
516 "finish_reason": finish_reason_str(&o.response.finish_reason),
517 }],
518 "usage": {
519 "prompt_tokens": o.response.usage.input_tokens,
520 "completion_tokens": o.response.usage.output_tokens,
521 "total_tokens": o.response.usage.total(),
522 "prompt_cache_hit_tokens": o.bill.billable_cache_hit_tokens,
523 },
524 })
525}
526
527fn sse_chunk(ev: &StreamEvent, id: &str, model: &str) -> String {
528 let base = || {
529 json!({
530 "id": id,
531 "object": "chat.completion.chunk",
532 "created": created_now(),
533 "model": model,
534 })
535 };
536 let mut chunk = base();
537 match ev {
538 StreamEvent::TextDelta { delta } => {
539 chunk["choices"] = json!([{
540 "index": 0,
541 "delta": { "content": delta },
542 "finish_reason": Value::Null,
543 }]);
544 }
545 StreamEvent::ReasoningDelta { delta } => {
546 chunk["choices"] = json!([{
547 "index": 0,
548 "delta": { "reasoning_content": delta },
549 "finish_reason": Value::Null,
550 }]);
551 }
552 StreamEvent::ToolCallDelta {
553 index,
554 id: call_id,
555 name,
556 args_delta,
557 } => {
558 chunk["choices"] = json!([{
559 "index": 0,
560 "delta": {
561 "tool_calls": [{
562 "index": index.unwrap_or(0),
563 "id": call_id,
564 "type": "function",
565 "function": {
566 "name": name.clone().unwrap_or_default(),
567 "arguments": args_delta,
568 },
569 }],
570 },
571 "finish_reason": Value::Null,
572 }]);
573 }
574 StreamEvent::Usage(u) => {
575 chunk["choices"] = json!([]);
576 chunk["usage"] = json!({
577 "prompt_tokens": u.input_tokens,
578 "completion_tokens": u.output_tokens,
579 "total_tokens": u.total(),
580 });
581 }
582 StreamEvent::Done { finish_reason } => {
583 chunk["choices"] = json!([{
584 "index": 0,
585 "delta": {},
586 "finish_reason": finish_reason_str(finish_reason),
587 }]);
588 }
589 }
590 format!("data: {chunk}\n\n")
591}
Deleted agent/lprovider/lib.rs
@@ -1,29 +0,0 @@
1pub mod admin_grpc;
2pub mod affinity;
3pub mod ai_grpc;
4pub mod audit;
5pub mod billing;
6pub mod config;
7pub mod credentials;
8pub mod error;
9pub mod http;
10pub mod quota;
11pub mod rotation;
12pub mod router;
13pub mod selector;
14pub mod state;
15pub mod upstream;
16pub mod wallet;
17
18pub mod lprovider {
19 pub mod ai {
20 pub mod v1 {
21 tonic::include_proto!("lprovider.ai.v1");
22 }
23 }
24 pub mod admin {
25 pub mod v1 {
26 tonic::include_proto!("lprovider.admin.v1");
27 }
28 }
29}
Deleted agent/lprovider/main.rs

Diff too large to display.

Deleted agent/lprovider/quota.rs

Diff too large to display.

Deleted agent/lprovider/rotation.rs

Diff too large to display.

Deleted agent/lprovider/router.rs

Diff too large to display.

Deleted agent/lprovider/selector.rs

Diff too large to display.

Deleted agent/lprovider/state.rs

Diff too large to display.

Deleted agent/lprovider/upstream.rs

Diff too large to display.

Deleted agent/lprovider/wallet.rs

Diff too large to display.

Deleted app/apihost/src/cli.rs

Diff too large to display.

Deleted app/apihost/src/cmd_openapi.rs

Diff too large to display.

Deleted app/apihost/src/cmd_serve.rs

Diff too large to display.

Deleted app/apihost/src/main.rs

Diff too large to display.

Deleted app/apihost/Cargo.toml

Diff too large to display.

Deleted app/githook/src/main.rs

Diff too large to display.

Deleted app/githook/Cargo.toml

Diff too large to display.

Deleted app/gitpro/src/main.rs

Diff too large to display.

Deleted app/gitpro/Cargo.toml

Diff too large to display.

Deleted app/livechat/src/main.rs

Diff too large to display.

Deleted app/livechat/Cargo.toml

Diff too large to display.

Deleted app/mails/src/main.rs

Diff too large to display.

Deleted app/mails/Cargo.toml

Diff too large to display.

Deleted app/migrate/src/main.rs

Diff too large to display.

Deleted app/migrate/Cargo.toml

Diff too large to display.

Deleted chat/chatlayer/actor/live.rs

Diff too large to display.

Deleted chat/chatlayer/actor/mod.rs

Diff too large to display.

Deleted chat/chatlayer/actor/room.rs

Diff too large to display.

Deleted chat/chatlayer/actor/runtime.rs

Diff too large to display.

Deleted chat/chatlayer/actor/user.rs

Diff too large to display.

Deleted chat/chatlayer/consumer/live_worker.rs

Diff too large to display.

Deleted chat/chatlayer/consumer/mod.rs

Diff too large to display.

Deleted chat/chatlayer/consumer/room_worker.rs

Diff too large to display.

Deleted chat/chatlayer/consumer/user_worker.rs

Diff too large to display.

Deleted chat/chatlayer/events/envelope.rs

Diff too large to display.

Deleted chat/chatlayer/events/live.rs

Diff too large to display.

Deleted chat/chatlayer/events/mod.rs

Diff too large to display.

Deleted chat/chatlayer/events/room.rs

Diff too large to display.

Deleted chat/chatlayer/events/user.rs

Diff too large to display.

Deleted chat/chatlayer/events/user_view.rs

Diff too large to display.

Deleted chat/chatlayer/projection/live.rs

Diff too large to display.

Deleted chat/chatlayer/projection/mod.rs

Diff too large to display.

Deleted chat/chatlayer/projection/room.rs

Diff too large to display.

Deleted chat/chatlayer/projection/user.rs

Diff too large to display.

Deleted chat/chatlayer/auth_bridge.rs

Diff too large to display.

Deleted chat/chatlayer/Cargo.toml

Diff too large to display.

Deleted chat/chatlayer/dedup.rs

Diff too large to display.

Deleted chat/chatlayer/error.rs

Diff too large to display.

Deleted chat/chatlayer/lib.rs

Diff too large to display.

Deleted chat/chatlayer/media.rs

Diff too large to display.

Deleted chat/chatlayer/metrics.rs

Diff too large to display.

Deleted chat/chatlayer/producer.rs

Diff too large to display.

Deleted chat/chatlayer/service.rs

Diff too large to display.

Deleted chat/chatlayer/storage_bridge.rs

Diff too large to display.

Deleted chat/chatlayer/topics.rs

Diff too large to display.

Deleted chat/glayer/handler/conversations.rs

Diff too large to display.

Deleted chat/glayer/handler/lookups.rs

Diff too large to display.

Deleted chat/glayer/handler/mentions.rs

Diff too large to display.

Deleted chat/glayer/handler/messages.rs

Diff too large to display.

Deleted chat/glayer/handler/mod.rs

Diff too large to display.

Deleted chat/glayer/handler/pin.rs

Diff too large to display.

Deleted chat/glayer/handler/presence.rs

Diff too large to display.

Deleted chat/glayer/handler/reactions.rs

Diff too large to display.

Deleted chat/glayer/handler/read.rs

Diff too large to display.

Deleted chat/glayer/handler/uploads.rs

Diff too large to display.

Deleted chat/glayer/auth.rs

Diff too large to display.

Deleted chat/glayer/Cargo.toml

Diff too large to display.

Deleted chat/glayer/consumer.rs

Diff too large to display.

Deleted chat/glayer/error.rs

Diff too large to display.

Deleted chat/glayer/hub.rs

Diff too large to display.

Deleted chat/glayer/lib.rs

Diff too large to display.

Deleted chat/glayer/metrics.rs

Diff too large to display.

Deleted chat/glayer/protocol.rs

Diff too large to display.

Deleted chat/glayer/router.rs

Diff too large to display.

Deleted chat/glayer/state.rs

Diff too large to display.

Deleted chat/glayer/typing.rs

Diff too large to display.

Deleted chat/glayer/ws.rs

Diff too large to display.

Deleted chat/live/handler/meet.rs

Diff too large to display.

Deleted chat/live/handler/mod.rs

Diff too large to display.

Deleted chat/live/handler/openapi.rs

Diff too large to display.

Deleted chat/live/handler/routes.rs

Diff too large to display.

Deleted chat/live/Cargo.toml

Diff too large to display.

Deleted chat/live/client.rs

Diff too large to display.

Deleted chat/live/error.rs

Diff too large to display.

Deleted chat/live/lib.rs

Diff too large to display.

Deleted chat/live/metrics.rs

Diff too large to display.

Deleted chat/live/service.rs

Diff too large to display.

Deleted chat/live/token.rs

Diff too large to display.

Deleted chat/live/types.rs

Diff too large to display.

Deleted chat/live/webhook.rs

Diff too large to display.

Deleted chat/storagelayer/blob/media.rs

Diff too large to display.

Deleted chat/storagelayer/blob/object_store.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/attachment.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/conversation.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/meet.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/meet_participant.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/mention.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/message.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/pinned.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/reaction.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/read_receipt.rs

Diff too large to display.

Deleted chat/storagelayer/mapper/thread.rs

Diff too large to display.

Deleted chat/storagelayer/models/attachment.rs

Diff too large to display.

Deleted chat/storagelayer/models/conversation.rs

Diff too large to display.

Deleted chat/storagelayer/models/meet.rs

Diff too large to display.

Deleted chat/storagelayer/models/meet_participant.rs

Diff too large to display.

Deleted chat/storagelayer/models/mention.rs

Diff too large to display.

Deleted chat/storagelayer/models/message.rs

Diff too large to display.

Deleted chat/storagelayer/models/pinned.rs

Diff too large to display.

Deleted chat/storagelayer/models/reaction.rs

Diff too large to display.

Deleted chat/storagelayer/models/read_receipt.rs

Diff too large to display.

Deleted chat/storagelayer/models/thread.rs

Diff too large to display.

Deleted chat/storagelayer/repository/attachment.rs

Diff too large to display.

Deleted chat/storagelayer/repository/conversation.rs

Diff too large to display.

Deleted chat/storagelayer/repository/meet.rs

Diff too large to display.

Deleted chat/storagelayer/repository/meet_participant.rs

Diff too large to display.

Deleted chat/storagelayer/repository/mention.rs

Diff too large to display.

Deleted chat/storagelayer/repository/message.rs

Diff too large to display.

Deleted chat/storagelayer/repository/pinned.rs

Diff too large to display.

Deleted chat/storagelayer/repository/reaction.rs

Diff too large to display.

Deleted chat/storagelayer/repository/read_receipt.rs

Diff too large to display.

Deleted chat/storagelayer/repository/thread.rs

Diff too large to display.

Deleted chat/storagelayer/auth.rs

Diff too large to display.

Deleted chat/storagelayer/blob.rs

Diff too large to display.

Deleted chat/storagelayer/Cargo.toml

Diff too large to display.

Deleted chat/storagelayer/error.rs

Diff too large to display.

Deleted chat/storagelayer/lib.rs

Diff too large to display.

Deleted chat/storagelayer/mapper.rs

Diff too large to display.

Deleted chat/storagelayer/mapping.rs

Diff too large to display.

Deleted chat/storagelayer/models.rs

Diff too large to display.

Deleted chat/storagelayer/repository.rs

Diff too large to display.

Deleted config/default.yaml

Diff too large to display.

Deleted docker/build.sh

Diff too large to display.

Deleted docker/Dockerfile

Diff too large to display.

Deleted dvcs/codesearch/Cargo.toml

Diff too large to display.

Deleted dvcs/codesearch/lib.rs

Diff too large to display.

Deleted dvcs/git/analysis/impls.rs

Diff too large to display.

Deleted dvcs/git/analysis/mod.rs

Diff too large to display.

Deleted dvcs/git/analysis/types.rs

Diff too large to display.

Deleted dvcs/git/analysis/usecase.rs

Diff too large to display.

Deleted dvcs/git/archive/impls.rs

Diff too large to display.

Deleted dvcs/git/archive/mod.rs

Diff too large to display.

Deleted dvcs/git/archive/types.rs

Diff too large to display.

Deleted dvcs/git/archive/usecase.rs

Diff too large to display.

Deleted dvcs/git/blob/impls.rs

Diff too large to display.

Deleted dvcs/git/blob/mod.rs

Diff too large to display.

Deleted dvcs/git/blob/types.rs

Diff too large to display.

Deleted dvcs/git/blob/usecase.rs

Diff too large to display.

Deleted dvcs/git/commit/impls.rs

Diff too large to display.

Deleted dvcs/git/commit/mod.rs

Diff too large to display.

Deleted dvcs/git/commit/types.rs

Diff too large to display.

Deleted dvcs/git/commit/usecase.rs

Diff too large to display.

Deleted dvcs/git/conflict/impls.rs

Diff too large to display.

Deleted dvcs/git/conflict/mod.rs

Diff too large to display.

Deleted dvcs/git/conflict/types.rs

Diff too large to display.

Deleted dvcs/git/conflict/usecase.rs

Diff too large to display.

Deleted dvcs/git/diff/impls.rs

Diff too large to display.

Deleted dvcs/git/diff/mod.rs

Diff too large to display.

Deleted dvcs/git/diff/types.rs

Diff too large to display.

Deleted dvcs/git/diff/usecase.rs

Diff too large to display.

Deleted dvcs/git/gc/impls.rs

Diff too large to display.

Deleted dvcs/git/gc/mod.rs

Diff too large to display.

Deleted dvcs/git/gc/types.rs

Diff too large to display.

Deleted dvcs/git/gc/usecase.rs

Diff too large to display.

Deleted dvcs/git/graphql/error.rs

Diff too large to display.

Deleted dvcs/git/graphql/mod.rs

Diff too large to display.

Deleted dvcs/git/graphql/query.rs

Diff too large to display.

Deleted dvcs/git/graphql/resolver.rs

Diff too large to display.

Deleted dvcs/git/graphql/scalars.rs

Diff too large to display.

Deleted dvcs/git/graphql/types.rs

Diff too large to display.

Deleted dvcs/git/hook/impls.rs

Diff too large to display.

Deleted dvcs/git/hook/mod.rs

Diff too large to display.

Deleted dvcs/git/hook/types.rs

Diff too large to display.

Deleted dvcs/git/hook/usecase.rs

Diff too large to display.

Deleted dvcs/git/lfs/impls.rs

Diff too large to display.

Deleted dvcs/git/lfs/mod.rs

Diff too large to display.

Deleted dvcs/git/lfs/types.rs

Diff too large to display.

Deleted dvcs/git/lfs/usecase.rs

Diff too large to display.

Deleted dvcs/git/log/impls.rs

Diff too large to display.

Deleted dvcs/git/log/mod.rs

Diff too large to display.

Deleted dvcs/git/log/types.rs

Diff too large to display.

Deleted dvcs/git/log/usecase.rs

Diff too large to display.

Deleted dvcs/git/object/cache.rs

Diff too large to display.

Deleted dvcs/git/object/impls.rs

Diff too large to display.

Deleted dvcs/git/object/mod.rs

Diff too large to display.

Deleted dvcs/git/object/types.rs

Diff too large to display.

Deleted dvcs/git/object/usecase.rs

Diff too large to display.

Deleted dvcs/git/pack/impls.rs

Diff too large to display.

Deleted dvcs/git/pack/mod.rs

Diff too large to display.

Deleted dvcs/git/pack/types.rs

Diff too large to display.

Deleted dvcs/git/pack/usecase.rs

Diff too large to display.

Deleted dvcs/git/patch/impls.rs

Diff too large to display.

Deleted dvcs/git/patch/mod.rs

Diff too large to display.

Deleted dvcs/git/patch/types.rs

Diff too large to display.

Deleted dvcs/git/patch/usecase.rs

Diff too large to display.

Deleted dvcs/git/pipeline/impls.rs

Diff too large to display.

Deleted dvcs/git/pipeline/mod.rs

Diff too large to display.

Deleted dvcs/git/pipeline/types.rs

Diff too large to display.

Deleted dvcs/git/pipeline/usecase.rs

Diff too large to display.

Deleted dvcs/git/refs/impls.rs

Diff too large to display.

Deleted dvcs/git/refs/mod.rs

Diff too large to display.

Deleted dvcs/git/refs/types.rs

Diff too large to display.

Deleted dvcs/git/refs/usecase.rs

Diff too large to display.

Deleted dvcs/git/remote/impls.rs

Diff too large to display.

Deleted dvcs/git/remote/mod.rs

Diff too large to display.

Deleted dvcs/git/remote/types.rs

Diff too large to display.

Deleted dvcs/git/remote/usecase.rs

Diff too large to display.

Deleted dvcs/git/signature/impls.rs

Diff too large to display.

Deleted dvcs/git/signature/mod.rs

Diff too large to display.

Deleted dvcs/git/signature/types.rs

Diff too large to display.

Deleted dvcs/git/signature/usecase.rs

Diff too large to display.

Deleted dvcs/git/submodule/impls.rs

Diff too large to display.

Deleted dvcs/git/submodule/mod.rs

Diff too large to display.

Deleted dvcs/git/submodule/types.rs

Diff too large to display.

Deleted dvcs/git/submodule/usecase.rs

Diff too large to display.

Deleted dvcs/git/tag/impls.rs

Diff too large to display.

Deleted dvcs/git/tag/mod.rs

Diff too large to display.

Deleted dvcs/git/tag/types.rs

Diff too large to display.

Deleted dvcs/git/tag/usecase.rs

Diff too large to display.

Deleted dvcs/git/tree/impls.rs

Diff too large to display.

Deleted dvcs/git/tree/mod.rs

Diff too large to display.

Deleted dvcs/git/tree/types.rs

Diff too large to display.

Deleted dvcs/git/tree/usecase.rs

Diff too large to display.

Deleted dvcs/git/Cargo.toml

Diff too large to display.

Deleted dvcs/git/error.rs

Diff too large to display.

Deleted dvcs/git/gitcli.rs

Diff too large to display.

Deleted dvcs/git/lib.rs

Diff too large to display.

Deleted dvcs/git/policy.rs

Diff too large to display.

Deleted dvcs/git/usecase_cache.rs

Diff too large to display.

Deleted dvcs/git/validate.rs

Diff too large to display.

Deleted dvcs/gitauth/basic.rs

Diff too large to display.

Deleted dvcs/gitauth/Cargo.toml

Diff too large to display.

Deleted dvcs/gitauth/error.rs

Diff too large to display.

Deleted dvcs/gitauth/lib.rs

Diff too large to display.

Deleted dvcs/gitauth/metrics.rs

Diff too large to display.

Deleted dvcs/gitauth/permission.rs

Diff too large to display.

Deleted dvcs/gitauth/resolve.rs

Diff too large to display.

Deleted dvcs/gitauth/service.rs

Diff too large to display.

Deleted dvcs/gitlfs/Cargo.toml

Diff too large to display.

Deleted dvcs/gitlfs/error.rs

Diff too large to display.

Deleted dvcs/gitlfs/lib.rs

Diff too large to display.

Deleted dvcs/gitlfs/metrics.rs

Diff too large to display.

Deleted dvcs/gitlfs/oid.rs

Diff too large to display.

Deleted dvcs/gitlfs/store.rs

Diff too large to display.

Deleted dvcs/gitsmart/handler/dumb.rs

Diff too large to display.

Deleted dvcs/gitsmart/handler/lfs.rs

Diff too large to display.

Deleted dvcs/gitsmart/handler/mod.rs

Diff too large to display.

Deleted dvcs/gitsmart/Cargo.toml

Diff too large to display.

Deleted dvcs/gitsmart/error.rs

Diff too large to display.

Modified lib/cache/Cargo.toml View lib/cache/Cargo.toml
@@ -2,23 +2,10 @@
22name = "cache"
33version.workspace = true
44edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
105
11[lib]
12path = "lib.rs"
13name = "cache"
146[dependencies]
15bincache = { workspace = true }
16config = { workspace = true }
17redis = { workspace = true }
7config = { path = "../config" }
8oxcache = { workspace = true }
9tokio = { workspace = true }
1810serde = { workspace = true }
1911serde_json = { workspace = true }
20metrics = { workspace = true }
21tokio = { workspace = true }
22lombok = { workspace = true }
23tracing = { workspace = true }
24thiserror = { workspace = true }
Modified lib/config/src/cache.rs View lib/config/src/cache.rs
@@ -1,65 +1,68 @@
1use lombok::{Builder, Getter, Setter};
1use serde::{Deserialize, Serialize};
22
3#[derive(
4 serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Getter, Setter, Builder,
5)]
3use crate::redis::RedisConfig;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(default)]
7pub struct AppCacheSettings {
8 pub enabled: bool,
9 pub ttl_seconds: u64,
10 pub max_capacity: usize,
11 pub max_size: usize,
12 pub enable_disk: bool,
13 pub disk_path: Option<String>,
14 pub disk_cache_max_size: usize,
15 pub disk_cache_max_capacity: usize,
7pub struct CacheConfig {
8 pub l1_capacity: u64,
9 pub l1_ttl_secs: Option<u64>,
10 pub l1_tti_secs: Option<u64>,
11 pub default_ttl_secs: Option<u64>,
12 pub backfill_enabled: bool,
13 pub redis: RedisConfig,
1614}
1715
18impl Default for AppCacheSettings {
16impl Default for CacheConfig {
1917 fn default() -> Self {
2018 Self {
21 enabled: true,
22 ttl_seconds: 300,
23 max_capacity: 10_000,
24 max_size: 50_000_000,
25 enable_disk: false,
26 disk_path: None,
27 disk_cache_max_size: 200_000_000,
28 disk_cache_max_capacity: 100_000,
19 l1_capacity: 10_000,
20 l1_ttl_secs: None,
21 l1_tti_secs: None,
22 default_ttl_secs: None,
23 backfill_enabled: true,
24 redis: RedisConfig::default(),
2925 }
3026 }
3127}
3228
33#[derive(
34 Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Getter, Setter, Builder,
35)]
36#[serde(default)]
37pub struct AppRedisSettings {
38 pub urls: Vec<String>,
39 pub mode: AppRedisMode,
40 pub connection_timeout_ms: Option<u64>,
41 pub response_timeout_ms: Option<u64>,
42 pub password: Option<String>,
43 pub database: Option<i64>,
44}
29impl CacheConfig {
30 pub fn new(redis: RedisConfig) -> Self {
31 Self {
32 redis,
33 ..Self::default()
34 }
35 }
4536
46#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Default)]
47pub enum AppRedisMode {
48 #[default]
49 Single,
50 Cluster,
51 Sentinel,
52}
37 pub fn with_l1_capacity(mut self, capacity: u64) -> Self {
38 self.l1_capacity = capacity;
39 self
40 }
5341
54impl Default for AppRedisSettings {
55 fn default() -> Self {
56 Self {
57 urls: vec!["redis://127.0.0.1:6379".to_string()],
58 mode: AppRedisMode::default(),
59 connection_timeout_ms: Some(2_000),
60 response_timeout_ms: Some(5_000),
61 password: None,
62 database: Some(0),
42 pub fn with_l1_ttl_secs(mut self, secs: u64) -> Self {
43 self.l1_ttl_secs = Some(secs);
44 self
45 }
46
47 pub fn with_l1_tti_secs(mut self, secs: u64) -> Self {
48 self.l1_tti_secs = Some(secs);
49 self
50 }
51
52 pub fn with_default_ttl_secs(mut self, secs: u64) -> Self {
53 self.default_ttl_secs = Some(secs);
54 self
55 }
56
57 pub fn with_backfill(mut self, enabled: bool) -> Self {
58 self.backfill_enabled = enabled;
59 self
60 }
61
62 pub fn validate(&self) -> Result<(), String> {
63 if self.l1_capacity == 0 {
64 return Err("cache.l1_capacity must be greater than 0".to_string());
6365 }
66 self.redis.validate().map(|_| ())
6467 }
6568}
Modified lib/config/src/lib.rs View lib/config/src/lib.rs
@@ -1,31 +1,5 @@
1pub mod app;
2pub mod auth;
1pub mod s3;
2pub mod redis;
33pub mod cache;
4pub mod chat;
5pub mod db;
6pub mod duration;
7pub mod email;
8pub mod error;
9pub mod git;
10pub mod issues;
11pub mod jwt;
12pub mod live;
13pub mod loader;
14pub mod lprovider;
15pub mod mcp;
16pub mod obs;
17pub mod payment;
18pub mod pow;
19pub mod project;
20pub mod queue;
21pub mod rate_limit;
22pub mod repo;
23pub mod search;
24pub mod space;
25pub mod storage;
26pub mod user;
27pub mod webhook;
28
29pub use app::AppConfig;
30pub use error::{ConfigError, ConfigResult};
31pub use loader::{ConfigLoader, load};
4pub mod database;
5pub mod session;
No newline at end of file
Modified lib/config/Cargo.toml View lib/config/Cargo.toml
@@ -2,23 +2,7 @@
22name = "config"
33version.workspace = true
44edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
105
11[lib]
12path = "src/lib.rs"
13name = "config"
14
156[dependencies]
16serde = { workspace = true }
17serde_json = { workspace = true }
18time = { workspace = true }
19lombok = { workspace = true }
20utoipa = { workspace = true }
21thiserror = { workspace = true }
22config-rs = { workspace = true }
23dotenvy = "0.15"
24ipnet = "2.12"
No newline at end of file
7serde = { workspace = true, features = ["derive"] }
8sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] }
Modified lib/export/Cargo.toml View lib/export/Cargo.toml
@@ -2,21 +2,10 @@
22name = "export"
33version.workspace = true
44edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
105
11[lib]
12path = "lib.rs"
13name = "export"
146[dependencies]
15axum.workspace = true
16db.workspace = true
17serde.workspace = true
18serde_json.workspace = true
19time.workspace = true
20utoipa.workspace = true
21uuid.workspace = true
22validator = { workspace = true, features = ["derive"] }
7config = { path = "../config" }
8storage = { path = "../storage" }
9sea-orm = { workspace = true }
10redis = { workspace = true }
11tokio = { workspace = true }
No newline at end of file
Modified lib/storage/Cargo.toml View lib/storage/Cargo.toml
@@ -2,27 +2,9 @@
22name = "storage"
33version.workspace = true
44edition.workspace = true
5authors.workspace = true
6categories.workspace = true
7keywords.workspace = true
8repository.workspace = true
9homepage.workspace = true
10
11[lib]
12path = "lib.rs"
13name = "storage"
145
156[dependencies]
16async-trait = { workspace = true }
17bytes = { workspace = true }
18config = { workspace = true }
19futures = { workspace = true }
20http = { workspace = true }
21lombok = { workspace = true }
22metrics = { workspace = true }
23object_store = { workspace = true }
24serde = { workspace = true }
25thiserror = { workspace = true }
7config = { path = "../config" }
8rust-s3-async = { workspace = true }
269tokio = { workspace = true }
27tracing = { workspace = true }
28url = { workspace = true }
10http = "0.2"
Modified .gitignore View .gitignore
@@ -1,28 +1,3 @@
1/target
2*.iml
1**.iml
32.idea
4.codex
5.codegraph
6.claude
7.qoder
8
9# secrets
10.env
11.env.*
12*.pem
13*.key
14*.p12
15*.pfx
16*.jks
17*.keystore
18.ssh/
19id_rsa
20id_ed25519
21.git-credentials
22.netrc
23.pgpass
24credentials
25*.secrets.yaml
26config/default.yaml
27config/ssh_host_ed25519_key
28config/ssh_host_ed25519_key.pub
No newline at end of file
3target
No newline at end of file
Deleted .uncommentrc.toml
@@ -1,33 +0,0 @@
1[global]
2remove_todos = false
3remove_fixme = false
4remove_docs = false
5preserve_patterns = ["HACK", "WORKAROUND", "NOTE"]
6use_default_ignores = true
7respect_gitignore = true
8traverse_git_repos = false
9
10[languages.xml]
11name = "XML"
12extensions = [".xml", ".xsd", ".xsl", ".xslt", ".svg"]
13comment_nodes = ["Comment"]
14
15[languages.rust]
16name = "Rust"
17extensions = [".rs"]
18comment_nodes = ["line_comment", "block_comment"]
19doc_comment_nodes = ["doc_comment"]
20preserve_patterns = ["clippy:", "allow", "deny", "warn"]
21remove_docs = false
22
23[patterns."tests/**/*"]
24remove_todos = true
25
26[patterns."**/*.spec.*"]
27remove_docs = true
28remove_todos = true
29
30[patterns."**/*.generated.*"]
31remove_docs = true
32remove_todos = true
33preserve_patterns = []
Modified Cargo.lock View Cargo.lock
@@ -3,16 +3,14 @@
33version = 4
44
55[[package]]
6name = "Inflector"
7version = "0.11.4"
6name = "aead"
7version = "0.5.2"
88source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10
11[[package]]
12name = "adler2"
13version = "2.0.1"
14source = "registry+https://github.com/rust-lang/crates.io-index"
15checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
9checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
10dependencies = [
11 "crypto-common 0.1.6",
12 "generic-array",
13]
1614
1715[[package]]
1816name = "aead"
@@ -21,7 +19,18 @@
2119checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
2220dependencies = [
2321 "crypto-common 0.2.2",
24 "inout",
22 "inout 0.2.2",
23]
24
25[[package]]
26name = "aes"
27version = "0.8.4"
28source = "registry+https://github.com/rust-lang/crates.io-index"
29checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
30dependencies = [
31 "cfg-if",
32 "cipher 0.4.4",
33 "cpufeatures 0.2.17",
2534]
2635
2736[[package]]
@@ -30,25 +39,37 @@
3039source = "registry+https://github.com/rust-lang/crates.io-index"
3140checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
3241dependencies = [
33 "cipher",
42 "cipher 0.5.2",
3443 "cpubits",
3544 "cpufeatures 0.3.0",
36 "zeroize",
3745]
3846
3947[[package]]
4048name = "aes-gcm"
49version = "0.10.3"
50source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
52dependencies = [
53 "aead 0.5.2",
54 "aes 0.8.4",
55 "cipher 0.4.4",
56 "ctr 0.9.2",
57 "ghash 0.5.1",
58 "subtle",
59]
60
61[[package]]
62name = "aes-gcm"
4163version = "0.11.0"
4264source = "registry+https://github.com/rust-lang/crates.io-index"
4365checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028"
4466dependencies = [
45 "aead",
46 "aes",
47 "cipher",
48 "ctr",
49 "ghash",
67 "aead 0.6.1",
68 "aes 0.9.2",
69 "cipher 0.5.2",
70 "ctr 0.10.1",
71 "ghash 0.6.0",
5072 "subtle",
51 "zeroize",
5273]
5374
5475[[package]]
@@ -59,7 +80,7 @@
5980dependencies = [
6081 "getrandom 0.2.17",
6182 "once_cell",
62 "version_check",
83 "version_check 0.9.5",
6384]
6485
6586[[package]]
@@ -72,7 +93,7 @@
7293 "const-random",
7394 "getrandom 0.3.4",
7495 "once_cell",
75 "version_check",
96 "version_check 0.9.5",
7697 "zerocopy",
7798]
7899
@@ -96,103 +117,29 @@
96117version = "0.2.21"
97118source = "registry+https://github.com/rust-lang/crates.io-index"
98119checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
99
100[[package]]
101name = "altcha"
102version = "0.2.0"
103source = "registry+https://github.com/rust-lang/crates.io-index"
104checksum = "d81f4a0f21c4145fdee4fb09dbb665da446c7af0ca0ec685e1651b0f96a84fab"
105dependencies = [
106 "argon2 0.5.3",
107 "digest 0.10.7",
108 "hex",
109 "hmac 0.12.1",
110 "pbkdf2 0.12.2",
111 "rand 0.8.7",
112 "serde",
113 "serde_json",
114 "sha2 0.10.9",
115 "subtle",
116 "thiserror 2.0.19",
117]
118120
119121[[package]]
120122name = "android_system_properties"
121version = "0.1.5"
123version = "0.1.6"
122124source = "registry+https://github.com/rust-lang/crates.io-index"
123checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
125checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
124126dependencies = [
125127 "libc",
126128]
127129
128130[[package]]
129name = "anstream"
131name = "antidote"
130132version = "1.0.0"
131133source = "registry+https://github.com/rust-lang/crates.io-index"
132checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
133dependencies = [
134 "anstyle",
135 "anstyle-parse",
136 "anstyle-query",
137 "anstyle-wincon",
138 "colorchoice",
139 "is_terminal_polyfill",
140 "utf8parse",
141]
134checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
142135
143136[[package]]
144name = "anstyle"
145version = "1.0.14"
137name = "ar_archive_writer"
138version = "0.5.3"
146139source = "registry+https://github.com/rust-lang/crates.io-index"
147checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
148
149[[package]]
150name = "anstyle-parse"
151version = "1.0.0"
152source = "registry+https://github.com/rust-lang/crates.io-index"
153checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
140checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6"
154141dependencies = [
155 "utf8parse",
156]
157
158[[package]]
159name = "anstyle-query"
160version = "1.1.5"
161source = "registry+https://github.com/rust-lang/crates.io-index"
162checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
163dependencies = [
164 "windows-sys 0.61.2",
165]
166
167[[package]]
168name = "anstyle-wincon"
169version = "3.0.11"
170source = "registry+https://github.com/rust-lang/crates.io-index"
171checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
172dependencies = [
173 "anstyle",
174 "once_cell_polyfill",
175 "windows-sys 0.61.2",
176]
177
178[[package]]
179name = "anyhow"
180version = "1.0.104"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
183
184[[package]]
185name = "apihost"
186version = "1.94.0"
187dependencies = [
188 "axum",
189 "clap",
190 "config 1.94.0",
191 "obs",
192 "thiserror 2.0.19",
193 "tokio",
194 "tracing",
195 "webapi",
142 "object",
196143]
197144
198145[[package]]
@@ -209,36 +156,6 @@
209156version = "1.2.0"
210157source = "registry+https://github.com/rust-lang/crates.io-index"
211158checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d"
212
213[[package]]
214name = "argon2"
215version = "0.5.3"
216source = "registry+https://github.com/rust-lang/crates.io-index"
217checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
218dependencies = [
219 "base64ct",
220 "blake2 0.10.6",
221 "cpufeatures 0.2.17",
222 "password-hash 0.5.0",
223]
224
225[[package]]
226name = "argon2"
227version = "0.6.0-rc.8"
228source = "registry+https://github.com/rust-lang/crates.io-index"
229checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5"
230dependencies = [
231 "base64ct",
232 "blake2 0.11.0-rc.6",
233 "cpufeatures 0.3.0",
234 "password-hash 0.6.1",
235]
236
237[[package]]
238name = "arraydeque"
239version = "0.5.1"
240source = "registry+https://github.com/rust-lang/crates.io-index"
241checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
242159
243160[[package]]
244161name = "arrayvec"
@@ -304,7 +221,7 @@
304221dependencies = [
305222 "bytes",
306223 "half",
307 "num-bigint",
224 "num-bigint 0.4.8",
308225 "num-traits",
309226]
310227
@@ -406,178 +323,47 @@
406323]
407324
408325[[package]]
409name = "ascii_utils"
410version = "0.9.3"
326name = "async-lock"
327version = "3.4.2"
411328source = "registry+https://github.com/rust-lang/crates.io-index"
412checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
413
414[[package]]
415name = "assert-json-diff"
416version = "2.0.2"
417source = "registry+https://github.com/rust-lang/crates.io-index"
418checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
329checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
419330dependencies = [
420 "serde",
421 "serde_json",
422]
423
424[[package]]
425name = "async-compression"
426version = "0.4.43"
427source = "registry+https://github.com/rust-lang/crates.io-index"
428checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8"
429dependencies = [
430 "compression-codecs",
431 "compression-core",
432 "futures-io",
331 "event-listener",
332 "event-listener-strategy",
433333 "pin-project-lite",
434 "tokio",
435334]
436335
437336[[package]]
438name = "async-graphql"
439version = "7.2.1"
337name = "async-stream"
338version = "0.3.6"
440339source = "registry+https://github.com/rust-lang/crates.io-index"
441checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40"
340checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
442341dependencies = [
443 "async-graphql-derive",
444 "async-graphql-parser",
445 "async-graphql-value",
446 "async-io",
447 "async-trait",
448 "asynk-strim",
449 "base64 0.22.1",
450 "bytes",
451 "fast_chemail",
452 "fnv",
453 "futures-util",
454 "handlebars",
455 "http",
456 "indexmap",
457 "mime",
458 "multer",
459 "num-traits",
342 "async-stream-impl",
343 "futures-core",
460344 "pin-project-lite",
461 "regex",
462 "serde",
463 "serde_json",
464 "serde_urlencoded",
465 "static_assertions_next",
466 "tempfile",
467 "thiserror 2.0.19",
468345]
469346
470347[[package]]
471name = "async-graphql-derive"
472version = "7.2.1"
348name = "async-stream-impl"
349version = "0.3.6"
473350source = "registry+https://github.com/rust-lang/crates.io-index"
474checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68"
351checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
475352dependencies = [
476 "Inflector",
477 "async-graphql-parser",
478 "darling 0.23.0",
479 "proc-macro-crate",
480353 "proc-macro2",
481354 "quote",
482 "strum 0.27.2",
483355 "syn 2.0.119",
484 "thiserror 2.0.19",
485356]
486357
487358[[package]]
488name = "async-graphql-parser"
489version = "7.2.1"
490source = "registry+https://github.com/rust-lang/crates.io-index"
491checksum = "e64ef70f77a1c689111e52076da1cd18f91834bcb847de0a9171f83624b07fbf"
492dependencies = [
493 "async-graphql-value",
494 "pest",
495 "serde",
496 "serde_json",
497]
498
499[[package]]
500name = "async-graphql-value"
501version = "7.2.1"
502source = "registry+https://github.com/rust-lang/crates.io-index"
503checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41"
504dependencies = [
505 "bytes",
506 "indexmap",
507 "serde",
508 "serde_json",
509]
510
511[[package]]
512name = "async-io"
513version = "2.6.0"
514source = "registry+https://github.com/rust-lang/crates.io-index"
515checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
516dependencies = [
517 "autocfg",
518 "cfg-if",
519 "concurrent-queue",
520 "futures-io",
521 "futures-lite",
522 "parking",
523 "polling",
524 "rustix",
525 "slab",
526 "windows-sys 0.61.2",
527]
528
529[[package]]
530name = "async-lock"
531version = "3.4.2"
532source = "registry+https://github.com/rust-lang/crates.io-index"
533checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
534dependencies = [
535 "event-listener",
536 "event-listener-strategy",
537 "pin-project-lite",
538]
539
540[[package]]
541name = "async-stream"
542version = "0.3.6"
543source = "registry+https://github.com/rust-lang/crates.io-index"
544checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
545dependencies = [
546 "async-stream-impl",
547 "futures-core",
548 "pin-project-lite",
549]
550
551[[package]]
552name = "async-stream-impl"
553version = "0.3.6"
554source = "registry+https://github.com/rust-lang/crates.io-index"
555checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
556dependencies = [
557 "proc-macro2",
558 "quote",
559 "syn 2.0.119",
560]
561
562[[package]]
563359name = "async-trait"
564version = "0.1.91"
360version = "0.1.92"
565361source = "registry+https://github.com/rust-lang/crates.io-index"
566checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
362checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
567363dependencies = [
568364 "proc-macro2",
569365 "quote",
570366 "syn 3.0.3",
571]
572
573[[package]]
574name = "asynk-strim"
575version = "0.1.5"
576source = "registry+https://github.com/rust-lang/crates.io-index"
577checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6"
578dependencies = [
579 "futures-core",
580 "pin-project-lite",
581367]
582368
583369[[package]]
@@ -596,41 +382,17 @@
596382checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
597383
598384[[package]]
599name = "auth"
600version = "1.94.0"
385name = "attohttpc"
386version = "0.24.1"
387source = "registry+https://github.com/rust-lang/crates.io-index"
388checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2"
601389dependencies = [
602 "altcha",
603 "argon2 0.5.3",
604 "axum",
605 "axum-extra",
606 "base64 0.23.1",
607 "cache",
608 "config 1.94.0",
609 "db",
610 "email",
611 "export",
612 "http",
613 "jsonwebtoken 11.0.0",
614 "jwt",
615 "log",
616 "metrics",
617 "password-hash 0.5.0",
618 "queue",
619 "rand 0.10.2",
620 "rate_limit",
621 "reqwest 0.13.4",
390 "http 0.2.12",
391 "log 0.4.33",
392 "native-tls",
622393 "serde",
623394 "serde_json",
624 "sha2 0.10.9",
625 "thiserror 2.0.19",
626 "time",
627 "tokio",
628 "totp",
629 "tracing",
630 "url",
631 "utoipa",
632 "uuid",
633 "validator",
395 "url 2.5.8",
634396]
635397
636398[[package]]
@@ -640,27 +402,29 @@
640402checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
641403
642404[[package]]
643name = "aws-lc-rs"
644version = "1.17.3"
405name = "aws-creds"
406version = "0.35.0"
645407source = "registry+https://github.com/rust-lang/crates.io-index"
646checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
408checksum = "01eb8a458c003bd8fe37badba2d8a5a3839748b6ade5cc2d92c2c6b9c6d5a437"
647409dependencies = [
648 "aws-lc-sys",
649 "untrusted 0.7.1",
650 "zeroize",
410 "attohttpc",
411 "dirs",
412 "log 0.4.33",
413 "quick-xml",
414 "rust-ini",
415 "serde",
416 "thiserror 1.0.69",
417 "time 0.3.55",
418 "url 2.5.8",
651419]
652420
653421[[package]]
654name = "aws-lc-sys"
655version = "0.43.0"
422name = "aws-region"
423version = "0.25.5"
656424source = "registry+https://github.com/rust-lang/crates.io-index"
657checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
425checksum = "e9aed3f9c7eac9be28662fdb3b0f4d1951e812f7c64fed4f0327ba702f459b3b"
658426dependencies = [
659 "cc",
660 "cmake",
661 "dunce",
662 "fs_extra",
663 "pkg-config",
427 "thiserror 1.0.69",
664428]
665429
666430[[package]]
@@ -670,29 +434,26 @@
670434checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
671435dependencies = [
672436 "axum-core",
673 "base64 0.22.1",
674437 "bytes",
675438 "form_urlencoded",
676439 "futures-util",
677 "http",
678 "http-body",
440 "http 1.5.0",
441 "http-body 1.1.0",
679442 "http-body-util",
680 "hyper",
443 "hyper 1.11.0",
681444 "hyper-util",
682445 "itoa",
683446 "matchit",
684447 "memchr",
685 "mime",
686 "percent-encoding",
448 "mime 0.3.17",
449 "percent-encoding 2.3.2",
687450 "pin-project-lite",
688451 "serde_core",
689452 "serde_json",
690453 "serde_path_to_error",
691454 "serde_urlencoded",
692 "sha1 0.10.7",
693455 "sync_wrapper",
694456 "tokio",
695 "tokio-tungstenite",
696457 "tower",
697458 "tower-layer",
698459 "tower-service",
@@ -707,10 +468,10 @@
707468dependencies = [
708469 "bytes",
709470 "futures-core",
710 "http",
711 "http-body",
471 "http 1.5.0",
472 "http-body 1.1.0",
712473 "http-body-util",
713 "mime",
474 "mime 0.3.17",
714475 "pin-project-lite",
715476 "sync_wrapper",
716477 "tower-layer",
@@ -719,38 +480,60 @@
719480]
720481
721482[[package]]
722name = "axum-extra"
723version = "0.12.6"
483name = "axum_session"
484version = "0.21.0"
724485source = "registry+https://github.com/rust-lang/crates.io-index"
725checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970"
486checksum = "337130e61b1166b57510a481c76f585f08d9b78449c754c4d1d4155f56823ecc"
726487dependencies = [
488 "aes-gcm 0.11.0",
489 "async-trait",
727490 "axum",
728 "axum-core",
491 "base64 0.23.1",
729492 "bytes",
493 "chrono",
730494 "cookie",
731 "futures-core",
732 "futures-util",
733 "http",
734 "http-body",
735 "http-body-util",
736 "mime",
737 "pin-project-lite",
495 "dashmap",
496 "forwarded-header-value",
497 "futures",
498 "hmac 0.13.0",
499 "http 1.5.0",
500 "http-body 1.1.0",
501 "rand 0.10.2",
502 "serde",
503 "serde_json",
504 "sha2 0.11.0",
505 "thiserror 2.0.20",
506 "tokio",
738507 "tower-layer",
739508 "tower-service",
740509 "tracing",
510 "uuid",
741511]
742512
743513[[package]]
744name = "base16ct"
745version = "0.2.0"
514name = "backon"
515version = "1.6.0"
516source = "registry+https://github.com/rust-lang/crates.io-index"
517checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
518dependencies = [
519 "fastrand",
520]
521
522[[package]]
523name = "base64"
524version = "0.9.3"
746525source = "registry+https://github.com/rust-lang/crates.io-index"
747checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
526checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
527dependencies = [
528 "byteorder",
529 "safemem",
530]
748531
749532[[package]]
750name = "base16ct"
751version = "1.0.0"
533name = "base64"
534version = "0.21.7"
752535source = "registry+https://github.com/rust-lang/crates.io-index"
753checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
536checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
754537
755538[[package]]
756539name = "base64"
@@ -763,23 +546,6 @@
763546version = "0.23.1"
764547source = "registry+https://github.com/rust-lang/crates.io-index"
765548checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
766
767[[package]]
768name = "base64ct"
769version = "1.8.3"
770source = "registry+https://github.com/rust-lang/crates.io-index"
771checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
772
773[[package]]
774name = "bcrypt-pbkdf"
775version = "0.11.0"
776source = "registry+https://github.com/rust-lang/crates.io-index"
777checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0"
778dependencies = [
779 "blowfish",
780 "pbkdf2 0.13.0",
781 "sha2 0.11.0",
782]
783549
784550[[package]]
785551name = "bigdecimal"
@@ -789,51 +555,16 @@
789555dependencies = [
790556 "autocfg",
791557 "libm",
792 "num-bigint",
558 "num-bigint 0.4.8",
793559 "num-integer",
794560 "num-traits",
795561 "serde",
796562]
797563
798564[[package]]
799name = "bincache"
800version = "0.5.1"
565name = "bitflags"
566version = "2.13.1"
801567source = "registry+https://github.com/rust-lang/crates.io-index"
802checksum = "ba78ebd89c3ffe1b3ca975f6fb41abe0f8cda79f76d08dcadb2f31dba38c8fc1"
803dependencies = [
804 "async-compression",
805 "async-trait",
806 "futures-util",
807 "paste",
808 "thiserror 1.0.69",
809 "tokio",
810]
811
812[[package]]
813name = "bisync"
814version = "0.3.0"
815source = "registry+https://github.com/rust-lang/crates.io-index"
816checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7"
817dependencies = [
818 "bisync_macros",
819]
820
821[[package]]
822name = "bisync_macros"
823version = "0.2.3"
824source = "registry+https://github.com/rust-lang/crates.io-index"
825checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6"
826
827[[package]]
828name = "bitflags"
829version = "1.3.2"
830source = "registry+https://github.com/rust-lang/crates.io-index"
831checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
832
833[[package]]
834name = "bitflags"
835version = "2.13.1"
836source = "registry+https://github.com/rust-lang/crates.io-index"
837568checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
838569dependencies = [
839570 "serde_core",
@@ -849,24 +580,6 @@
849580 "radium",
850581 "tap",
851582 "wyz",
852]
853
854[[package]]
855name = "blake2"
856version = "0.10.6"
857source = "registry+https://github.com/rust-lang/crates.io-index"
858checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
859dependencies = [
860 "digest 0.10.7",
861]
862
863[[package]]
864name = "blake2"
865version = "0.11.0-rc.6"
866source = "registry+https://github.com/rust-lang/crates.io-index"
867checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690"
868dependencies = [
869 "digest 0.11.3",
870583]
871584
872585[[package]]
@@ -875,7 +588,7 @@
875588source = "registry+https://github.com/rust-lang/crates.io-index"
876589checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
877590dependencies = [
878 "generic-array 0.14.9",
591 "generic-array",
879592]
880593
881594[[package]]
@@ -885,26 +598,6 @@
885598checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
886599dependencies = [
887600 "hybrid-array",
888 "zeroize",
889]
890
891[[package]]
892name = "block-padding"
893version = "0.4.2"
894source = "registry+https://github.com/rust-lang/crates.io-index"
895checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
896dependencies = [
897 "hybrid-array",
898]
899
900[[package]]
901name = "blowfish"
902version = "0.10.0"
903source = "registry+https://github.com/rust-lang/crates.io-index"
904checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298"
905dependencies = [
906 "byteorder",
907 "cipher",
908601]
909602
910603[[package]]
@@ -932,17 +625,6 @@
932625]
933626
934627[[package]]
935name = "bstr"
936version = "1.13.0"
937source = "registry+https://github.com/rust-lang/crates.io-index"
938checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
939dependencies = [
940 "memchr",
941 "regex-automata",
942 "serde_core",
943]
944
945[[package]]
946628name = "bumpalo"
947629version = "3.20.3"
948630source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -981,50 +663,35 @@
981663version = "1.12.1"
982664source = "registry+https://github.com/rust-lang/crates.io-index"
983665checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
984dependencies = [
985 "serde",
986]
987
988[[package]]
989name = "bytesize"
990version = "2.7.0"
991source = "registry+https://github.com/rust-lang/crates.io-index"
992checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b"
993666
994667[[package]]
995668name = "cache"
996version = "1.94.0"
669version = "1.0.0"
997670dependencies = [
998 "bincache",
999 "config 1.94.0",
1000 "lombok",
1001 "metrics",
1002 "redis",
671 "config",
672 "oxcache",
1003673 "serde",
1004674 "serde_json",
1005 "thiserror 2.0.19",
1006675 "tokio",
1007 "tracing",
1008676]
1009677
1010678[[package]]
1011name = "cbc"
1012version = "0.2.1"
679name = "calendrical_calculations"
680version = "0.2.4"
1013681source = "registry+https://github.com/rust-lang/crates.io-index"
1014checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
682checksum = "5abbd6eeda6885048d357edc66748eea6e0268e3dd11f326fff5bd248d779c26"
1015683dependencies = [
1016 "cipher",
684 "core_maths",
685 "displaydoc",
1017686]
1018687
1019688[[package]]
1020689name = "cc"
1021version = "1.4.0"
690version = "1.4.3"
1022691source = "registry+https://github.com/rust-lang/crates.io-index"
1023checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
692checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
1024693dependencies = [
1025694 "find-msvc-tools",
1026 "jobserver",
1027 "libc",
1028695 "shlex",
1029696]
1030697
@@ -1047,37 +714,8 @@
1047714checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
1048715dependencies = [
1049716 "cfg-if",
1050 "cipher",
1051717 "cpufeatures 0.3.0",
1052718 "rand_core 0.10.1",
1053 "zeroize",
1054]
1055
1056[[package]]
1057name = "chatlayer"
1058version = "1.94.0"
1059dependencies = [
1060 "cache",
1061 "config 1.94.0",
1062 "db",
1063 "futures",
1064 "jwt",
1065 "metrics",
1066 "object_store",
1067 "queue",
1068 "rate_limit",
1069 "sea-orm",
1070 "serde",
1071 "serde_json",
1072 "storage",
1073 "storagelayer",
1074 "thiserror 2.0.19",
1075 "time",
1076 "tokio",
1077 "tokio-util",
1078 "tracing",
1079 "url",
1080 "uuid",
1081719]
1082720
1083721[[package]]
@@ -1096,72 +734,23 @@
1096734
1097735[[package]]
1098736name = "cipher"
1099version = "0.5.2"
737version = "0.4.4"
1100738source = "registry+https://github.com/rust-lang/crates.io-index"
1101checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
739checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
1102740dependencies = [
1103 "block-buffer 0.12.1",
1104 "crypto-common 0.2.2",
1105 "inout",
1106 "zeroize",
741 "crypto-common 0.1.6",
742 "inout 0.1.4",
1107743]
1108744
1109745[[package]]
1110name = "clap"
1111version = "4.6.6"
746name = "cipher"
747version = "0.5.2"
1112748source = "registry+https://github.com/rust-lang/crates.io-index"
1113checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
749checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
1114750dependencies = [
1115 "clap_builder",
1116 "clap_derive",
1117]
1118
1119[[package]]
1120name = "clap_builder"
1121version = "4.6.6"
1122source = "registry+https://github.com/rust-lang/crates.io-index"
1123checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
1124dependencies = [
1125 "anstream",
1126 "anstyle",
1127 "clap_lex",
1128 "strsim",
1129]
1130
1131[[package]]
1132name = "clap_derive"
1133version = "4.6.4"
1134source = "registry+https://github.com/rust-lang/crates.io-index"
1135checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
1136dependencies = [
1137 "heck 0.5.0",
1138 "proc-macro2",
1139 "quote",
1140 "syn 3.0.3",
1141]
1142
1143[[package]]
1144name = "clap_lex"
1145version = "1.1.0"
1146source = "registry+https://github.com/rust-lang/crates.io-index"
1147checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
1148
1149[[package]]
1150name = "clru"
1151version = "0.6.3"
1152source = "registry+https://github.com/rust-lang/crates.io-index"
1153checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5"
1154dependencies = [
1155 "hashbrown 0.16.1",
1156]
1157
1158[[package]]
1159name = "cmake"
1160version = "0.1.58"
1161source = "registry+https://github.com/rust-lang/crates.io-index"
1162checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
1163dependencies = [
1164 "cc",
751 "block-buffer 0.12.1",
752 "crypto-common 0.2.2",
753 "inout 0.2.2",
1165754]
1166755
1167756[[package]]
@@ -1169,16 +758,6 @@
1169758version = "0.5.4"
1170759source = "registry+https://github.com/rust-lang/crates.io-index"
1171760checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
1172
1173[[package]]
1174name = "codesearch"
1175version = "1.94.0"
1176
1177[[package]]
1178name = "colorchoice"
1179version = "1.0.5"
1180source = "registry+https://github.com/rust-lang/crates.io-index"
1181checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
1182761
1183762[[package]]
1184763name = "combine"
@@ -1195,81 +774,29 @@
1195774]
1196775
1197776[[package]]
1198name = "compression-codecs"
1199version = "0.4.38"
1200source = "registry+https://github.com/rust-lang/crates.io-index"
1201checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
777name = "config"
778version = "1.0.0"
1202779dependencies = [
1203 "compression-core",
1204 "flate2",
1205 "memchr",
780 "sea-orm",
781 "serde",
1206782]
1207783
1208784[[package]]
1209name = "compression-core"
1210version = "0.4.32"
785name = "const-oid"
786version = "0.10.2"
1211787source = "registry+https://github.com/rust-lang/crates.io-index"
1212checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
788checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
1213789
1214790[[package]]
1215name = "concurrent-queue"
1216version = "2.5.0"
791name = "const-random"
792version = "0.1.18"
1217793source = "registry+https://github.com/rust-lang/crates.io-index"
1218checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
794checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
1219795dependencies = [
1220 "crossbeam-utils",
796 "const-random-macro",
1221797]
1222798
1223799[[package]]
1224name = "config"
1225version = "0.15.25"
1226source = "registry+https://github.com/rust-lang/crates.io-index"
1227checksum = "b85f248a4de22d204ceabc6299d89d2c70fbd7f09fea53c06c852369652d8139"
1228dependencies = [
1229 "pathdiff",
1230 "serde_core",
1231 "toml",
1232 "winnow",
1233 "yaml-rust2",
1234]
1235
1236[[package]]
1237name = "config"
1238version = "1.94.0"
1239dependencies = [
1240 "config 0.15.25",
1241 "dotenvy",
1242 "ipnet",
1243 "lombok",
1244 "serde",
1245 "serde_json",
1246 "thiserror 2.0.19",
1247 "time",
1248 "utoipa",
1249]
1250
1251[[package]]
1252name = "const-oid"
1253version = "0.9.6"
1254source = "registry+https://github.com/rust-lang/crates.io-index"
1255checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
1256
1257[[package]]
1258name = "const-oid"
1259version = "0.10.2"
1260source = "registry+https://github.com/rust-lang/crates.io-index"
1261checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
1262
1263[[package]]
1264name = "const-random"
1265version = "0.1.18"
1266source = "registry+https://github.com/rust-lang/crates.io-index"
1267checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
1268dependencies = [
1269 "const-random-macro",
1270]
1271
1272[[package]]
1273800name = "const-random-macro"
1274801version = "0.1.16"
1275802source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1278,35 +805,21 @@
1278805 "getrandom 0.2.17",
1279806 "once_cell",
1280807 "tiny-keccak",
1281]
1282
1283[[package]]
1284name = "convert_case"
1285version = "0.8.0"
1286source = "registry+https://github.com/rust-lang/crates.io-index"
1287checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
1288dependencies = [
1289 "unicode-segmentation",
1290]
1291
1292[[package]]
1293name = "convert_case"
1294version = "0.10.0"
1295source = "registry+https://github.com/rust-lang/crates.io-index"
1296checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
1297dependencies = [
1298 "unicode-segmentation",
1299808]
1300809
1301810[[package]]
1302811name = "cookie"
1303version = "0.18.1"
812version = "0.18.2"
1304813source = "registry+https://github.com/rust-lang/crates.io-index"
1305checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
814checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
1306815dependencies = [
1307 "percent-encoding",
1308 "time",
1309 "version_check",
816 "aes-gcm 0.10.3",
817 "base64 0.22.1",
818 "percent-encoding 2.3.2",
819 "rand 0.8.7",
820 "subtle",
821 "time 0.3.55",
822 "version_check 0.9.5",
1310823]
1311824
1312825[[package]]
@@ -1326,6 +839,15 @@
1326839checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
1327840
1328841[[package]]
842name = "core_maths"
843version = "0.1.1"
844source = "registry+https://github.com/rust-lang/crates.io-index"
845checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
846dependencies = [
847 "libm",
848]
849
850[[package]]
1329851name = "cpubits"
1330852version = "0.1.1"
1331853source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1363,16 +885,6 @@
1363885version = "2.5.0"
1364886source = "registry+https://github.com/rust-lang/crates.io-index"
1365887checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
1366
1367[[package]]
1368name = "crc-fast"
1369version = "1.10.0"
1370source = "registry+https://github.com/rust-lang/crates.io-index"
1371checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5"
1372dependencies = [
1373 "digest 0.10.7",
1374 "spin 0.10.1",
1375]
1376888
1377889[[package]]
1378890name = "crc16"
@@ -1381,19 +893,19 @@
1381893checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
1382894
1383895[[package]]
1384name = "crc32fast"
1385version = "1.5.0"
896name = "crossbeam-channel"
897version = "0.5.16"
1386898source = "registry+https://github.com/rust-lang/crates.io-index"
1387checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
899checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
1388900dependencies = [
1389 "cfg-if",
901 "crossbeam-utils",
1390902]
1391903
1392904[[package]]
1393name = "crossbeam-channel"
1394version = "0.5.16"
905name = "crossbeam-epoch"
906version = "0.9.20"
1395907source = "registry+https://github.com/rust-lang/crates.io-index"
1396checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
908checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
1397909dependencies = [
1398910 "crossbeam-utils",
1399911]
@@ -1418,35 +930,6 @@
1418930version = "0.2.4"
1419931source = "registry+https://github.com/rust-lang/crates.io-index"
1420932checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
1421
1422[[package]]
1423name = "crypto-bigint"
1424version = "0.5.5"
1425source = "registry+https://github.com/rust-lang/crates.io-index"
1426checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
1427dependencies = [
1428 "generic-array 0.14.9",
1429 "rand_core 0.6.4",
1430 "subtle",
1431 "zeroize",
1432]
1433
1434[[package]]
1435name = "crypto-bigint"
1436version = "0.7.5"
1437source = "registry+https://github.com/rust-lang/crates.io-index"
1438checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
1439dependencies = [
1440 "cpubits",
1441 "ctutils",
1442 "getrandom 0.4.3",
1443 "hybrid-array",
1444 "num-traits",
1445 "rand_core 0.10.1",
1446 "serdect",
1447 "subtle",
1448 "zeroize",
1449]
1450933
1451934[[package]]
1452935name = "crypto-common"
@@ -1454,7 +937,8 @@
1454937source = "registry+https://github.com/rust-lang/crates.io-index"
1455938checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
1456939dependencies = [
1457 "generic-array 0.14.9",
940 "generic-array",
941 "rand_core 0.6.4",
1458942 "typenum",
1459943]
1460944
@@ -1470,13 +954,12 @@
1470954]
1471955
1472956[[package]]
1473name = "crypto-primes"
1474version = "0.7.2"
957name = "ctr"
958version = "0.9.2"
1475959source = "registry+https://github.com/rust-lang/crates.io-index"
1476checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f"
960checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
1477961dependencies = [
1478 "crypto-bigint 0.7.5",
1479 "rand_core 0.10.1",
962 "cipher 0.4.4",
1480963]
1481964
1482965[[package]]
@@ -1485,7 +968,7 @@
1485968source = "registry+https://github.com/rust-lang/crates.io-index"
1486969checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21"
1487970dependencies = [
1488 "cipher",
971 "cipher 0.5.2",
1489972]
1490973
1491974[[package]]
@@ -1495,4665 +978,1988 @@
1495978checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
1496979dependencies = [
1497980 "cmov",
1498 "subtle",
1499981]
1500982
1501983[[package]]
1502name = "curve25519-dalek"
1503version = "4.1.3"
984name = "darling"
985version = "0.20.11"
1504986source = "registry+https://github.com/rust-lang/crates.io-index"
1505checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
987checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
1506988dependencies = [
1507 "cfg-if",
1508 "cpufeatures 0.2.17",
1509 "curve25519-dalek-derive",
1510 "digest 0.10.7",
1511 "fiat-crypto 0.2.9",
1512 "rustc_version",
1513 "subtle",
1514 "zeroize",
989 "darling_core",
990 "darling_macro",
1515991]
1516992
1517993[[package]]
1518name = "curve25519-dalek"
1519version = "5.0.0-rc.1"
994name = "darling_core"
995version = "0.20.11"
1520996source = "registry+https://github.com/rust-lang/crates.io-index"
1521checksum = "c906a87e53a36ff795d72e06e8162a83c5436e3ea89e942a9cb9fc083f0a384f"
997checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
1522998dependencies = [
1523 "cfg-if",
1524 "cpufeatures 0.3.0",
1525 "curve25519-dalek-derive",
1526 "digest 0.11.3",
1527 "fiat-crypto 0.3.0",
1528 "rustc_version",
1529 "subtle",
1530 "zeroize",
1531]
1532
1533[[package]]
1534name = "curve25519-dalek-derive"
1535version = "0.1.1"
1536source = "registry+https://github.com/rust-lang/crates.io-index"
1537checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
1538dependencies = [
999 "fnv",
1000 "ident_case",
15391001 "proc-macro2",
15401002 "quote",
15411003 "syn 2.0.119",
15421004]
15431005
15441006[[package]]
1545name = "darling"
1007name = "darling_macro"
15461008version = "0.20.11"
15471009source = "registry+https://github.com/rust-lang/crates.io-index"
1548checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
1010checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
15491011dependencies = [
1550 "darling_core 0.20.11",
1551 "darling_macro 0.20.11",
1012 "darling_core",
1013 "quote",
1014 "syn 2.0.119",
15521015]
15531016
15541017[[package]]
1555name = "darling"
1556version = "0.23.0"
1018name = "dashmap"
1019version = "6.2.1"
15571020source = "registry+https://github.com/rust-lang/crates.io-index"
1558checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
1021checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
15591022dependencies = [
1560 "darling_core 0.23.0",
1561 "darling_macro 0.23.0",
1023 "cfg-if",
1024 "crossbeam-utils",
1025 "hashbrown 0.14.5",
1026 "lock_api",
1027 "once_cell",
1028 "parking_lot_core",
15621029]
15631030
15641031[[package]]
1565name = "darling"
1566version = "0.24.0"
1032name = "deranged"
1033version = "0.5.8"
15671034source = "registry+https://github.com/rust-lang/crates.io-index"
1568checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23"
1035checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
15691036dependencies = [
1570 "darling_core 0.24.0",
1571 "darling_macro 0.24.0",
1037 "serde_core",
15721038]
15731039
15741040[[package]]
1575name = "darling_core"
1576version = "0.20.11"
1041name = "derive-where"
1042version = "1.6.1"
15771043source = "registry+https://github.com/rust-lang/crates.io-index"
1578checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
1044checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
15791045dependencies = [
1580 "fnv",
1581 "ident_case",
15821046 "proc-macro2",
15831047 "quote",
1584 "strsim",
15851048 "syn 2.0.119",
15861049]
15871050
15881051[[package]]
1589name = "darling_core"
1590version = "0.23.0"
1052name = "derive_more"
1053version = "2.1.1"
15911054source = "registry+https://github.com/rust-lang/crates.io-index"
1592checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
1055checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
15931056dependencies = [
1594 "ident_case",
1595 "proc-macro2",
1596 "quote",
1597 "strsim",
1598 "syn 2.0.119",
1057 "derive_more-impl",
15991058]
16001059
16011060[[package]]
1602name = "darling_core"
1603version = "0.24.0"
1061name = "derive_more-impl"
1062version = "2.1.1"
16041063source = "registry+https://github.com/rust-lang/crates.io-index"
1605checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4"
1064checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
16061065dependencies = [
1607 "ident_case",
16081066 "proc-macro2",
16091067 "quote",
1610 "strsim",
1611 "syn 3.0.3",
1612]
1613
1614[[package]]
1615name = "darling_macro"
1616version = "0.20.11"
1617source = "registry+https://github.com/rust-lang/crates.io-index"
1618checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
1619dependencies = [
1620 "darling_core 0.20.11",
1621 "quote",
1068 "rustc_version",
16221069 "syn 2.0.119",
1070 "unicode-xid",
16231071]
16241072
16251073[[package]]
1626name = "darling_macro"
1627version = "0.23.0"
1074name = "digest"
1075version = "0.10.7"
16281076source = "registry+https://github.com/rust-lang/crates.io-index"
1629checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
1077checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
16301078dependencies = [
1631 "darling_core 0.23.0",
1632 "quote",
1633 "syn 2.0.119",
1079 "block-buffer 0.10.4",
1080 "crypto-common 0.1.6",
1081 "subtle",
16341082]
16351083
16361084[[package]]
1637name = "darling_macro"
1638version = "0.24.0"
1085name = "digest"
1086version = "0.11.3"
16391087source = "registry+https://github.com/rust-lang/crates.io-index"
1640checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e"
1088checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
16411089dependencies = [
1642 "darling_core 0.24.0",
1643 "quote",
1644 "syn 3.0.3",
1090 "block-buffer 0.12.1",
1091 "const-oid",
1092 "crypto-common 0.2.2",
1093 "ctutils",
16451094]
16461095
16471096[[package]]
1648name = "dashmap"
1649version = "6.2.1"
1097name = "dirs"
1098version = "5.0.1"
16501099source = "registry+https://github.com/rust-lang/crates.io-index"
1651checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
1100checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
16521101dependencies = [
1653 "cfg-if",
1654 "crossbeam-utils",
1655 "hashbrown 0.14.5",
1656 "lock_api",
1657 "once_cell",
1658 "parking_lot_core",
1102 "dirs-sys",
16591103]
16601104
16611105[[package]]
1662name = "data-encoding"
1663version = "2.11.1"
1106name = "dirs-sys"
1107version = "0.4.1"
16641108source = "registry+https://github.com/rust-lang/crates.io-index"
1665checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
1666
1667[[package]]
1668name = "db"
1669version = "1.94.0"
1109checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
16701110dependencies = [
1671 "config 1.94.0",
1672 "log",
1673 "lombok",
1674 "sea-orm",
1675 "serde",
1676 "thiserror 2.0.19",
1677 "time",
1678 "tracing",
1679 "uuid",
1111 "libc",
1112 "option-ext",
1113 "redox_users",
1114 "windows-sys 0.48.0",
16801115]
16811116
16821117[[package]]
1683name = "deadpool"
1684version = "0.12.3"
1118name = "displaydoc"
1119version = "0.2.7"
16851120source = "registry+https://github.com/rust-lang/crates.io-index"
1686checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
1121checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
16871122dependencies = [
1688 "deadpool-runtime",
1689 "lazy_static",
1690 "num_cpus",
1691 "tokio",
1123 "proc-macro2",
1124 "quote",
1125 "syn 3.0.3",
16921126]
16931127
16941128[[package]]
1695name = "deadpool-runtime"
1696version = "0.1.4"
1129name = "dlv-list"
1130version = "0.3.0"
16971131source = "registry+https://github.com/rust-lang/crates.io-index"
1698checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
1132checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
16991133
17001134[[package]]
1701name = "defmt"
1702version = "1.1.1"
1135name = "dotenvy"
1136version = "0.15.7"
17031137source = "registry+https://github.com/rust-lang/crates.io-index"
1704checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
1705dependencies = [
1706 "bitflags 1.3.2",
1707 "defmt-macros",
1708]
1138checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
17091139
17101140[[package]]
1711name = "defmt-macros"
1712version = "1.1.1"
1141name = "either"
1142version = "1.17.0"
17131143source = "registry+https://github.com/rust-lang/crates.io-index"
1714checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
1144checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
17151145dependencies = [
1716 "defmt-parser",
1717 "proc-macro2",
1718 "quote",
1719 "syn 2.0.119",
1146 "serde",
17201147]
17211148
17221149[[package]]
1723name = "defmt-parser"
1724version = "1.0.0"
1150name = "equivalent"
1151version = "1.0.2"
17251152source = "registry+https://github.com/rust-lang/crates.io-index"
1726checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
1727dependencies = [
1728 "thiserror 2.0.19",
1729]
1153checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
17301154
17311155[[package]]
1732name = "delegate"
1733version = "0.13.5"
1156name = "errno"
1157version = "0.3.14"
17341158source = "registry+https://github.com/rust-lang/crates.io-index"
1735checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"
1159checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
17361160dependencies = [
1737 "proc-macro2",
1738 "quote",
1739 "syn 2.0.119",
1161 "libc",
1162 "windows-sys 0.61.2",
17401163]
17411164
17421165[[package]]
1743name = "der"
1744version = "0.7.10"
1166name = "etcetera"
1167version = "0.11.0"
17451168source = "registry+https://github.com/rust-lang/crates.io-index"
1746checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
1169checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
17471170dependencies = [
1748 "const-oid 0.9.6",
1749 "pem-rfc7468 0.7.0",
1750 "zeroize",
1171 "cfg-if",
1172 "windows-sys 0.61.2",
17511173]
17521174
17531175[[package]]
1754name = "der"
1755version = "0.8.1"
1176name = "event-listener"
1177version = "5.4.2"
17561178source = "registry+https://github.com/rust-lang/crates.io-index"
1757checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
1179checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
17581180dependencies = [
1759 "const-oid 0.10.2",
1760 "pem-rfc7468 1.0.0",
1761 "zeroize",
1181 "parking",
1182 "pin-project-lite",
17621183]
17631184
17641185[[package]]
1765name = "deranged"
1766version = "0.5.8"
1186name = "event-listener-strategy"
1187version = "0.5.4"
17671188source = "registry+https://github.com/rust-lang/crates.io-index"
1768checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
1189checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
17691190dependencies = [
1770 "serde_core",
1191 "event-listener",
1192 "pin-project-lite",
17711193]
17721194
17731195[[package]]
1774name = "derive-where"
1775version = "1.6.1"
1776source = "registry+https://github.com/rust-lang/crates.io-index"
1777checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
1196name = "export"
1197version = "1.0.0"
17781198dependencies = [
1779 "proc-macro2",
1780 "quote",
1781 "syn 2.0.119",
1199 "config",
1200 "redis",
1201 "sea-orm",
1202 "storage",
1203 "tokio",
17821204]
17831205
17841206[[package]]
1785name = "derive_builder"
1786version = "0.20.2"
1207name = "fastrand"
1208version = "2.5.0"
17871209source = "registry+https://github.com/rust-lang/crates.io-index"
1788checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
1789dependencies = [
1790 "derive_builder_macro",
1791]
1210checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
17921211
17931212[[package]]
1794name = "derive_builder_core"
1795version = "0.20.2"
1213name = "find-msvc-tools"
1214version = "0.1.11"
17961215source = "registry+https://github.com/rust-lang/crates.io-index"
1797checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
1798dependencies = [
1799 "darling 0.20.11",
1800 "proc-macro2",
1801 "quote",
1802 "syn 2.0.119",
1803]
1216checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
18041217
18051218[[package]]
1806name = "derive_builder_macro"
1807version = "0.20.2"
1219name = "fixed_decimal"
1220version = "0.7.2"
18081221source = "registry+https://github.com/rust-lang/crates.io-index"
1809checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
1222checksum = "79c3c892f121fff406e5dd6b28c1b30096b95111c30701a899d4f2b18da6d1bd"
18101223dependencies = [
1811 "derive_builder_core",
1812 "syn 2.0.119",
1224 "displaydoc",
1225 "smallvec",
1226 "writeable",
18131227]
18141228
18151229[[package]]
1816name = "derive_more"
1817version = "2.1.1"
1230name = "flume"
1231version = "0.12.0"
18181232source = "registry+https://github.com/rust-lang/crates.io-index"
1819checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
1233checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
18201234dependencies = [
1821 "derive_more-impl",
1235 "futures-core",
1236 "futures-sink",
1237 "spin",
18221238]
18231239
18241240[[package]]
1825name = "derive_more-impl"
1826version = "2.1.1"
1241name = "fnv"
1242version = "1.0.7"
18271243source = "registry+https://github.com/rust-lang/crates.io-index"
1828checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
1829dependencies = [
1830 "convert_case 0.10.0",
1831 "proc-macro2",
1832 "quote",
1833 "rustc_version",
1834 "syn 2.0.119",
1835 "unicode-xid",
1836]
1244checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
18371245
18381246[[package]]
1839name = "des"
1840version = "0.9.0"
1247name = "foldhash"
1248version = "0.2.0"
18411249source = "registry+https://github.com/rust-lang/crates.io-index"
1842checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a"
1843dependencies = [
1844 "cipher",
1845]
1250checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
18461251
18471252[[package]]
1848name = "digest"
1849version = "0.10.7"
1253name = "foreign-types"
1254version = "0.3.2"
18501255source = "registry+https://github.com/rust-lang/crates.io-index"
1851checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
1256checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
18521257dependencies = [
1853 "block-buffer 0.10.4",
1854 "const-oid 0.9.6",
1855 "crypto-common 0.1.6",
1856 "subtle",
1258 "foreign-types-shared",
18571259]
18581260
18591261[[package]]
1860name = "digest"
1861version = "0.11.3"
1262name = "foreign-types-shared"
1263version = "0.1.1"
1264source = "registry+https://github.com/rust-lang/crates.io-index"
1265checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
1266
1267[[package]]
1268name = "form_urlencoded"
1269version = "1.2.2"
18621270source = "registry+https://github.com/rust-lang/crates.io-index"
1863checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
1271checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
18641272dependencies = [
1865 "block-buffer 0.12.1",
1866 "const-oid 0.10.2",
1867 "crypto-common 0.2.2",
1868 "ctutils",
1273 "percent-encoding 2.3.2",
18691274]
18701275
18711276[[package]]
1872name = "displaydoc"
1873version = "0.2.7"
1277name = "forwarded-header-value"
1278version = "0.1.1"
18741279source = "registry+https://github.com/rust-lang/crates.io-index"
1875checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
1280checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
18761281dependencies = [
1877 "proc-macro2",
1878 "quote",
1879 "syn 3.0.3",
1282 "nonempty",
1283 "thiserror 1.0.69",
18801284]
18811285
18821286[[package]]
1883name = "dotenvy"
1884version = "0.15.7"
1287name = "funty"
1288version = "2.0.0"
18851289source = "registry+https://github.com/rust-lang/crates.io-index"
1886checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
1290checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
18871291
18881292[[package]]
1889name = "duct"
1890version = "1.1.1"
1293name = "futures"
1294version = "0.3.34"
18911295source = "registry+https://github.com/rust-lang/crates.io-index"
1892checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684"
1296checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
18931297dependencies = [
1894 "libc",
1895 "os_pipe",
1896 "shared_child",
1897 "shared_thread",
1298 "futures-channel",
1299 "futures-core",
1300 "futures-executor",
1301 "futures-io",
1302 "futures-sink",
1303 "futures-task",
1304 "futures-util",
18981305]
18991306
19001307[[package]]
1901name = "dunce"
1902version = "1.0.5"
1308name = "futures-channel"
1309version = "0.3.34"
19031310source = "registry+https://github.com/rust-lang/crates.io-index"
1904checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
1311checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
1312dependencies = [
1313 "futures-core",
1314 "futures-sink",
1315]
19051316
19061317[[package]]
1907name = "dyn-clone"
1908version = "1.0.20"
1318name = "futures-core"
1319version = "0.3.34"
19091320source = "registry+https://github.com/rust-lang/crates.io-index"
1910checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
1321checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
19111322
19121323[[package]]
1913name = "ecdsa"
1914version = "0.16.9"
1324name = "futures-executor"
1325version = "0.3.34"
19151326source = "registry+https://github.com/rust-lang/crates.io-index"
1916checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
1327checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
19171328dependencies = [
1918 "der 0.7.10",
1919 "digest 0.10.7",
1920 "elliptic-curve 0.13.8",
1921 "rfc6979 0.4.0",
1922 "signature 2.2.0",
1923 "spki 0.7.3",
1329 "futures-core",
1330 "futures-task",
1331 "futures-util",
19241332]
19251333
19261334[[package]]
1927name = "ecdsa"
1928version = "0.17.0"
1335name = "futures-intrusive"
1336version = "0.5.0"
19291337source = "registry+https://github.com/rust-lang/crates.io-index"
1930checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
1338checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
19311339dependencies = [
1932 "der 0.8.1",
1933 "digest 0.11.3",
1934 "elliptic-curve 0.14.1",
1935 "rfc6979 0.6.0",
1936 "signature 3.0.0",
1937 "spki 0.8.0",
1938 "zeroize",
1340 "futures-core",
1341 "lock_api",
1342 "parking_lot",
19391343]
19401344
19411345[[package]]
1942name = "ed25519"
1943version = "2.2.3"
1346name = "futures-io"
1347version = "0.3.34"
1348source = "registry+https://github.com/rust-lang/crates.io-index"
1349checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
1350
1351[[package]]
1352name = "futures-macro"
1353version = "0.3.34"
19441354source = "registry+https://github.com/rust-lang/crates.io-index"
1945checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
1355checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
19461356dependencies = [
1947 "pkcs8 0.10.2",
1948 "signature 2.2.0",
1357 "proc-macro2",
1358 "quote",
1359 "syn 3.0.3",
19491360]
19501361
19511362[[package]]
1952name = "ed25519"
1953version = "3.0.0"
1363name = "futures-sink"
1364version = "0.3.34"
1365source = "registry+https://github.com/rust-lang/crates.io-index"
1366checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
1367
1368[[package]]
1369name = "futures-task"
1370version = "0.3.34"
19541371source = "registry+https://github.com/rust-lang/crates.io-index"
1955checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
1956dependencies = [
1957 "pkcs8 0.11.0",
1958 "signature 3.0.0",
1959]
1372checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
19601373
19611374[[package]]
1962name = "ed25519-dalek"
1963version = "2.2.0"
1375name = "futures-util"
1376version = "0.3.34"
19641377source = "registry+https://github.com/rust-lang/crates.io-index"
1965checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
1378checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
19661379dependencies = [
1967 "curve25519-dalek 4.1.3",
1968 "ed25519 2.2.3",
1969 "serde",
1970 "sha2 0.10.9",
1971 "subtle",
1972 "zeroize",
1380 "futures-channel",
1381 "futures-core",
1382 "futures-io",
1383 "futures-macro",
1384 "futures-sink",
1385 "futures-task",
1386 "memchr",
1387 "pin-project-lite",
1388 "slab",
19731389]
19741390
19751391[[package]]
1976name = "ed25519-dalek"
1977version = "3.0.0-rc.1"
1392name = "generic-array"
1393version = "0.14.9"
19781394source = "registry+https://github.com/rust-lang/crates.io-index"
1979checksum = "1685663e23882cd8517dcbcb1c23a6ebff4433c22dfb681d760219b62cd1b849"
1395checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
19801396dependencies = [
1981 "curve25519-dalek 5.0.0-rc.1",
1982 "ed25519 3.0.0",
1983 "rand_core 0.10.1",
1984 "serde",
1985 "sha2 0.11.0",
1986 "signature 3.0.0",
1987 "subtle",
1988 "zeroize",
1397 "typenum",
1398 "version_check 0.9.5",
19891399]
19901400
19911401[[package]]
1992name = "either"
1993version = "1.17.0"
1402name = "getrandom"
1403version = "0.2.17"
19941404source = "registry+https://github.com/rust-lang/crates.io-index"
1995checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
1405checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
19961406dependencies = [
1997 "serde",
1407 "cfg-if",
1408 "libc",
1409 "wasi 0.11.1+wasi-snapshot-preview1",
19981410]
19991411
20001412[[package]]
2001name = "elliptic-curve"
2002version = "0.13.8"
1413name = "getrandom"
1414version = "0.3.4"
20031415source = "registry+https://github.com/rust-lang/crates.io-index"
2004checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
1416checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
20051417dependencies = [
2006 "base16ct 0.2.0",
2007 "crypto-bigint 0.5.5",
2008 "digest 0.10.7",
2009 "ff 0.13.1",
2010 "generic-array 0.14.9",
2011 "group 0.13.0",
2012 "hkdf 0.12.4",
2013 "pem-rfc7468 0.7.0",
2014 "pkcs8 0.10.2",
2015 "rand_core 0.6.4",
2016 "sec1 0.7.3",
2017 "subtle",
2018 "zeroize",
1418 "cfg-if",
1419 "libc",
1420 "r-efi 5.3.0",
1421 "wasip2",
20191422]
20201423
20211424[[package]]
2022name = "elliptic-curve"
2023version = "0.14.1"
1425name = "getrandom"
1426version = "0.4.3"
20241427source = "registry+https://github.com/rust-lang/crates.io-index"
2025checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
1428checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
20261429dependencies = [
2027 "base16ct 1.0.0",
2028 "crypto-bigint 0.7.5",
2029 "crypto-common 0.2.2",
2030 "digest 0.11.3",
2031 "ff 0.14.0",
2032 "group 0.14.0",
2033 "hkdf 0.13.0",
2034 "hybrid-array",
2035 "pem-rfc7468 1.0.0",
2036 "pkcs8 0.11.0",
1430 "cfg-if",
1431 "libc",
1432 "r-efi 6.0.0",
20371433 "rand_core 0.10.1",
2038 "sec1 0.8.1",
2039 "subtle",
2040 "zeroize",
20411434]
20421435
20431436[[package]]
2044name = "email"
2045version = "1.94.0"
1437name = "ghash"
1438version = "0.5.1"
1439source = "registry+https://github.com/rust-lang/crates.io-index"
1440checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
20461441dependencies = [
2047 "async-trait",
2048 "base64 0.22.1",
2049 "config 1.94.0",
2050 "futures",
2051 "handlebars",
2052 "lettre",
2053 "lombok",
2054 "metrics",
2055 "queue",
2056 "serde",
2057 "serde_json",
2058 "thiserror 2.0.19",
2059 "tokio",
2060 "tokio-util",
2061 "tracing",
2062 "uuid",
1442 "opaque-debug",
1443 "polyval 0.6.2",
20631444]
20641445
20651446[[package]]
2066name = "email-encoding"
2067version = "0.4.1"
1447name = "ghash"
1448version = "0.6.0"
20681449source = "registry+https://github.com/rust-lang/crates.io-index"
2069checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
1450checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5"
20701451dependencies = [
2071 "base64 0.22.1",
2072 "memchr",
1452 "polyval 0.7.3",
20731453]
20741454
20751455[[package]]
2076name = "email_address"
2077version = "0.2.9"
1456name = "h2"
1457version = "0.3.27"
20781458source = "registry+https://github.com/rust-lang/crates.io-index"
2079checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
1459checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
1460dependencies = [
1461 "bytes",
1462 "fnv",
1463 "futures-core",
1464 "futures-sink",
1465 "futures-util",
1466 "http 0.2.12",
1467 "indexmap",
1468 "slab",
1469 "tokio",
1470 "tokio-util",
1471 "tracing",
1472]
20801473
20811474[[package]]
2082name = "encoding_rs"
2083version = "0.8.35"
1475name = "half"
1476version = "2.7.1"
20841477source = "registry+https://github.com/rust-lang/crates.io-index"
2085checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
1478checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
20861479dependencies = [
20871480 "cfg-if",
1481 "crunchy",
1482 "num-traits",
1483 "zerocopy",
20881484]
20891485
20901486[[package]]
2091name = "enum_dispatch"
2092version = "0.3.13"
1487name = "hashbrown"
1488version = "0.12.3"
20931489source = "registry+https://github.com/rust-lang/crates.io-index"
2094checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
1490checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
20951491dependencies = [
2096 "once_cell",
2097 "proc-macro2",
2098 "quote",
2099 "syn 2.0.119",
1492 "ahash 0.7.8",
21001493]
21011494
21021495[[package]]
2103name = "equivalent"
2104version = "1.0.2"
1496name = "hashbrown"
1497version = "0.14.5"
21051498source = "registry+https://github.com/rust-lang/crates.io-index"
2106checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
1499checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
21071500
21081501[[package]]
2109name = "errno"
2110version = "0.3.14"
1502name = "hashbrown"
1503version = "0.16.1"
21111504source = "registry+https://github.com/rust-lang/crates.io-index"
2112checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1505checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
21131506dependencies = [
2114 "libc",
2115 "windows-sys 0.61.2",
1507 "allocator-api2",
1508 "equivalent",
1509 "foldhash",
21161510]
21171511
21181512[[package]]
2119name = "etcetera"
2120version = "0.11.0"
1513name = "hashbrown"
1514version = "0.17.1"
21211515source = "registry+https://github.com/rust-lang/crates.io-index"
2122checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
2123dependencies = [
2124 "cfg-if",
2125 "windows-sys 0.61.2",
2126]
1516checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
21271517
21281518[[package]]
2129name = "event-listener"
2130version = "5.4.2"
1519name = "hashlink"
1520version = "0.11.1"
21311521source = "registry+https://github.com/rust-lang/crates.io-index"
2132checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
1522checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
21331523dependencies = [
2134 "parking",
2135 "pin-project-lite",
1524 "hashbrown 0.16.1",
21361525]
21371526
21381527[[package]]
2139name = "event-listener-strategy"
2140version = "0.5.4"
1528name = "heck"
1529version = "0.4.1"
21411530source = "registry+https://github.com/rust-lang/crates.io-index"
2142checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
2143dependencies = [
2144 "event-listener",
2145 "pin-project-lite",
2146]
1531checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
21471532
21481533[[package]]
2149name = "eventsource-stream"
2150version = "0.2.3"
1534name = "heck"
1535version = "0.5.0"
21511536source = "registry+https://github.com/rust-lang/crates.io-index"
2152checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
2153dependencies = [
2154 "futures-core",
2155 "nom 7.1.3",
2156 "pin-project-lite",
2157]
1537checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
21581538
21591539[[package]]
2160name = "export"
2161version = "1.94.0"
2162dependencies = [
2163 "axum",
2164 "db",
2165 "serde",
2166 "serde_json",
2167 "time",
2168 "utoipa",
2169 "uuid",
2170 "validator",
2171]
1540name = "hermit-abi"
1541version = "0.5.2"
1542source = "registry+https://github.com/rust-lang/crates.io-index"
1543checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
21721544
21731545[[package]]
2174name = "fast_chemail"
2175version = "0.9.6"
1546name = "hex"
1547version = "0.4.3"
21761548source = "registry+https://github.com/rust-lang/crates.io-index"
2177checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
2178dependencies = [
2179 "ascii_utils",
2180]
1549checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
21811550
21821551[[package]]
2183name = "faster-hex"
2184version = "0.10.0"
1552name = "hkdf"
1553version = "0.13.0"
21851554source = "registry+https://github.com/rust-lang/crates.io-index"
2186checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
1555checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
21871556dependencies = [
2188 "heapless",
2189 "serde",
1557 "hmac 0.13.0",
21901558]
21911559
21921560[[package]]
2193name = "fastrand"
2194version = "2.5.0"
1561name = "hmac"
1562version = "0.12.1"
21951563source = "registry+https://github.com/rust-lang/crates.io-index"
2196checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
1564checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
1565dependencies = [
1566 "digest 0.10.7",
1567]
21971568
21981569[[package]]
2199name = "ff"
2200version = "0.13.1"
1570name = "hmac"
1571version = "0.13.0"
22011572source = "registry+https://github.com/rust-lang/crates.io-index"
2202checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
1573checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
22031574dependencies = [
2204 "rand_core 0.6.4",
2205 "subtle",
1575 "digest 0.11.3",
22061576]
22071577
22081578[[package]]
2209name = "ff"
2210version = "0.14.0"
1579name = "http"
1580version = "0.2.12"
22111581source = "registry+https://github.com/rust-lang/crates.io-index"
2212checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
1582checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
22131583dependencies = [
2214 "rand_core 0.10.1",
2215 "subtle",
1584 "bytes",
1585 "fnv",
1586 "itoa",
22161587]
22171588
22181589[[package]]
2219name = "fiat-crypto"
2220version = "0.2.9"
1590name = "http"
1591version = "1.5.0"
22211592source = "registry+https://github.com/rust-lang/crates.io-index"
2222checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
1593checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
1594dependencies = [
1595 "bytes",
1596 "itoa",
1597]
22231598
22241599[[package]]
2225name = "fiat-crypto"
2226version = "0.3.0"
1600name = "http-body"
1601version = "0.4.6"
22271602source = "registry+https://github.com/rust-lang/crates.io-index"
2228checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
2229
2230[[package]]
2231name = "filetime"
2232version = "0.2.29"
2233source = "registry+https://github.com/rust-lang/crates.io-index"
2234checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
1603checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
22351604dependencies = [
2236 "cfg-if",
2237 "libc",
1605 "bytes",
1606 "http 0.2.12",
1607 "pin-project-lite",
22381608]
22391609
22401610[[package]]
2241name = "find-msvc-tools"
2242version = "0.1.9"
1611name = "http-body"
1612version = "1.1.0"
22431613source = "registry+https://github.com/rust-lang/crates.io-index"
2244checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
2245
2246[[package]]
2247name = "fixedbitset"
2248version = "0.5.7"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2250checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
2251
2252[[package]]
2253name = "flate2"
2254version = "1.1.9"
2255source = "registry+https://github.com/rust-lang/crates.io-index"
2256checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
1614checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
22571615dependencies = [
2258 "crc32fast",
2259 "miniz_oxide",
1616 "bytes",
1617 "http 1.5.0",
22601618]
22611619
22621620[[package]]
2263name = "flume"
2264version = "0.12.0"
1621name = "http-body-util"
1622version = "0.1.5"
22651623source = "registry+https://github.com/rust-lang/crates.io-index"
2266checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
1624checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
22671625dependencies = [
1626 "bytes",
22681627 "futures-core",
2269 "futures-sink",
2270 "spin 0.9.9",
1628 "http 1.5.0",
1629 "http-body 1.1.0",
1630 "pin-project-lite",
22711631]
22721632
22731633[[package]]
2274name = "fnv"
2275version = "1.0.7"
1634name = "httparse"
1635version = "1.10.1"
22761636source = "registry+https://github.com/rust-lang/crates.io-index"
2277checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1637checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
22781638
22791639[[package]]
2280name = "foldhash"
2281version = "0.1.5"
1640name = "httpdate"
1641version = "1.0.3"
22821642source = "registry+https://github.com/rust-lang/crates.io-index"
2283checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1643checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
22841644
22851645[[package]]
2286name = "foldhash"
2287version = "0.2.0"
1646name = "hybrid-array"
1647version = "0.4.14"
22881648source = "registry+https://github.com/rust-lang/crates.io-index"
2289checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
2290
2291[[package]]
2292name = "foreign-types"
2293version = "0.3.2"
2294source = "registry+https://github.com/rust-lang/crates.io-index"
2295checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
1649checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
22961650dependencies = [
2297 "foreign-types-shared",
1651 "typenum",
22981652]
22991653
23001654[[package]]
2301name = "foreign-types-shared"
2302version = "0.1.1"
2303source = "registry+https://github.com/rust-lang/crates.io-index"
2304checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
2305
2306[[package]]
2307name = "form_urlencoded"
2308version = "1.2.2"
1655name = "hyper"
1656version = "0.10.16"
23091657source = "registry+https://github.com/rust-lang/crates.io-index"
2310checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
1658checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
23111659dependencies = [
2312 "percent-encoding",
1660 "base64 0.9.3",
1661 "httparse",
1662 "language-tags",
1663 "log 0.3.9",
1664 "mime 0.2.6",
1665 "num_cpus",
1666 "time 0.1.45",
1667 "traitobject",
1668 "typeable",
1669 "unicase",
1670 "url 1.7.2",
23131671]
23141672
23151673[[package]]
2316name = "fs_extra"
2317version = "1.3.0"
1674name = "hyper"
1675version = "0.14.32"
23181676source = "registry+https://github.com/rust-lang/crates.io-index"
2319checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
2320
2321[[package]]
2322name = "funty"
2323version = "2.0.0"
2324source = "registry+https://github.com/rust-lang/crates.io-index"
2325checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
2326
2327[[package]]
2328name = "futures"
2329version = "0.3.33"
2330source = "registry+https://github.com/rust-lang/crates.io-index"
2331checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
1677checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
23321678dependencies = [
1679 "bytes",
23331680 "futures-channel",
23341681 "futures-core",
2335 "futures-executor",
2336 "futures-io",
2337 "futures-sink",
2338 "futures-task",
23391682 "futures-util",
1683 "h2",
1684 "http 0.2.12",
1685 "http-body 0.4.6",
1686 "httparse",
1687 "httpdate",
1688 "itoa",
1689 "pin-project-lite",
1690 "socket2 0.5.10",
1691 "tokio",
1692 "tower-service",
1693 "tracing",
1694 "want",
23401695]
23411696
23421697[[package]]
2343name = "futures-channel"
2344version = "0.3.33"
1698name = "hyper"
1699version = "1.11.0"
23451700source = "registry+https://github.com/rust-lang/crates.io-index"
2346checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
1701checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
23471702dependencies = [
1703 "atomic-waker",
1704 "bytes",
1705 "futures-channel",
23481706 "futures-core",
2349 "futures-sink",
1707 "http 1.5.0",
1708 "http-body 1.1.0",
1709 "httparse",
1710 "httpdate",
1711 "itoa",
1712 "pin-project-lite",
1713 "smallvec",
1714 "tokio",
23501715]
23511716
23521717[[package]]
2353name = "futures-core"
2354version = "0.3.33"
1718name = "hyper-native-tls"
1719version = "0.3.0"
23551720source = "registry+https://github.com/rust-lang/crates.io-index"
2356checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
2357
2358[[package]]
2359name = "futures-executor"
2360version = "0.3.33"
2361source = "registry+https://github.com/rust-lang/crates.io-index"
2362checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
1721checksum = "6d375598f442742b0e66208ee12501391f1c7ac0bafb90b4fe53018f81f06068"
23631722dependencies = [
2364 "futures-core",
2365 "futures-task",
2366 "futures-util",
1723 "antidote",
1724 "hyper 0.10.16",
1725 "native-tls",
23671726]
23681727
23691728[[package]]
2370name = "futures-intrusive"
1729name = "hyper-tls"
23711730version = "0.5.0"
23721731source = "registry+https://github.com/rust-lang/crates.io-index"
2373checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
1732checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
23741733dependencies = [
2375 "futures-core",
2376 "lock_api",
2377 "parking_lot",
1734 "bytes",
1735 "hyper 0.14.32",
1736 "native-tls",
1737 "tokio",
1738 "tokio-native-tls",
23781739]
23791740
23801741[[package]]
2381name = "futures-io"
2382version = "0.3.33"
1742name = "hyper-util"
1743version = "0.1.20"
23831744source = "registry+https://github.com/rust-lang/crates.io-index"
2384checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
2385
2386[[package]]
2387name = "futures-lite"
2388version = "2.6.1"
2389source = "registry+https://github.com/rust-lang/crates.io-index"
2390checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
1745checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
23911746dependencies = [
2392 "futures-core",
1747 "bytes",
1748 "http 1.5.0",
1749 "http-body 1.1.0",
1750 "hyper 1.11.0",
23931751 "pin-project-lite",
1752 "tokio",
1753 "tower-service",
23941754]
23951755
23961756[[package]]
2397name = "futures-macro"
2398version = "0.3.33"
1757name = "iana-time-zone"
1758version = "0.1.65"
23991759source = "registry+https://github.com/rust-lang/crates.io-index"
2400checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
1760checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
24011761dependencies = [
2402 "proc-macro2",
2403 "quote",
2404 "syn 2.0.119",
1762 "android_system_properties",
1763 "core-foundation-sys",
1764 "iana-time-zone-haiku",
1765 "js-sys",
1766 "log 0.4.33",
1767 "wasm-bindgen",
1768 "windows-core",
24051769]
24061770
24071771[[package]]
2408name = "futures-sink"
2409version = "0.3.33"
1772name = "iana-time-zone-haiku"
1773version = "0.1.2"
24101774source = "registry+https://github.com/rust-lang/crates.io-index"
2411checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
2412
2413[[package]]
2414name = "futures-task"
2415version = "0.3.33"
2416source = "registry+https://github.com/rust-lang/crates.io-index"
2417checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
2418
2419[[package]]
2420name = "futures-util"
2421version = "0.3.33"
2422source = "registry+https://github.com/rust-lang/crates.io-index"
2423checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
1775checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
24241776dependencies = [
2425 "futures-channel",
2426 "futures-core",
2427 "futures-io",
2428 "futures-macro",
2429 "futures-sink",
2430 "futures-task",
2431 "memchr",
2432 "pin-project-lite",
2433 "slab",
1777 "cc",
24341778]
24351779
24361780[[package]]
2437name = "generic-array"
2438version = "0.14.9"
1781name = "icu"
1782version = "2.3.0"
24391783source = "registry+https://github.com/rust-lang/crates.io-index"
2440checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
1784checksum = "8d5080b89807979ab05f8b6f3c6bbd580ece1512a6cf4cf800baa76937e488b3"
24411785dependencies = [
2442 "typenum",
2443 "version_check",
2444 "zeroize",
1786 "icu_calendar",
1787 "icu_casemap",
1788 "icu_collator",
1789 "icu_collections",
1790 "icu_datetime",
1791 "icu_decimal",
1792 "icu_experimental",
1793 "icu_list",
1794 "icu_locale",
1795 "icu_normalizer",
1796 "icu_pattern",
1797 "icu_plurals",
1798 "icu_properties",
1799 "icu_provider",
1800 "icu_segmenter",
1801 "icu_time",
24451802]
24461803
24471804[[package]]
2448name = "generic-array"
2449version = "1.4.4"
1805name = "icu_calendar"
1806version = "2.3.0"
24501807source = "registry+https://github.com/rust-lang/crates.io-index"
2451checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b"
1808checksum = "58655df2f728e46e4eee80bddebefacf52ec3e77cdb925014b47c5a5905eb55c"
24521809dependencies = [
2453 "generic-array 0.14.9",
2454 "rustversion",
2455 "typenum",
1810 "calendrical_calculations",
1811 "displaydoc",
1812 "icu_calendar_data",
1813 "icu_locale_core",
1814 "icu_locale_fallback",
1815 "icu_provider",
1816 "ixdtf",
1817 "serde",
1818 "tinystr",
1819 "zerovec",
24561820]
24571821
24581822[[package]]
2459name = "getrandom"
2460version = "0.2.17"
1823name = "icu_calendar_data"
1824version = "2.3.0"
24611825source = "registry+https://github.com/rust-lang/crates.io-index"
2462checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
2463dependencies = [
2464 "cfg-if",
2465 "js-sys",
2466 "libc",
2467 "wasi",
2468 "wasm-bindgen",
2469]
1826checksum = "dc00caaa3fb3201ff7a18aa458e8c3ab042b9da154cfdf948bdde3936c31c36e"
24701827
24711828[[package]]
2472name = "getrandom"
2473version = "0.3.4"
1829name = "icu_casemap"
1830version = "2.3.0"
24741831source = "registry+https://github.com/rust-lang/crates.io-index"
2475checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
1832checksum = "f2edfd120d8445de853cf53a1abec916f638f933a70a6f4bf8f7e2d7b7f884e9"
24761833dependencies = [
2477 "cfg-if",
2478 "libc",
2479 "r-efi 5.3.0",
2480 "wasip2",
1834 "icu_casemap_data",
1835 "icu_collections",
1836 "icu_locale_core",
1837 "icu_properties",
1838 "icu_provider",
1839 "potential_utf",
1840 "writeable",
1841 "zerovec",
24811842]
24821843
24831844[[package]]
2484name = "getrandom"
2485version = "0.4.3"
1845name = "icu_casemap_data"
1846version = "2.3.0"
24861847source = "registry+https://github.com/rust-lang/crates.io-index"
2487checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
2488dependencies = [
2489 "cfg-if",
2490 "js-sys",
2491 "libc",
2492 "r-efi 6.0.0",
2493 "rand_core 0.10.1",
2494 "wasm-bindgen",
2495]
1848checksum = "059fcda0fc8d33effc6633a8cf0d8a3c17caf7d7c91918fba9918744f501e4e8"
24961849
24971850[[package]]
2498name = "ghash"
2499version = "0.6.0"
1851name = "icu_collator"
1852version = "2.3.0"
25001853source = "registry+https://github.com/rust-lang/crates.io-index"
2501checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5"
1854checksum = "59eea194d189ca897bcb960fd2130f9e7d53998460d4d32e85a60b10d5507cf4"
25021855dependencies = [
2503 "polyval",
2504]
2505
2506[[package]]
2507name = "git"
2508version = "1.94.0"
2509dependencies = [
2510 "async-graphql",
2511 "async-trait",
2512 "base64 0.23.1",
2513 "bytes",
2514 "cache",
2515 "duct",
2516 "gitlfs",
2517 "gix",
2518 "serde",
2519 "serde_json",
2520 "storage",
2521 "thiserror 2.0.19",
2522 "tokio",
2523 "tracing",
2524]
2525
2526[[package]]
2527name = "gitauth"
2528version = "1.94.0"
2529dependencies = [
2530 "auth",
2531 "base64 0.23.1",
2532 "cache",
2533 "config 1.94.0",
2534 "db",
2535 "jwt",
2536 "metrics",
2537 "repo",
2538 "sea-orm",
2539 "thiserror 2.0.19",
2540 "time",
2541 "tokio",
2542 "tracing",
2543 "uuid",
2544]
2545
2546[[package]]
2547name = "githook"
2548version = "1.94.0"
2549dependencies = [
2550 "cache",
2551 "config 1.94.0",
2552 "db",
2553 "gitsync",
2554 "obs",
2555 "queue",
2556 "storage",
2557 "tokio",
2558 "tracing",
2559 "webhook",
2560]
2561
2562[[package]]
2563name = "gitlfs"
2564version = "1.94.0"
2565dependencies = [
2566 "async-trait",
2567 "bytes",
2568 "futures",
2569 "metrics",
2570 "object_store",
2571 "sha2 0.10.9",
2572 "storage",
2573 "thiserror 2.0.19",
2574 "tracing",
2575 "url",
2576]
2577
2578[[package]]
2579name = "gitpro"
2580version = "1.94.0"
2581dependencies = [
2582 "axum",
2583 "cache",
2584 "config 1.94.0",
2585 "db",
2586 "gitauth",
2587 "gitsmart",
2588 "gitssh",
2589 "jwt",
2590 "obs",
2591 "queue",
2592 "storage",
2593 "tokio",
2594 "tracing",
2595]
2596
2597[[package]]
2598name = "gitsmart"
2599version = "1.94.0"
2600dependencies = [
2601 "async-compression",
2602 "async-trait",
2603 "axum",
2604 "base64 0.23.1",
2605 "bytes",
2606 "cache",
2607 "config 1.94.0",
2608 "db",
2609 "flate2",
2610 "futures",
2611 "git",
2612 "gitauth",
2613 "gitlfs",
2614 "gitsync",
2615 "http",
2616 "metrics",
2617 "queue",
2618 "repo",
2619 "serde",
2620 "serde_json",
2621 "storage",
2622 "thiserror 2.0.19",
2623 "tokio",
2624 "tokio-util",
2625 "tracing",
2626 "uuid",
2627]
2628
2629[[package]]
2630name = "gitssh"
2631version = "1.94.0"
2632dependencies = [
2633 "bytes",
2634 "cache",
2635 "config 1.94.0",
2636 "db",
2637 "futures",
2638 "gitauth",
2639 "gitsmart",
2640 "gitsync",
2641 "metrics",
2642 "queue",
2643 "rand 0.10.2",
2644 "repo",
2645 "russh",
2646 "sea-orm",
2647 "ssh-key",
2648 "thiserror 2.0.19",
2649 "time",
2650 "tokio",
2651 "tracing",
2652 "uuid",
2653]
2654
2655[[package]]
2656name = "gitsync"
2657version = "1.94.0"
2658dependencies = [
2659 "async-trait",
2660 "cache",
2661 "config 1.94.0",
2662 "db",
2663 "futures",
2664 "git",
2665 "metrics",
2666 "queue",
2667 "redis",
2668 "repo",
2669 "sea-orm",
2670 "serde",
2671 "serde_json",
2672 "storage",
2673 "thiserror 2.0.19",
2674 "time",
2675 "tokio",
2676 "tracing",
2677 "uuid",
2678]
2679
2680[[package]]
2681name = "gix"
2682version = "0.86.0"
2683source = "registry+https://github.com/rust-lang/crates.io-index"
2684checksum = "bb3790fd8981cba7949f1ba924ef865d902df731627bc5998d14164063892fce"
2685dependencies = [
2686 "gix-actor",
2687 "gix-archive",
2688 "gix-attributes",
2689 "gix-blame",
2690 "gix-command",
2691 "gix-commitgraph",
2692 "gix-config",
2693 "gix-credentials",
2694 "gix-date",
2695 "gix-diff",
2696 "gix-dir",
2697 "gix-discover",
2698 "gix-error",
2699 "gix-features",
2700 "gix-filter",
2701 "gix-fs",
2702 "gix-glob",
2703 "gix-hash",
2704 "gix-hashtable",
2705 "gix-ignore",
2706 "gix-index",
2707 "gix-lock",
2708 "gix-mailmap",
2709 "gix-negotiate",
2710 "gix-object",
2711 "gix-odb",
2712 "gix-pack",
2713 "gix-path",
2714 "gix-pathspec",
2715 "gix-prompt",
2716 "gix-protocol",
2717 "gix-ref",
2718 "gix-refspec",
2719 "gix-revision",
2720 "gix-revwalk",
2721 "gix-sec",
2722 "gix-shallow",
2723 "gix-status",
2724 "gix-submodule",
2725 "gix-tempfile",
2726 "gix-trace",
2727 "gix-transport",
2728 "gix-traverse",
2729 "gix-url",
2730 "gix-utils",
2731 "gix-validate",
2732 "gix-worktree",
2733 "gix-worktree-state",
2734 "gix-worktree-stream",
2735 "gix-zlib",
2736 "nonempty",
2737 "parking_lot",
2738 "regex",
2739 "serde",
2740 "signal-hook 0.4.4",
2741 "smallvec",
2742 "thiserror 2.0.19",
2743]
2744
2745[[package]]
2746name = "gix-actor"
2747version = "0.41.2"
2748source = "registry+https://github.com/rust-lang/crates.io-index"
2749checksum = "33f9308ad6fd35b2a865cbe4117ac61b2be59e4a9ef1621c7a9794f7c8e52c5b"
2750dependencies = [
2751 "bstr",
2752 "gix-date",
2753 "gix-error",
2754 "serde",
2755]
2756
2757[[package]]
2758name = "gix-archive"
2759version = "0.35.0"
2760source = "registry+https://github.com/rust-lang/crates.io-index"
2761checksum = "6760ef6ef4edfa6449918977d59518ff209676f47e89bdce87a36a50be35e834"
2762dependencies = [
2763 "bstr",
2764 "gix-date",
2765 "gix-error",
2766 "gix-object",
2767 "gix-worktree-stream",
2768]
2769
2770[[package]]
2771name = "gix-attributes"
2772version = "0.34.0"
2773source = "registry+https://github.com/rust-lang/crates.io-index"
2774checksum = "c31c593692ebdc1e38858d9a2b56f6a594c501e24a38971fe6685571f5a07be0"
2775dependencies = [
2776 "bstr",
2777 "gix-features",
2778 "gix-glob",
2779 "gix-path",
2780 "gix-quote",
2781 "gix-trace",
2782 "serde",
2783 "smallvec",
2784 "thiserror 2.0.19",
2785 "unicode-bom",
2786]
2787
2788[[package]]
2789name = "gix-bitmap"
2790version = "0.3.3"
2791source = "registry+https://github.com/rust-lang/crates.io-index"
2792checksum = "7cd1d118d0f5d88b96e6f6e13b566475fef4797ead4a02c26fed36c1375066f7"
2793dependencies = [
2794 "gix-error",
2795]
2796
2797[[package]]
2798name = "gix-blame"
2799version = "0.16.0"
2800source = "registry+https://github.com/rust-lang/crates.io-index"
2801checksum = "b06d20ff0e88ada2dd852b3852727b664ec9baefdf653d1d4e722e858c52cd17"
2802dependencies = [
2803 "gix-commitgraph",
2804 "gix-date",
2805 "gix-diff",
2806 "gix-error",
2807 "gix-hash",
2808 "gix-object",
2809 "gix-revwalk",
2810 "gix-trace",
2811 "gix-traverse",
2812 "gix-worktree",
2813 "smallvec",
2814 "thiserror 2.0.19",
2815]
2816
2817[[package]]
2818name = "gix-chunk"
2819version = "0.7.3"
2820source = "registry+https://github.com/rust-lang/crates.io-index"
2821checksum = "b2a871e5cab12ba568845714473505deefffb3c04eb47f4708ce344cd459c1cc"
2822dependencies = [
2823 "gix-error",
2824]
2825
2826[[package]]
2827name = "gix-command"
2828version = "0.9.2"
2829source = "registry+https://github.com/rust-lang/crates.io-index"
2830checksum = "cf4363accdf6ef7ba861871d2d521ab7418a04aaaed919fadb022af71d379b12"
2831dependencies = [
2832 "bstr",
2833 "gix-path",
2834 "gix-quote",
2835 "gix-trace",
2836 "shell-words",
2837]
2838
2839[[package]]
2840name = "gix-commitgraph"
2841version = "0.38.0"
2842source = "registry+https://github.com/rust-lang/crates.io-index"
2843checksum = "a2cd7f054ae2727223fe46dd39c012f066b12f532962d336d29ee193261787da"
2844dependencies = [
2845 "bstr",
2846 "gix-chunk",
2847 "gix-error",
2848 "gix-hash",
2849 "memmap2",
2850 "nonempty",
2851 "serde",
2852]
2853
2854[[package]]
2855name = "gix-config"
2856version = "0.59.0"
2857source = "registry+https://github.com/rust-lang/crates.io-index"
2858checksum = "103d11bef95c467577ecfa8b7b86a22e65af3507b2c9bfa3809a4afbae7df301"
2859dependencies = [
2860 "bstr",
2861 "gix-config-value",
2862 "gix-features",
2863 "gix-glob",
2864 "gix-path",
2865 "gix-ref",
2866 "gix-sec",
2867 "gix-utils",
2868 "smallvec",
2869 "thiserror 2.0.19",
2870 "unicode-bom",
2871]
2872
2873[[package]]
2874name = "gix-config-value"
2875version = "0.19.1"
2876source = "registry+https://github.com/rust-lang/crates.io-index"
2877checksum = "6f6af5321bfd3711a279d6b244d58532ba1cfabf9eb6374791f19929d8970082"
2878dependencies = [
2879 "bitflags 2.13.1",
2880 "bstr",
2881 "gix-path",
2882 "libc",
2883 "thiserror 2.0.19",
2884]
2885
2886[[package]]
2887name = "gix-credentials"
2888version = "0.39.1"
2889source = "registry+https://github.com/rust-lang/crates.io-index"
2890checksum = "f9fbdb1c417980d05d547c19fc2b63344b5257c2387048d69ffe957bda142b59"
2891dependencies = [
2892 "bstr",
2893 "gix-command",
2894 "gix-config-value",
2895 "gix-date",
2896 "gix-path",
2897 "gix-prompt",
2898 "gix-quote",
2899 "gix-sec",
2900 "gix-trace",
2901 "gix-url",
2902 "serde",
2903 "thiserror 2.0.19",
2904]
2905
2906[[package]]
2907name = "gix-date"
2908version = "0.15.6"
2909source = "registry+https://github.com/rust-lang/crates.io-index"
2910checksum = "7e47b9e8cdc688296609b706428de570f88b1e0eed7156dde7b4a89d26fa4567"
2911dependencies = [
2912 "bstr",
2913 "gix-error",
2914 "itoa",
2915 "jiff",
2916 "serde",
2917]
2918
2919[[package]]
2920name = "gix-diff"
2921version = "0.66.0"
2922source = "registry+https://github.com/rust-lang/crates.io-index"
2923checksum = "fee7d89a3c507491cdfc57a1d1e0e300214720b4f7709ebc253e422f99822bfc"
2924dependencies = [
2925 "bstr",
2926 "gix-attributes",
2927 "gix-command",
2928 "gix-filter",
2929 "gix-fs",
2930 "gix-hash",
2931 "gix-imara-diff",
2932 "gix-index",
2933 "gix-object",
2934 "gix-path",
2935 "gix-pathspec",
2936 "gix-tempfile",
2937 "gix-trace",
2938 "gix-traverse",
2939 "gix-worktree",
2940 "thiserror 2.0.19",
2941]
2942
2943[[package]]
2944name = "gix-dir"
2945version = "0.28.0"
2946source = "registry+https://github.com/rust-lang/crates.io-index"
2947checksum = "f24bc78f283946ac64757c8a61ffa71f0230aa1e8d98cbb0771db479871dd5b6"
2948dependencies = [
2949 "bstr",
2950 "gix-discover",
2951 "gix-fs",
2952 "gix-ignore",
2953 "gix-index",
2954 "gix-object",
2955 "gix-path",
2956 "gix-pathspec",
2957 "gix-trace",
2958 "gix-utils",
2959 "gix-worktree",
2960 "thiserror 2.0.19",
2961]
2962
2963[[package]]
2964name = "gix-discover"
2965version = "0.54.0"
2966source = "registry+https://github.com/rust-lang/crates.io-index"
2967checksum = "b9f517766fa1101dfe2606c1a19a8ffa699099030995a9194445446dfe261bdf"
2968dependencies = [
2969 "bstr",
2970 "dunce",
2971 "gix-fs",
2972 "gix-path",
2973 "gix-ref",
2974 "gix-sec",
2975 "thiserror 2.0.19",
2976]
2977
2978[[package]]
2979name = "gix-error"
2980version = "0.2.5"
2981source = "registry+https://github.com/rust-lang/crates.io-index"
2982checksum = "4a9292309fd944e71b2a3c96d3c03a6feb8852db646febdde7cbb9f79cb5f329"
2983dependencies = [
2984 "bstr",
2985]
2986
2987[[package]]
2988name = "gix-features"
2989version = "0.49.0"
2990source = "registry+https://github.com/rust-lang/crates.io-index"
2991checksum = "20aa09e83a48dc02c5f5f08578aa79d3ab1bab4618b8c362f88684645a02bdcc"
2992dependencies = [
2993 "bytes",
2994 "bytesize",
2995 "crc32fast",
2996 "crossbeam-channel",
2997 "gix-path",
2998 "gix-trace",
2999 "gix-utils",
3000 "libc",
3001 "once_cell",
3002 "parking_lot",
3003 "prodash",
3004 "walkdir",
3005]
3006
3007[[package]]
3008name = "gix-filter"
3009version = "0.33.0"
3010source = "registry+https://github.com/rust-lang/crates.io-index"
3011checksum = "5e7b5dbf524d97e839f642930c76d7f011c0791e7d11d8148989ac5af7c76aa8"
3012dependencies = [
3013 "bstr",
3014 "encoding_rs",
3015 "gix-attributes",
3016 "gix-command",
3017 "gix-hash",
3018 "gix-object",
3019 "gix-packetline",
3020 "gix-path",
3021 "gix-quote",
3022 "gix-trace",
3023 "gix-utils",
3024 "smallvec",
3025 "thiserror 2.0.19",
3026]
3027
3028[[package]]
3029name = "gix-fs"
3030version = "0.22.0"
3031source = "registry+https://github.com/rust-lang/crates.io-index"
3032checksum = "865cf13fcaf5455220546cb9607c416bd1be9a6caafd143655a362fdeab64e80"
3033dependencies = [
3034 "bstr",
3035 "gix-features",
3036 "gix-path",
3037 "gix-utils",
3038 "thiserror 2.0.19",
3039]
3040
3041[[package]]
3042name = "gix-glob"
3043version = "0.27.0"
3044source = "registry+https://github.com/rust-lang/crates.io-index"
3045checksum = "421e92a711554fa5827d1b0599d3389acdd0f6729e97a8c5a57d79af1e50bf36"
3046dependencies = [
3047 "bitflags 2.13.1",
3048 "bstr",
3049 "gix-features",
3050 "gix-path",
3051 "serde",
3052]
3053
3054[[package]]
3055name = "gix-hash"
3056version = "0.26.0"
3057source = "registry+https://github.com/rust-lang/crates.io-index"
3058checksum = "13adaa73415fd6c902310923f68d0b98e8cecf14b33ea58c02cc387cee56f54e"
3059dependencies = [
3060 "faster-hex",
3061 "gix-features",
3062 "serde",
3063 "sha1-checked",
3064 "sha2 0.11.0",
3065 "thiserror 2.0.19",
3066]
3067
3068[[package]]
3069name = "gix-hashtable"
3070version = "0.16.0"
3071source = "registry+https://github.com/rust-lang/crates.io-index"
3072checksum = "78fccd6fea3bcf0b39c076bae60ae49b08daaf538b950202101a981f9d3c01d3"
3073dependencies = [
3074 "gix-hash",
3075 "hashbrown 0.17.1",
3076 "parking_lot",
3077]
3078
3079[[package]]
3080name = "gix-ignore"
3081version = "0.22.0"
3082source = "registry+https://github.com/rust-lang/crates.io-index"
3083checksum = "12cff8e8aa125e39377456073e63df3334d9e5741372ddcc226198015076dda2"
3084dependencies = [
3085 "bstr",
3086 "gix-glob",
3087 "gix-path",
3088 "gix-trace",
3089 "serde",
3090 "unicode-bom",
3091]
3092
3093[[package]]
3094name = "gix-imara-diff"
3095version = "0.2.4"
3096source = "registry+https://github.com/rust-lang/crates.io-index"
3097checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1"
3098dependencies = [
3099 "bstr",
3100 "hashbrown 0.17.1",
3101]
3102
3103[[package]]
3104name = "gix-index"
3105version = "0.54.0"
3106source = "registry+https://github.com/rust-lang/crates.io-index"
3107checksum = "5009c4e7e9f9b4cfaaab1153e49133eb04d79c015b5702d6c3d2ab94271a89c6"
3108dependencies = [
3109 "bitflags 2.13.1",
3110 "bstr",
3111 "filetime",
3112 "fnv",
3113 "gix-bitmap",
3114 "gix-features",
3115 "gix-fs",
3116 "gix-hash",
3117 "gix-lock",
3118 "gix-object",
3119 "gix-traverse",
3120 "gix-utils",
3121 "gix-validate",
3122 "hashbrown 0.17.1",
3123 "itoa",
3124 "libc",
3125 "memmap2",
3126 "rustix",
3127 "serde",
3128 "smallvec",
3129 "thiserror 2.0.19",
3130]
3131
3132[[package]]
3133name = "gix-lock"
3134version = "24.0.0"
3135source = "registry+https://github.com/rust-lang/crates.io-index"
3136checksum = "d4c69157820343bf1c6e4b88b9808e920900de02e18aaf5862b30ada43814848"
3137dependencies = [
3138 "gix-tempfile",
3139 "gix-utils",
3140 "thiserror 2.0.19",
3141]
3142
3143[[package]]
3144name = "gix-mailmap"
3145version = "0.33.2"
3146source = "registry+https://github.com/rust-lang/crates.io-index"
3147checksum = "a824767d38b81475059cb01f5020a13fb96e7ed6bbf9851c7112b46ada78db48"
3148dependencies = [
3149 "bstr",
3150 "gix-actor",
3151 "gix-date",
3152 "gix-error",
3153 "serde",
3154]
3155
3156[[package]]
3157name = "gix-negotiate"
3158version = "0.34.0"
3159source = "registry+https://github.com/rust-lang/crates.io-index"
3160checksum = "2fa5aa789990f124e4f559b142cecfb60662cb4ae17006684ea9d668f4f07689"
3161dependencies = [
3162 "bitflags 2.13.1",
3163 "gix-commitgraph",
3164 "gix-date",
3165 "gix-hash",
3166 "gix-object",
3167 "gix-revwalk",
3168]
3169
3170[[package]]
3171name = "gix-object"
3172version = "0.63.0"
3173source = "registry+https://github.com/rust-lang/crates.io-index"
3174checksum = "0e48c235e7f886eb819fc878af75be889333dd3c38bee02ed7af48ae2cf596c4"
3175dependencies = [
3176 "bstr",
3177 "gix-actor",
3178 "gix-date",
3179 "gix-features",
3180 "gix-hash",
3181 "gix-hashtable",
3182 "gix-utils",
3183 "gix-validate",
3184 "itoa",
3185 "serde",
3186 "smallvec",
3187 "thiserror 2.0.19",
3188]
3189
3190[[package]]
3191name = "gix-odb"
3192version = "0.83.0"
3193source = "registry+https://github.com/rust-lang/crates.io-index"
3194checksum = "8dd494ffb5037e62b8220109e894d2861ff2150a2cacbfccdba57ae1ebab2b96"
3195dependencies = [
3196 "arc-swap",
3197 "gix-features",
3198 "gix-fs",
3199 "gix-hash",
3200 "gix-hashtable",
3201 "gix-object",
3202 "gix-pack",
3203 "gix-path",
3204 "gix-quote",
3205 "gix-zlib",
3206 "memmap2",
3207 "parking_lot",
3208 "serde",
3209 "tempfile",
3210 "thiserror 2.0.19",
3211]
3212
3213[[package]]
3214name = "gix-pack"
3215version = "0.73.0"
3216source = "registry+https://github.com/rust-lang/crates.io-index"
3217checksum = "6d5446127b269706e85998065267ddd2ccc3550179da6780b22fe496175ccb20"
3218dependencies = [
3219 "clru",
3220 "gix-chunk",
3221 "gix-error",
3222 "gix-features",
3223 "gix-hash",
3224 "gix-hashtable",
3225 "gix-object",
3226 "gix-path",
3227 "gix-tempfile",
3228 "gix-zlib",
3229 "memmap2",
3230 "parking_lot",
3231 "serde",
3232 "smallvec",
3233 "thiserror 2.0.19",
3234 "uluru",
3235]
3236
3237[[package]]
3238name = "gix-packetline"
3239version = "0.22.0"
3240source = "registry+https://github.com/rust-lang/crates.io-index"
3241checksum = "3766025c72319c4accdd854a18e6f0dd176c8eb0f3bc8a60a7765be2b50cabf2"
3242dependencies = [
3243 "bstr",
3244 "faster-hex",
3245 "gix-trace",
3246 "thiserror 2.0.19",
3247]
3248
3249[[package]]
3250name = "gix-path"
3251version = "0.12.4"
3252source = "registry+https://github.com/rust-lang/crates.io-index"
3253checksum = "751d6bd162106f8c1e7e9aaccb5bbdd605267e91a930a17a4560c46e33a9100c"
3254dependencies = [
3255 "bstr",
3256 "gix-trace",
3257 "gix-validate",
3258 "thiserror 2.0.19",
3259]
3260
3261[[package]]
3262name = "gix-pathspec"
3263version = "0.19.0"
3264source = "registry+https://github.com/rust-lang/crates.io-index"
3265checksum = "49f6fa5f8007f008187c3f60b4373209ca83d1cc947f35ede03e16cd15a4d137"
3266dependencies = [
3267 "bitflags 2.13.1",
3268 "bstr",
3269 "gix-attributes",
3270 "gix-config-value",
3271 "gix-glob",
3272 "gix-path",
3273 "thiserror 2.0.19",
3274]
3275
3276[[package]]
3277name = "gix-prompt"
3278version = "0.16.0"
3279source = "registry+https://github.com/rust-lang/crates.io-index"
3280checksum = "5cb1f1eb92d6f4c9d4c105a6ca912cff637fbd5cacbcbafa5deccd88bfaa3565"
3281dependencies = [
3282 "gix-command",
3283 "gix-config-value",
3284 "parking_lot",
3285 "rustix",
3286 "thiserror 2.0.19",
3287]
3288
3289[[package]]
3290name = "gix-protocol"
3291version = "0.64.0"
3292source = "registry+https://github.com/rust-lang/crates.io-index"
3293checksum = "dede40e89c1e90f548415f50636bb051f6d9c60f68b8b710bc07825722d19588"
3294dependencies = [
3295 "bisync",
3296 "bstr",
3297 "gix-credentials",
3298 "gix-date",
3299 "gix-features",
3300 "gix-hash",
3301 "gix-lock",
3302 "gix-negotiate",
3303 "gix-object",
3304 "gix-ref",
3305 "gix-refspec",
3306 "gix-revwalk",
3307 "gix-shallow",
3308 "gix-trace",
3309 "gix-transport",
3310 "gix-utils",
3311 "nonempty",
3312 "serde",
3313 "thiserror 2.0.19",
3314]
3315
3316[[package]]
3317name = "gix-quote"
3318version = "0.7.2"
3319source = "registry+https://github.com/rust-lang/crates.io-index"
3320checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef"
3321dependencies = [
3322 "bstr",
3323 "gix-error",
3324 "gix-utils",
3325]
3326
3327[[package]]
3328name = "gix-ref"
3329version = "0.66.0"
3330source = "registry+https://github.com/rust-lang/crates.io-index"
3331checksum = "eeb0c90a8f6202ceaaa22996cbf837c943ccb2d8af9ff3490f0758305e6b7883"
3332dependencies = [
3333 "gix-actor",
3334 "gix-features",
3335 "gix-fs",
3336 "gix-hash",
3337 "gix-lock",
3338 "gix-object",
3339 "gix-path",
3340 "gix-tempfile",
3341 "gix-utils",
3342 "gix-validate",
3343 "memmap2",
3344 "serde",
3345 "thiserror 2.0.19",
3346]
3347
3348[[package]]
3349name = "gix-refspec"
3350version = "0.44.0"
3351source = "registry+https://github.com/rust-lang/crates.io-index"
3352checksum = "7406282cc0259b51f6aee299ca3d31279a020530363152a2e6c96e8a7f7bbc83"
3353dependencies = [
3354 "bstr",
3355 "gix-error",
3356 "gix-glob",
3357 "gix-hash",
3358 "gix-revision",
3359 "gix-validate",
3360 "smallvec",
3361 "thiserror 2.0.19",
3362]
3363
3364[[package]]
3365name = "gix-revision"
3366version = "0.48.0"
3367source = "registry+https://github.com/rust-lang/crates.io-index"
3368checksum = "e55e09d4a1ecf2beecc8c09cafcad37979e805b31f588b0e957e191df5783681"
3369dependencies = [
3370 "bitflags 2.13.1",
3371 "bstr",
3372 "gix-commitgraph",
3373 "gix-date",
3374 "gix-error",
3375 "gix-hash",
3376 "gix-hashtable",
3377 "gix-object",
3378 "gix-revwalk",
3379 "gix-trace",
3380 "nonempty",
3381 "serde",
3382]
3383
3384[[package]]
3385name = "gix-revwalk"
3386version = "0.34.0"
3387source = "registry+https://github.com/rust-lang/crates.io-index"
3388checksum = "36c113c0a53294dc6280ffc06cbcc4f50f820397e97d6a00b429a44b8db26e29"
3389dependencies = [
3390 "gix-commitgraph",
3391 "gix-date",
3392 "gix-error",
3393 "gix-hash",
3394 "gix-hashtable",
3395 "gix-object",
3396 "smallvec",
3397 "thiserror 2.0.19",
3398]
3399
3400[[package]]
3401name = "gix-sec"
3402version = "0.14.2"
3403source = "registry+https://github.com/rust-lang/crates.io-index"
3404checksum = "af4fe6c152c1d50aea36f299825702cd37e303307832fec1d0fdd5844e47ce2f"
3405dependencies = [
3406 "bitflags 2.13.1",
3407 "gix-path",
3408 "libc",
3409 "serde",
3410 "windows-sys 0.61.2",
3411]
3412
3413[[package]]
3414name = "gix-shallow"
3415version = "0.13.0"
3416source = "registry+https://github.com/rust-lang/crates.io-index"
3417checksum = "1ecc9f4b40537043e4bbd7d3d1760e74fb8e7b07a546166b558acaa73ad97f4a"
3418dependencies = [
3419 "bstr",
3420 "gix-hash",
3421 "gix-lock",
3422 "nonempty",
3423 "serde",
3424 "thiserror 2.0.19",
3425]
3426
3427[[package]]
3428name = "gix-status"
3429version = "0.33.0"
3430source = "registry+https://github.com/rust-lang/crates.io-index"
3431checksum = "5f83b1c74e69b90411fbfe89ccebc47aa49457ce4eb9b942b1311258fc863d22"
3432dependencies = [
3433 "bstr",
3434 "filetime",
3435 "gix-diff",
3436 "gix-dir",
3437 "gix-features",
3438 "gix-filter",
3439 "gix-fs",
3440 "gix-hash",
3441 "gix-index",
3442 "gix-object",
3443 "gix-path",
3444 "gix-pathspec",
3445 "gix-worktree",
3446 "hashbrown 0.16.1",
3447 "portable-atomic",
3448 "thiserror 2.0.19",
3449 "windows-sys 0.61.2",
3450]
3451
3452[[package]]
3453name = "gix-submodule"
3454version = "0.33.0"
3455source = "registry+https://github.com/rust-lang/crates.io-index"
3456checksum = "5fd98077a56d08886112e6b08dc94076d03539f4bc0b9d7880e4be2b8a640d8c"
3457dependencies = [
3458 "bstr",
3459 "gix-config",
3460 "gix-path",
3461 "gix-pathspec",
3462 "gix-refspec",
3463 "gix-url",
3464 "thiserror 2.0.19",
3465]
3466
3467[[package]]
3468name = "gix-tempfile"
3469version = "24.0.0"
3470source = "registry+https://github.com/rust-lang/crates.io-index"
3471checksum = "b675b920bd5a61d17ad542772f03ec34c60feb8ff683e1560c03ae967363731e"
3472dependencies = [
3473 "dashmap",
3474 "gix-fs",
3475 "libc",
3476 "parking_lot",
3477 "signal-hook 0.4.4",
3478 "signal-hook-registry",
3479 "tempfile",
3480]
3481
3482[[package]]
3483name = "gix-trace"
3484version = "0.1.21"
3485source = "registry+https://github.com/rust-lang/crates.io-index"
3486checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814"
3487
3488[[package]]
3489name = "gix-transport"
3490version = "0.58.1"
3491source = "registry+https://github.com/rust-lang/crates.io-index"
3492checksum = "3f36d045b840f8aeee1a527e677eab1fbebfbbe94bf2e708fa81d0b4b742d5fc"
3493dependencies = [
3494 "bstr",
3495 "gix-command",
3496 "gix-features",
3497 "gix-packetline",
3498 "gix-path",
3499 "gix-quote",
3500 "gix-sec",
3501 "gix-url",
3502 "serde",
3503 "thiserror 2.0.19",
3504]
3505
3506[[package]]
3507name = "gix-traverse"
3508version = "0.60.0"
3509source = "registry+https://github.com/rust-lang/crates.io-index"
3510checksum = "008c5cd879e46e86b5c2469e633611978b18775d53d05668d691bc13088bd409"
3511dependencies = [
3512 "bitflags 2.13.1",
3513 "gix-commitgraph",
3514 "gix-date",
3515 "gix-hash",
3516 "gix-hashtable",
3517 "gix-object",
3518 "gix-revwalk",
3519 "smallvec",
3520 "thiserror 2.0.19",
3521]
3522
3523[[package]]
3524name = "gix-url"
3525version = "0.37.1"
3526source = "registry+https://github.com/rust-lang/crates.io-index"
3527checksum = "31bdfc93aa880cda3272718a5879ce3aa7723fa13514320dd6608151607afe72"
3528dependencies = [
3529 "bstr",
3530 "gix-path",
3531 "gix-utils",
3532 "percent-encoding",
3533 "serde",
3534 "thiserror 2.0.19",
3535]
3536
3537[[package]]
3538name = "gix-utils"
3539version = "0.3.5"
3540source = "registry+https://github.com/rust-lang/crates.io-index"
3541checksum = "b1795bd2a970ca8b2185318c2abb97d955c71992f1cf28de73ad3b593a9f3ce8"
3542dependencies = [
3543 "bstr",
3544 "fastrand",
3545 "getrandom 0.4.3",
3546 "unicode-normalization",
3547]
3548
3549[[package]]
3550name = "gix-validate"
3551version = "0.11.3"
3552source = "registry+https://github.com/rust-lang/crates.io-index"
3553checksum = "9a034e84d1e04e1b1f20f51f12491da230b6ac8b925d0c8e1b89bcd87a7c5ccc"
3554dependencies = [
3555 "bstr",
3556]
3557
3558[[package]]
3559name = "gix-worktree"
3560version = "0.55.0"
3561source = "registry+https://github.com/rust-lang/crates.io-index"
3562checksum = "31eb8e675122e83585e461fe28f68ff8c5ed55b49017b697e7e76423ff973424"
3563dependencies = [
3564 "bstr",
3565 "gix-attributes",
3566 "gix-features",
3567 "gix-fs",
3568 "gix-glob",
3569 "gix-hash",
3570 "gix-ignore",
3571 "gix-index",
3572 "gix-object",
3573 "gix-path",
3574 "gix-validate",
3575 "serde",
3576]
3577
3578[[package]]
3579name = "gix-worktree-state"
3580version = "0.33.0"
3581source = "registry+https://github.com/rust-lang/crates.io-index"
3582checksum = "bc47372fc12b9fbea51b257bcc8d4970326cf5c7e42135bbfb5d72871bb30bd4"
3583dependencies = [
3584 "bstr",
3585 "gix-features",
3586 "gix-filter",
3587 "gix-fs",
3588 "gix-index",
3589 "gix-object",
3590 "gix-path",
3591 "gix-worktree",
3592 "io-close",
3593 "thiserror 2.0.19",
3594]
3595
3596[[package]]
3597name = "gix-worktree-stream"
3598version = "0.35.0"
3599source = "registry+https://github.com/rust-lang/crates.io-index"
3600checksum = "3b088c8724e7be120c4798dd86925cf05332c9d356a463542578600c50c7a549"
3601dependencies = [
3602 "gix-attributes",
3603 "gix-error",
3604 "gix-features",
3605 "gix-filter",
3606 "gix-fs",
3607 "gix-hash",
3608 "gix-object",
3609 "gix-path",
3610 "gix-traverse",
3611 "parking_lot",
3612]
3613
3614[[package]]
3615name = "gix-zlib"
3616version = "0.1.0"
3617source = "registry+https://github.com/rust-lang/crates.io-index"
3618checksum = "3e8813f5579b3075ff9c90f7c59cd2b62b4ebb639361f0911648b22d7446cc7c"
3619dependencies = [
3620 "serde",
3621 "thiserror 2.0.19",
3622 "zlib-rs",
3623]
3624
3625[[package]]
3626name = "glayer"
3627version = "1.94.0"
3628dependencies = [
3629 "axum",
3630 "bytes",
3631 "chatlayer",
3632 "config 1.94.0",
3633 "export",
3634 "metrics",
3635 "queue",
3636 "serde",
3637 "serde_json",
3638 "storagelayer",
3639 "thiserror 2.0.19",
3640 "time",
3641 "tokio",
3642 "tokio-util",
3643 "tracing",
3644 "uuid",
3645]
3646
3647[[package]]
3648name = "group"
3649version = "0.13.0"
3650source = "registry+https://github.com/rust-lang/crates.io-index"
3651checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
3652dependencies = [
3653 "ff 0.13.1",
3654 "rand_core 0.6.4",
3655 "subtle",
3656]
3657
3658[[package]]
3659name = "group"
3660version = "0.14.0"
3661source = "registry+https://github.com/rust-lang/crates.io-index"
3662checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
3663dependencies = [
3664 "ff 0.14.0",
3665 "rand_core 0.10.1",
3666 "subtle",
3667]
3668
3669[[package]]
3670name = "h2"
3671version = "0.4.15"
3672source = "registry+https://github.com/rust-lang/crates.io-index"
3673checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
3674dependencies = [
3675 "atomic-waker",
3676 "bytes",
3677 "fnv",
3678 "futures-core",
3679 "futures-sink",
3680 "http",
3681 "indexmap",
3682 "slab",
3683 "tokio",
3684 "tokio-util",
3685 "tracing",
3686]
3687
3688[[package]]
3689name = "half"
3690version = "2.7.1"
3691source = "registry+https://github.com/rust-lang/crates.io-index"
3692checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
3693dependencies = [
3694 "cfg-if",
3695 "crunchy",
3696 "num-traits",
3697 "zerocopy",
3698]
3699
3700[[package]]
3701name = "handlebars"
3702version = "6.4.3"
3703source = "registry+https://github.com/rust-lang/crates.io-index"
3704checksum = "4633d16a2350341713c379d6d06a4b9e1845329386026a49ce4fd09c2f3b16f6"
3705dependencies = [
3706 "derive_builder",
3707 "log",
3708 "num-order",
3709 "pest",
3710 "pest_derive",
3711 "serde",
3712 "serde_json",
3713 "thiserror 2.0.19",
3714]
3715
3716[[package]]
3717name = "hash32"
3718version = "0.3.1"
3719source = "registry+https://github.com/rust-lang/crates.io-index"
3720checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
3721dependencies = [
3722 "byteorder",
3723]
3724
3725[[package]]
3726name = "hashbrown"
3727version = "0.12.3"
3728source = "registry+https://github.com/rust-lang/crates.io-index"
3729checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
3730dependencies = [
3731 "ahash 0.7.8",
3732]
3733
3734[[package]]
3735name = "hashbrown"
3736version = "0.14.5"
3737source = "registry+https://github.com/rust-lang/crates.io-index"
3738checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
3739
3740[[package]]
3741name = "hashbrown"
3742version = "0.15.5"
3743source = "registry+https://github.com/rust-lang/crates.io-index"
3744checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
3745dependencies = [
3746 "foldhash 0.1.5",
3747]
3748
3749[[package]]
3750name = "hashbrown"
3751version = "0.16.1"
3752source = "registry+https://github.com/rust-lang/crates.io-index"
3753checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
3754dependencies = [
3755 "allocator-api2",
3756 "equivalent",
3757 "foldhash 0.2.0",
3758]
3759
3760[[package]]
3761name = "hashbrown"
3762version = "0.17.1"
3763source = "registry+https://github.com/rust-lang/crates.io-index"
3764checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
3765dependencies = [
3766 "allocator-api2",
3767 "equivalent",
3768 "foldhash 0.2.0",
3769]
3770
3771[[package]]
3772name = "hashlink"
3773version = "0.11.1"
3774source = "registry+https://github.com/rust-lang/crates.io-index"
3775checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
3776dependencies = [
3777 "hashbrown 0.16.1",
3778]
3779
3780[[package]]
3781name = "heapless"
3782version = "0.8.0"
3783source = "registry+https://github.com/rust-lang/crates.io-index"
3784checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
3785dependencies = [
3786 "hash32",
3787 "stable_deref_trait",
3788]
3789
3790[[package]]
3791name = "heck"
3792version = "0.4.1"
3793source = "registry+https://github.com/rust-lang/crates.io-index"
3794checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
3795
3796[[package]]
3797name = "heck"
3798version = "0.5.0"
3799source = "registry+https://github.com/rust-lang/crates.io-index"
3800checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
3801
3802[[package]]
3803name = "hermit-abi"
3804version = "0.5.2"
3805source = "registry+https://github.com/rust-lang/crates.io-index"
3806checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
3807
3808[[package]]
3809name = "hex"
3810version = "0.4.3"
3811source = "registry+https://github.com/rust-lang/crates.io-index"
3812checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
3813
3814[[package]]
3815name = "hex-literal"
3816version = "1.1.0"
3817source = "registry+https://github.com/rust-lang/crates.io-index"
3818checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
3819
3820[[package]]
3821name = "hkdf"
3822version = "0.12.4"
3823source = "registry+https://github.com/rust-lang/crates.io-index"
3824checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
3825dependencies = [
3826 "hmac 0.12.1",
3827]
3828
3829[[package]]
3830name = "hkdf"
3831version = "0.13.0"
3832source = "registry+https://github.com/rust-lang/crates.io-index"
3833checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
3834dependencies = [
3835 "hmac 0.13.0",
3836]
3837
3838[[package]]
3839name = "hmac"
3840version = "0.12.1"
3841source = "registry+https://github.com/rust-lang/crates.io-index"
3842checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
3843dependencies = [
3844 "digest 0.10.7",
3845]
3846
3847[[package]]
3848name = "hmac"
3849version = "0.13.0"
3850source = "registry+https://github.com/rust-lang/crates.io-index"
3851checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
3852dependencies = [
3853 "digest 0.11.3",
3854]
3855
3856[[package]]
3857name = "http"
3858version = "1.5.0"
3859source = "registry+https://github.com/rust-lang/crates.io-index"
3860checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
3861dependencies = [
3862 "bytes",
3863 "itoa",
3864]
3865
3866[[package]]
3867name = "http-body"
3868version = "1.1.0"
3869source = "registry+https://github.com/rust-lang/crates.io-index"
3870checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
3871dependencies = [
3872 "bytes",
3873 "http",
3874]
3875
3876[[package]]
3877name = "http-body-util"
3878version = "0.1.4"
3879source = "registry+https://github.com/rust-lang/crates.io-index"
3880checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
3881dependencies = [
3882 "bytes",
3883 "futures-core",
3884 "http",
3885 "http-body",
3886 "pin-project-lite",
3887]
3888
3889[[package]]
3890name = "httparse"
3891version = "1.10.1"
3892source = "registry+https://github.com/rust-lang/crates.io-index"
3893checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
3894
3895[[package]]
3896name = "httpdate"
3897version = "1.0.3"
3898source = "registry+https://github.com/rust-lang/crates.io-index"
3899checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
3900
3901[[package]]
3902name = "human_format"
3903version = "1.2.1"
3904source = "registry+https://github.com/rust-lang/crates.io-index"
3905checksum = "eaec953f16e5bcf6b8a3cb3aa959b17e5577dbd2693e94554c462c08be22624b"
3906
3907[[package]]
3908name = "humantime"
3909version = "2.4.0"
3910source = "registry+https://github.com/rust-lang/crates.io-index"
3911checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"
3912
3913[[package]]
3914name = "hybrid-array"
3915version = "0.4.14"
3916source = "registry+https://github.com/rust-lang/crates.io-index"
3917checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
3918dependencies = [
3919 "ctutils",
3920 "subtle",
3921 "typenum",
3922 "zeroize",
3923]
3924
3925[[package]]
3926name = "hyper"
3927version = "1.11.0"
3928source = "registry+https://github.com/rust-lang/crates.io-index"
3929checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
3930dependencies = [
3931 "atomic-waker",
3932 "bytes",
3933 "futures-channel",
3934 "futures-core",
3935 "h2",
3936 "http",
3937 "http-body",
3938 "httparse",
3939 "httpdate",
3940 "itoa",
3941 "pin-project-lite",
3942 "smallvec",
3943 "tokio",
3944 "want",
3945]
3946
3947[[package]]
3948name = "hyper-rustls"
3949version = "0.27.9"
3950source = "registry+https://github.com/rust-lang/crates.io-index"
3951checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
3952dependencies = [
3953 "http",
3954 "hyper",
3955 "hyper-util",
3956 "rustls",
3957 "tokio",
3958 "tokio-rustls",
3959 "tower-service",
3960 "webpki-roots",
3961]
3962
3963[[package]]
3964name = "hyper-timeout"
3965version = "0.5.2"
3966source = "registry+https://github.com/rust-lang/crates.io-index"
3967checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
3968dependencies = [
3969 "hyper",
3970 "hyper-util",
3971 "pin-project-lite",
3972 "tokio",
3973 "tower-service",
3974]
3975
3976[[package]]
3977name = "hyper-util"
3978version = "0.1.20"
3979source = "registry+https://github.com/rust-lang/crates.io-index"
3980checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
3981dependencies = [
3982 "base64 0.22.1",
3983 "bytes",
3984 "futures-channel",
3985 "futures-util",
3986 "http",
3987 "http-body",
3988 "hyper",
3989 "ipnet",
3990 "libc",
3991 "percent-encoding",
3992 "pin-project-lite",
3993 "socket2",
3994 "tokio",
3995 "tower-service",
3996 "tracing",
3997]
3998
3999[[package]]
4000name = "iana-time-zone"
4001version = "0.1.65"
4002source = "registry+https://github.com/rust-lang/crates.io-index"
4003checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
4004dependencies = [
4005 "android_system_properties",
4006 "core-foundation-sys",
4007 "iana-time-zone-haiku",
4008 "js-sys",
4009 "log",
4010 "wasm-bindgen",
4011 "windows-core",
4012]
4013
4014[[package]]
4015name = "iana-time-zone-haiku"
4016version = "0.1.2"
4017source = "registry+https://github.com/rust-lang/crates.io-index"
4018checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
4019dependencies = [
4020 "cc",
4021]
4022
4023[[package]]
4024name = "icu_collections"
4025version = "2.2.0"
4026source = "registry+https://github.com/rust-lang/crates.io-index"
4027checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
4028dependencies = [
4029 "displaydoc",
4030 "potential_utf",
4031 "utf8_iter",
4032 "yoke",
4033 "zerofrom",
4034 "zerovec",
4035]
4036
4037[[package]]
4038name = "icu_locale_core"
4039version = "2.2.0"
4040source = "registry+https://github.com/rust-lang/crates.io-index"
4041checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
4042dependencies = [
4043 "displaydoc",
4044 "litemap",
4045 "tinystr",
4046 "writeable",
4047 "zerovec",
4048]
4049
4050[[package]]
4051name = "icu_normalizer"
4052version = "2.2.0"
4053source = "registry+https://github.com/rust-lang/crates.io-index"
4054checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
4055dependencies = [
1856 "icu_collator_data",
40561857 "icu_collections",
4057 "icu_normalizer_data",
1858 "icu_locale_core",
1859 "icu_locale_fallback",
1860 "icu_normalizer",
40581861 "icu_properties",
40591862 "icu_provider",
40601863 "smallvec",
1864 "utf16_iter",
1865 "utf8_iter",
40611866 "zerovec",
40621867]
40631868
40641869[[package]]
4065name = "icu_normalizer_data"
4066version = "2.2.0"
1870name = "icu_collator_data"
1871version = "2.3.0"
40671872source = "registry+https://github.com/rust-lang/crates.io-index"
4068checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
4069
4070[[package]]
4071name = "icu_properties"
4072version = "2.2.0"
4073source = "registry+https://github.com/rust-lang/crates.io-index"
4074checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
4075dependencies = [
4076 "icu_collections",
4077 "icu_locale_core",
4078 "icu_properties_data",
4079 "icu_provider",
4080 "zerotrie",
4081 "zerovec",
4082]
4083
4084[[package]]
4085name = "icu_properties_data"
4086version = "2.2.0"
4087source = "registry+https://github.com/rust-lang/crates.io-index"
4088checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
4089
4090[[package]]
4091name = "icu_provider"
4092version = "2.2.0"
4093source = "registry+https://github.com/rust-lang/crates.io-index"
4094checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
4095dependencies = [
4096 "displaydoc",
4097 "icu_locale_core",
4098 "writeable",
4099 "yoke",
4100 "zerofrom",
4101 "zerotrie",
4102 "zerovec",
4103]
4104
4105[[package]]
4106name = "ident_case"
4107version = "1.0.1"
4108source = "registry+https://github.com/rust-lang/crates.io-index"
4109checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
4110
4111[[package]]
4112name = "idna"
4113version = "1.1.0"
4114source = "registry+https://github.com/rust-lang/crates.io-index"
4115checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
4116dependencies = [
4117 "idna_adapter",
4118 "smallvec",
4119 "utf8_iter",
4120]
4121
4122[[package]]
4123name = "idna_adapter"
4124version = "1.2.2"
4125source = "registry+https://github.com/rust-lang/crates.io-index"
4126checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
4127dependencies = [
4128 "icu_normalizer",
4129 "icu_properties",
4130]
4131
4132[[package]]
4133name = "indexmap"
4134version = "2.14.0"
4135source = "registry+https://github.com/rust-lang/crates.io-index"
4136checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
4137dependencies = [
4138 "equivalent",
4139 "hashbrown 0.17.1",
4140 "serde",
4141 "serde_core",
4142]
4143
4144[[package]]
4145name = "inout"
4146version = "0.2.2"
4147source = "registry+https://github.com/rust-lang/crates.io-index"
4148checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
4149dependencies = [
4150 "block-padding",
4151 "hybrid-array",
4152]
4153
4154[[package]]
4155name = "internal-russh-num-bigint"
4156version = "0.5.0"
4157source = "registry+https://github.com/rust-lang/crates.io-index"
4158checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
4159dependencies = [
4160 "num-integer",
4161 "num-traits",
4162 "rand 0.10.2",
4163 "rand_core 0.10.1",
4164]
4165
4166[[package]]
4167name = "inventory"
4168version = "0.3.24"
4169source = "registry+https://github.com/rust-lang/crates.io-index"
4170checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
4171dependencies = [
4172 "rustversion",
4173]
4174
4175[[package]]
4176name = "io-close"
4177version = "0.3.7"
4178source = "registry+https://github.com/rust-lang/crates.io-index"
4179checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
4180dependencies = [
4181 "libc",
4182 "winapi",
4183]
4184
4185[[package]]
4186name = "ipnet"
4187version = "2.12.1"
4188source = "registry+https://github.com/rust-lang/crates.io-index"
4189checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
4190
4191[[package]]
4192name = "is_terminal_polyfill"
4193version = "1.70.2"
4194source = "registry+https://github.com/rust-lang/crates.io-index"
4195checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
4196
4197[[package]]
4198name = "iso8601"
4199version = "0.6.5"
4200source = "registry+https://github.com/rust-lang/crates.io-index"
4201checksum = "74a0559b45528cf0732d911524974977a5749f477d7dd99652830ffdaf53c4d1"
4202dependencies = [
4203 "nom 8.0.0",
4204]
4205
4206[[package]]
4207name = "issues"
4208version = "1.94.0"
4209dependencies = [
4210 "async-graphql",
4211 "async-trait",
4212 "axum",
4213 "axum-extra",
4214 "bytes",
4215 "cache",
4216 "config 1.94.0",
4217 "db",
4218 "export",
4219 "futures",
4220 "http",
4221 "jwt",
4222 "log",
4223 "metrics",
4224 "object_store",
4225 "rate_limit",
4226 "sea-orm",
4227 "serde",
4228 "serde_json",
4229 "space",
4230 "storage",
4231 "thiserror 2.0.19",
4232 "time",
4233 "tokio",
4234 "tower",
4235 "tracing",
4236 "utoipa",
4237 "uuid",
4238 "validator",
4239]
4240
4241[[package]]
4242name = "itertools"
4243version = "0.14.0"
4244source = "registry+https://github.com/rust-lang/crates.io-index"
4245checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
4246dependencies = [
4247 "either",
4248]
4249
4250[[package]]
4251name = "itertools"
4252version = "0.15.0"
4253source = "registry+https://github.com/rust-lang/crates.io-index"
4254checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
4255dependencies = [
4256 "either",
4257]
4258
4259[[package]]
4260name = "itoa"
4261version = "1.0.18"
4262source = "registry+https://github.com/rust-lang/crates.io-index"
4263checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
4264
4265[[package]]
4266name = "jiff"
4267version = "0.2.35"
4268source = "registry+https://github.com/rust-lang/crates.io-index"
4269checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
4270dependencies = [
4271 "defmt",
4272 "jiff-core",
4273 "jiff-static",
4274 "jiff-tzdb-platform",
4275 "log",
4276 "portable-atomic",
4277 "portable-atomic-util",
4278 "serde_core",
4279 "windows-link",
4280]
4281
4282[[package]]
4283name = "jiff-core"
4284version = "0.1.0"
4285source = "registry+https://github.com/rust-lang/crates.io-index"
4286checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
4287dependencies = [
4288 "defmt",
4289]
4290
4291[[package]]
4292name = "jiff-static"
4293version = "0.2.35"
4294source = "registry+https://github.com/rust-lang/crates.io-index"
4295checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
4296dependencies = [
4297 "jiff-core",
4298 "proc-macro2",
4299 "quote",
4300 "syn 2.0.119",
4301]
4302
4303[[package]]
4304name = "jiff-tzdb"
4305version = "0.1.8"
4306source = "registry+https://github.com/rust-lang/crates.io-index"
4307checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
4308
4309[[package]]
4310name = "jiff-tzdb-platform"
4311version = "0.1.3"
4312source = "registry+https://github.com/rust-lang/crates.io-index"
4313checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
4314dependencies = [
4315 "jiff-tzdb",
4316]
4317
4318[[package]]
4319name = "jni"
4320version = "0.22.4"
4321source = "registry+https://github.com/rust-lang/crates.io-index"
4322checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
4323dependencies = [
4324 "cfg-if",
4325 "combine",
4326 "jni-macros",
4327 "jni-sys",
4328 "log",
4329 "simd_cesu8",
4330 "thiserror 2.0.19",
4331 "walkdir",
4332 "windows-link",
4333]
4334
4335[[package]]
4336name = "jni-macros"
4337version = "0.22.4"
4338source = "registry+https://github.com/rust-lang/crates.io-index"
4339checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
4340dependencies = [
4341 "proc-macro2",
4342 "quote",
4343 "rustc_version",
4344 "simd_cesu8",
4345 "syn 2.0.119",
4346]
4347
4348[[package]]
4349name = "jni-sys"
4350version = "0.4.1"
4351source = "registry+https://github.com/rust-lang/crates.io-index"
4352checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
4353dependencies = [
4354 "jni-sys-macros",
4355]
4356
4357[[package]]
4358name = "jni-sys-macros"
4359version = "0.4.1"
4360source = "registry+https://github.com/rust-lang/crates.io-index"
4361checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
4362dependencies = [
4363 "quote",
4364 "syn 2.0.119",
4365]
4366
4367[[package]]
4368name = "jobserver"
4369version = "0.1.35"
4370source = "registry+https://github.com/rust-lang/crates.io-index"
4371checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
4372dependencies = [
4373 "getrandom 0.4.3",
4374 "libc",
4375]
4376
4377[[package]]
4378name = "js-sys"
4379version = "0.3.103"
4380source = "registry+https://github.com/rust-lang/crates.io-index"
4381checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
4382dependencies = [
4383 "cfg-if",
4384 "futures-util",
4385 "wasm-bindgen",
4386]
4387
4388[[package]]
4389name = "jsonwebtoken"
4390version = "10.4.0"
4391source = "registry+https://github.com/rust-lang/crates.io-index"
4392checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc"
4393dependencies = [
4394 "base64 0.22.1",
4395 "getrandom 0.2.17",
4396 "js-sys",
4397 "serde",
4398 "serde_json",
4399 "signature 2.2.0",
4400 "zeroize",
4401]
4402
4403[[package]]
4404name = "jsonwebtoken"
4405version = "11.0.0"
4406source = "registry+https://github.com/rust-lang/crates.io-index"
4407checksum = "881733cbc631fc9e472e24447ce32a64bedf2da498d6d8570b08edc87de71f65"
4408dependencies = [
4409 "base64 0.22.1",
4410 "ed25519-dalek 2.2.0",
4411 "getrandom 0.2.17",
4412 "hmac 0.12.1",
4413 "js-sys",
4414 "p256 0.13.2",
4415 "p384 0.13.1",
4416 "pem",
4417 "rand 0.8.7",
4418 "rsa 0.9.10",
4419 "serde",
4420 "serde_json",
4421 "sha2 0.10.9",
4422 "signature 2.2.0",
4423 "simple_asn1",
4424 "zeroize",
4425]
4426
4427[[package]]
4428name = "jwt"
4429version = "1.94.0"
4430dependencies = [
4431 "axum",
4432 "base64 0.23.1",
4433 "config 1.94.0",
4434 "db",
4435 "jsonwebtoken 11.0.0",
4436 "metrics",
4437 "rand 0.10.2",
4438 "rate_limit",
4439 "sea-orm",
4440 "serde",
4441 "serde_json",
4442 "sha2 0.10.9",
4443 "thiserror 2.0.19",
4444 "time",
4445 "tokio",
4446 "tracing",
4447 "uuid",
4448]
4449
4450[[package]]
4451name = "keccak"
4452version = "0.2.1"
4453source = "registry+https://github.com/rust-lang/crates.io-index"
4454checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4"
4455dependencies = [
4456 "cfg-if",
4457 "cpufeatures 0.3.0",
4458]
4459
4460[[package]]
4461name = "kem"
4462version = "0.3.0"
4463source = "registry+https://github.com/rust-lang/crates.io-index"
4464checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
4465dependencies = [
4466 "crypto-common 0.2.2",
4467 "rand_core 0.10.1",
4468]
4469
4470[[package]]
4471name = "lazy_static"
4472version = "1.5.0"
4473source = "registry+https://github.com/rust-lang/crates.io-index"
4474checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
4475dependencies = [
4476 "spin 0.9.9",
4477]
4478
4479[[package]]
4480name = "lettre"
4481version = "0.11.23"
4482source = "registry+https://github.com/rust-lang/crates.io-index"
4483checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae"
4484dependencies = [
4485 "async-trait",
4486 "base64 0.23.1",
4487 "email-encoding",
4488 "email_address",
4489 "fastrand",
4490 "futures-io",
4491 "futures-util",
4492 "httpdate",
4493 "idna",
4494 "mime",
4495 "native-tls",
4496 "nom 8.0.0",
4497 "percent-encoding",
4498 "quoted_printable",
4499 "socket2",
4500 "tokio",
4501 "tokio-native-tls",
4502 "url",
4503]
4504
4505[[package]]
4506name = "lexical-core"
4507version = "1.0.6"
4508source = "registry+https://github.com/rust-lang/crates.io-index"
4509checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594"
4510dependencies = [
4511 "lexical-parse-float",
4512 "lexical-parse-integer",
4513 "lexical-util",
4514 "lexical-write-float",
4515 "lexical-write-integer",
4516]
4517
4518[[package]]
4519name = "lexical-parse-float"
4520version = "1.0.6"
4521source = "registry+https://github.com/rust-lang/crates.io-index"
4522checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56"
4523dependencies = [
4524 "lexical-parse-integer",
4525 "lexical-util",
4526]
4527
4528[[package]]
4529name = "lexical-parse-integer"
4530version = "1.0.6"
4531source = "registry+https://github.com/rust-lang/crates.io-index"
4532checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34"
4533dependencies = [
4534 "lexical-util",
4535]
4536
4537[[package]]
4538name = "lexical-util"
4539version = "1.0.7"
4540source = "registry+https://github.com/rust-lang/crates.io-index"
4541checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17"
4542
4543[[package]]
4544name = "lexical-write-float"
4545version = "1.0.6"
4546source = "registry+https://github.com/rust-lang/crates.io-index"
4547checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361"
4548dependencies = [
4549 "lexical-util",
4550 "lexical-write-integer",
4551]
4552
4553[[package]]
4554name = "lexical-write-integer"
4555version = "1.0.6"
4556source = "registry+https://github.com/rust-lang/crates.io-index"
4557checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df"
4558dependencies = [
4559 "lexical-util",
4560]
4561
4562[[package]]
4563name = "libc"
4564version = "0.2.189"
4565source = "registry+https://github.com/rust-lang/crates.io-index"
4566checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
4567
4568[[package]]
4569name = "libm"
4570version = "0.2.16"
4571source = "registry+https://github.com/rust-lang/crates.io-index"
4572checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
4573
4574[[package]]
4575name = "libsqlite3-sys"
4576version = "0.37.0"
4577source = "registry+https://github.com/rust-lang/crates.io-index"
4578checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
4579dependencies = [
4580 "cc",
4581 "pkg-config",
4582 "vcpkg",
4583]
4584
4585[[package]]
4586name = "libz-sys"
4587version = "1.1.29"
4588source = "registry+https://github.com/rust-lang/crates.io-index"
4589checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
4590dependencies = [
4591 "cc",
4592 "libc",
4593 "pkg-config",
4594 "vcpkg",
4595]
4596
4597[[package]]
4598name = "linux-raw-sys"
4599version = "0.12.1"
4600source = "registry+https://github.com/rust-lang/crates.io-index"
4601checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
4602
4603[[package]]
4604name = "litemap"
4605version = "0.8.2"
4606source = "registry+https://github.com/rust-lang/crates.io-index"
4607checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
4608
4609[[package]]
4610name = "live"
4611version = "1.94.0"
4612dependencies = [
4613 "axum",
4614 "cache",
4615 "chatlayer",
4616 "config 1.94.0",
4617 "export",
4618 "hex",
4619 "jsonwebtoken 11.0.0",
4620 "jwt",
4621 "metrics",
4622 "rate_limit",
4623 "reqwest 0.13.4",
4624 "serde",
4625 "serde_json",
4626 "sha2 0.10.9",
4627 "storagelayer",
4628 "thiserror 2.0.19",
4629 "time",
4630 "tokio",
4631 "utoipa",
4632 "uuid",
4633]
4634
4635[[package]]
4636name = "livechat"
4637version = "1.94.0"
4638dependencies = [
4639 "axum",
4640 "cache",
4641 "chatlayer",
4642 "config 1.94.0",
4643 "db",
4644 "glayer",
4645 "jwt",
4646 "live",
4647 "obs",
4648 "queue",
4649 "rate_limit",
4650 "serde",
4651 "storage",
4652 "storagelayer",
4653 "tokio",
4654 "tracing",
4655]
4656
4657[[package]]
4658name = "llm"
4659version = "1.94.0"
4660dependencies = [
4661 "async-stream",
4662 "async-trait",
4663 "bytes",
4664 "eventsource-stream",
4665 "futures",
4666 "reqwest 0.13.4",
4667 "serde",
4668 "serde_json",
4669 "thiserror 2.0.19",
4670 "tokio",
4671 "tokio-stream",
4672 "tracing",
4673 "wiremock",
4674]
4675
4676[[package]]
4677name = "lock_api"
4678version = "0.4.14"
4679source = "registry+https://github.com/rust-lang/crates.io-index"
4680checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
4681dependencies = [
4682 "scopeguard",
4683]
4684
4685[[package]]
4686name = "log"
4687version = "0.4.33"
4688source = "registry+https://github.com/rust-lang/crates.io-index"
4689checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
4690
4691[[package]]
4692name = "lombok"
4693version = "0.4.0"
4694source = "registry+https://github.com/rust-lang/crates.io-index"
4695checksum = "0fd3ecc97c69dd7420e63ddc37457e143cff962c7301de78e4a6ec62d45abdf7"
4696dependencies = [
4697 "proc-macro2",
4698 "quote",
4699 "syn 1.0.109",
4700]
4701
4702[[package]]
4703name = "lprovider"
4704version = "1.94.0"
4705dependencies = [
4706 "aes-gcm",
4707 "async-trait",
4708 "axum",
4709 "base64 0.23.1",
4710 "bytes",
4711 "cache",
4712 "clap",
4713 "config 1.94.0",
4714 "db",
4715 "futures",
4716 "hex",
4717 "http",
4718 "llm",
4719 "metrics",
4720 "prost",
4721 "prost-types",
4722 "rand 0.10.2",
4723 "redis",
4724 "reqwest 0.13.4",
4725 "sea-orm",
4726 "secret",
4727 "serde",
4728 "serde_json",
4729 "serde_yaml",
4730 "sha2 0.10.9",
4731 "thiserror 2.0.19",
4732 "time",
4733 "tokio",
4734 "tokio-stream",
4735 "tokio-util",
4736 "tonic",
4737 "tonic-health",
4738 "tonic-prost",
4739 "tonic-prost-build",
4740 "tracing",
4741 "tracing-subscriber",
4742 "uuid",
4743]
4744
4745[[package]]
4746name = "lru-slab"
4747version = "0.1.2"
4748source = "registry+https://github.com/rust-lang/crates.io-index"
4749checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
4750
4751[[package]]
4752name = "mac_address"
4753version = "1.1.8"
4754source = "registry+https://github.com/rust-lang/crates.io-index"
4755checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
4756dependencies = [
4757 "nix 0.29.0",
4758 "serde",
4759 "winapi",
4760]
4761
4762[[package]]
4763name = "mails"
4764version = "1.94.0"
4765dependencies = [
4766 "config 1.94.0",
4767 "email",
4768 "obs",
4769 "tokio",
4770 "tracing",
4771]
4772
4773[[package]]
4774name = "matchers"
4775version = "0.2.0"
4776source = "registry+https://github.com/rust-lang/crates.io-index"
4777checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
4778dependencies = [
4779 "regex-automata",
4780]
4781
4782[[package]]
4783name = "matchit"
4784version = "0.8.4"
4785source = "registry+https://github.com/rust-lang/crates.io-index"
4786checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
4787
4788[[package]]
4789name = "mcp"
4790version = "1.94.0"
4791dependencies = [
4792 "async-trait",
4793 "axum",
4794 "config 1.94.0",
4795 "db",
4796 "export",
4797 "hex",
4798 "http",
4799 "ipnet",
4800 "issues",
4801 "project",
4802 "repo",
4803 "rmcp",
4804 "serde",
4805 "serde_json",
4806 "sha2 0.10.9",
4807 "space",
4808 "subtle",
4809 "thiserror 2.0.19",
4810 "tokio",
4811 "tokio-util",
4812 "tower",
4813 "tracing",
4814 "uuid",
4815]
4816
4817[[package]]
4818name = "md-5"
4819version = "0.11.0"
4820source = "registry+https://github.com/rust-lang/crates.io-index"
4821checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
4822dependencies = [
4823 "cfg-if",
4824 "digest 0.11.3",
4825]
4826
4827[[package]]
4828name = "md5"
4829version = "0.8.1"
4830source = "registry+https://github.com/rust-lang/crates.io-index"
4831checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
4832
4833[[package]]
4834name = "meilisearch-index-setting-macro"
4835version = "0.33.0"
4836source = "registry+https://github.com/rust-lang/crates.io-index"
4837checksum = "93b5b21df781c820a9cc387b808d4128cbc164dd28d67ac6ed666a00996f8f15"
4838dependencies = [
4839 "convert_case 0.8.0",
4840 "proc-macro2",
4841 "quote",
4842 "structmeta",
4843 "syn 2.0.119",
4844]
4845
4846[[package]]
4847name = "meilisearch-sdk"
4848version = "0.33.0"
4849source = "registry+https://github.com/rust-lang/crates.io-index"
4850checksum = "19e6e3646ba2a9a306296c1edf4a050508a408c1b59ca456d9ad4965ec6e91e9"
4851dependencies = [
4852 "async-trait",
4853 "bytes",
4854 "either",
4855 "futures-channel",
4856 "futures-core",
4857 "futures-io",
4858 "futures-util",
4859 "iso8601",
4860 "jsonwebtoken 10.4.0",
4861 "log",
4862 "meilisearch-index-setting-macro",
4863 "pin-project-lite",
4864 "reqwest 0.12.28",
4865 "serde",
4866 "serde_json",
4867 "thiserror 2.0.19",
4868 "time",
4869 "tokio",
4870 "uuid",
4871 "wasm-bindgen-futures",
4872 "web-sys",
4873 "yaup",
4874]
4875
4876[[package]]
4877name = "memchr"
4878version = "2.8.3"
4879source = "registry+https://github.com/rust-lang/crates.io-index"
4880checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
4881
4882[[package]]
4883name = "memmap2"
4884version = "0.9.11"
4885source = "registry+https://github.com/rust-lang/crates.io-index"
4886checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
4887dependencies = [
4888 "libc",
4889]
4890
4891[[package]]
4892name = "memoffset"
4893version = "0.9.1"
4894source = "registry+https://github.com/rust-lang/crates.io-index"
4895checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
4896dependencies = [
4897 "autocfg",
4898]
4899
4900[[package]]
4901name = "metrics"
4902version = "0.24.6"
4903source = "registry+https://github.com/rust-lang/crates.io-index"
4904checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2"
4905dependencies = [
4906 "portable-atomic",
4907 "rapidhash",
4908]
1873checksum = "f7d7e54efdddeb1208c08dd5d32b53a879ac00d2d3d051b2255fd26821d16368"
49091874
49101875[[package]]
4911name = "metrics-exporter-opentelemetry"
4912version = "0.2.1"
1876name = "icu_collections"
1877version = "2.3.0"
49131878source = "registry+https://github.com/rust-lang/crates.io-index"
4914checksum = "500e1fa58894a8c140ee0a9282e1d9e765fc7c7853bd310d1357d2808ad8f0b0"
1879checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
49151880dependencies = [
4916 "derive_more",
4917 "metrics",
4918 "opentelemetry",
4919 "opentelemetry_sdk",
1881 "displaydoc",
1882 "potential_utf",
1883 "serde",
1884 "utf8_iter",
1885 "yoke",
1886 "zerofrom",
1887 "zerovec",
49201888]
49211889
49221890[[package]]
4923name = "migrate"
4924version = "1.94.0"
1891name = "icu_datetime"
1892version = "2.3.0"
1893source = "registry+https://github.com/rust-lang/crates.io-index"
1894checksum = "c44df99c4c5297b05653d1a1629f13bdc9fd4044acfbbaf30b15194a1eed4cd6"
49251895dependencies = [
4926 "config 1.94.0",
4927 "db",
4928 "obs",
4929 "tokio",
4930 "tracing",
1896 "displaydoc",
1897 "fixed_decimal",
1898 "icu_calendar",
1899 "icu_datetime_data",
1900 "icu_decimal",
1901 "icu_locale_core",
1902 "icu_locale_fallback",
1903 "icu_pattern",
1904 "icu_plurals",
1905 "icu_provider",
1906 "icu_time",
1907 "potential_utf",
1908 "tinystr",
1909 "writeable",
1910 "zerovec",
49311911]
49321912
49331913[[package]]
4934name = "mime"
4935version = "0.3.17"
1914name = "icu_datetime_data"
1915version = "2.3.0"
49361916source = "registry+https://github.com/rust-lang/crates.io-index"
4937checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1917checksum = "8bb5555f6e4b42233f58564351ce16d73e9e6ebfce6bdcf29875d0cfc554d6a8"
49381918
49391919[[package]]
4940name = "minimal-lexical"
4941version = "0.2.1"
1920name = "icu_decimal"
1921version = "2.3.0"
49421922source = "registry+https://github.com/rust-lang/crates.io-index"
4943checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
4944
4945[[package]]
4946name = "miniz_oxide"
4947version = "0.8.9"
4948source = "registry+https://github.com/rust-lang/crates.io-index"
4949checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1923checksum = "9eb8f655bba2d0c0459e43a5b0e6cd3cd388109898fb3d85d048165e8b0abd08"
49501924dependencies = [
4951 "adler2",
4952 "simd-adler32",
1925 "displaydoc",
1926 "fixed_decimal",
1927 "icu_decimal_data",
1928 "icu_locale_core",
1929 "icu_locale_fallback",
1930 "icu_pattern",
1931 "icu_plurals",
1932 "icu_provider",
1933 "serde",
1934 "writeable",
1935 "zerovec",
49531936]
49541937
49551938[[package]]
4956name = "mio"
4957version = "1.2.2"
1939name = "icu_decimal_data"
1940version = "2.3.0"
49581941source = "registry+https://github.com/rust-lang/crates.io-index"
4959checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
4960dependencies = [
4961 "libc",
4962 "wasi",
4963 "windows-sys 0.61.2",
4964]
1942checksum = "e4c887fc7d4c297cf3f9436864af9e3dba7f8be9415a6c2a7f392f3b1636298c"
49651943
49661944[[package]]
4967name = "ml-kem"
4968version = "0.3.2"
1945name = "icu_experimental"
1946version = "0.6.0"
49691947source = "registry+https://github.com/rust-lang/crates.io-index"
4970checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
1948checksum = "7c09609ad18e249b97a0afa20e19502c4d11ab039e6e35f5ecf48bebd4eedd7e"
49711949dependencies = [
4972 "hybrid-array",
4973 "kem",
4974 "module-lattice",
4975 "pkcs8 0.11.0",
4976 "rand_core 0.10.1",
4977 "sha3",
1950 "displaydoc",
1951 "either",
1952 "fixed_decimal",
1953 "icu_casemap",
1954 "icu_collections",
1955 "icu_decimal",
1956 "icu_experimental_data",
1957 "icu_list",
1958 "icu_locale",
1959 "icu_locale_core",
1960 "icu_locale_fallback",
1961 "icu_normalizer",
1962 "icu_pattern",
1963 "icu_plurals",
1964 "icu_properties",
1965 "icu_provider",
1966 "litemap",
1967 "num-bigint 0.4.8",
1968 "num-rational",
1969 "num-traits",
1970 "potential_utf",
1971 "smallvec",
1972 "tinystr",
1973 "writeable",
1974 "zerotrie",
1975 "zerovec",
49781976]
49791977
49801978[[package]]
4981name = "module-lattice"
4982version = "0.2.3"
1979name = "icu_experimental_data"
1980version = "0.6.0"
49831981source = "registry+https://github.com/rust-lang/crates.io-index"
4984checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
4985dependencies = [
4986 "ctutils",
4987 "hybrid-array",
4988 "num-traits",
4989]
1982checksum = "377481a8935d6774f410424d79a6dc8a09d9bd8def9560cff17abe755a246280"
49901983
49911984[[package]]
4992name = "multer"
4993version = "3.1.0"
1985name = "icu_list"
1986version = "2.3.0"
49941987source = "registry+https://github.com/rust-lang/crates.io-index"
4995checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
1988checksum = "5a5c625125085fac4bf9ad111888a90c6ab9dc7bcaec392ca79cab417f46e3fb"
49961989dependencies = [
4997 "bytes",
4998 "encoding_rs",
4999 "futures-util",
5000 "http",
5001 "httparse",
5002 "memchr",
5003 "mime",
5004 "spin 0.9.9",
5005 "version_check",
1990 "icu_list_data",
1991 "icu_locale_fallback",
1992 "icu_provider",
1993 "regex-automata",
1994 "serde",
1995 "writeable",
1996 "zerovec",
50061997]
50071998
50081999[[package]]
5009name = "multimap"
5010version = "0.10.1"
2000name = "icu_list_data"
2001version = "2.3.0"
50112002source = "registry+https://github.com/rust-lang/crates.io-index"
5012checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
2003checksum = "4ffef91ee351bd195a803115615a034dfb9f02bb48850980548c31cb78568a91"
50132004
50142005[[package]]
5015name = "native-tls"
5016version = "0.2.18"
2006name = "icu_locale"
2007version = "2.3.0"
50172008source = "registry+https://github.com/rust-lang/crates.io-index"
5018checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
2009checksum = "250ec6d4ac288584d1d0ba29dbfad7737635b7b0e822441f4026a523e3fdb147"
50192010dependencies = [
5020 "libc",
5021 "log",
5022 "openssl",
5023 "openssl-probe",
5024 "openssl-sys",
5025 "schannel",
5026 "security-framework",
5027 "security-framework-sys",
5028 "tempfile",
2011 "icu_collections",
2012 "icu_locale_core",
2013 "icu_locale_data",
2014 "icu_locale_fallback",
2015 "icu_provider",
2016 "tinystr",
2017 "zerovec",
50292018]
50302019
50312020[[package]]
5032name = "nix"
5033version = "0.29.0"
2021name = "icu_locale_core"
2022version = "2.3.0"
50342023source = "registry+https://github.com/rust-lang/crates.io-index"
5035checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
2024checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
50362025dependencies = [
5037 "bitflags 2.13.1",
5038 "cfg-if",
5039 "cfg_aliases",
5040 "libc",
5041 "memoffset",
2026 "displaydoc",
2027 "litemap",
2028 "serde",
2029 "tinystr",
2030 "writeable",
2031 "zerovec",
50422032]
50432033
50442034[[package]]
5045name = "nix"
5046version = "0.31.3"
2035name = "icu_locale_data"
2036version = "2.3.0"
2037source = "registry+https://github.com/rust-lang/crates.io-index"
2038checksum = "d37d91460e362a5cf58907cd7ce871411775ee6294e49a5cc8e6cec16dfa75ea"
2039
2040[[package]]
2041name = "icu_locale_fallback"
2042version = "2.3.0"
50472043source = "registry+https://github.com/rust-lang/crates.io-index"
5048checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
2044checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9"
50492045dependencies = [
5050 "bitflags 2.13.1",
5051 "cfg-if",
5052 "cfg_aliases",
5053 "libc",
2046 "icu_locale_core",
2047 "icu_locale_fallback_data",
2048 "icu_provider",
2049 "potential_utf",
2050 "tinystr",
2051 "zerovec",
50542052]
50552053
50562054[[package]]
5057name = "nom"
5058version = "7.1.3"
2055name = "icu_locale_fallback_data"
2056version = "2.3.0"
50592057source = "registry+https://github.com/rust-lang/crates.io-index"
5060checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
5061dependencies = [
5062 "memchr",
5063 "minimal-lexical",
5064]
2058checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8"
50652059
50662060[[package]]
5067name = "nom"
5068version = "8.0.0"
2061name = "icu_normalizer"
2062version = "2.3.0"
50692063source = "registry+https://github.com/rust-lang/crates.io-index"
5070checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
2064checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
50712065dependencies = [
5072 "memchr",
2066 "icu_collections",
2067 "icu_normalizer_data",
2068 "icu_properties",
2069 "icu_provider",
2070 "smallvec",
2071 "utf16_iter",
2072 "utf8_iter",
2073 "write16",
2074 "zerovec",
50732075]
50742076
50752077[[package]]
5076name = "nonempty"
5077version = "0.12.0"
2078name = "icu_normalizer_data"
2079version = "2.3.0"
50782080source = "registry+https://github.com/rust-lang/crates.io-index"
5079checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6"
5080dependencies = [
5081 "serde",
5082]
2081checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
50832082
50842083[[package]]
5085name = "nu-ansi-term"
5086version = "0.50.3"
2084name = "icu_pattern"
2085version = "0.5.0"
50872086source = "registry+https://github.com/rust-lang/crates.io-index"
5088checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
2087checksum = "2b3db611446bd92c246b03e4aa0bafa71ddb194af431ddbb42b9d70e4bfce1a0"
50892088dependencies = [
5090 "windows-sys 0.61.2",
2089 "displaydoc",
2090 "either",
2091 "serde",
2092 "writeable",
2093 "yoke",
2094 "zerovec",
50912095]
50922096
50932097[[package]]
5094name = "num-bigint"
5095version = "0.4.8"
2098name = "icu_plurals"
2099version = "2.3.0"
50962100source = "registry+https://github.com/rust-lang/crates.io-index"
5097checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
2101checksum = "e475e6766ef87b1d3c1f97be6363995b0bfaeddbc789671615df598a97ff0593"
50982102dependencies = [
5099 "num-integer",
5100 "num-traits",
2103 "fixed_decimal",
2104 "icu_locale_fallback",
2105 "icu_plurals_data",
2106 "icu_provider",
2107 "zerovec",
51012108]
51022109
51032110[[package]]
5104name = "num-bigint-dig"
5105version = "0.8.6"
2111name = "icu_plurals_data"
2112version = "2.3.0"
51062113source = "registry+https://github.com/rust-lang/crates.io-index"
5107checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
5108dependencies = [
5109 "lazy_static",
5110 "libm",
5111 "num-integer",
5112 "num-iter",
5113 "num-traits",
5114 "rand 0.8.7",
5115 "smallvec",
5116 "zeroize",
5117]
2114checksum = "1251aa39a95e1333888e499b1263e1c196447a28948acf5bdabd82c046f153bf"
51182115
51192116[[package]]
5120name = "num-complex"
5121version = "0.4.6"
2117name = "icu_properties"
2118version = "2.3.0"
51222119source = "registry+https://github.com/rust-lang/crates.io-index"
5123checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
2120checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
51242121dependencies = [
5125 "num-traits",
2122 "displaydoc",
2123 "icu_collections",
2124 "icu_locale_core",
2125 "icu_properties_data",
2126 "icu_provider",
2127 "serde",
2128 "zerotrie",
2129 "zerovec",
51262130]
51272131
51282132[[package]]
5129name = "num-conv"
5130version = "0.2.2"
2133name = "icu_properties_data"
2134version = "2.3.0"
51312135source = "registry+https://github.com/rust-lang/crates.io-index"
5132checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
2136checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
51332137
51342138[[package]]
5135name = "num-integer"
5136version = "0.1.46"
2139name = "icu_provider"
2140version = "2.3.0"
51372141source = "registry+https://github.com/rust-lang/crates.io-index"
5138checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
2142checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428"
51392143dependencies = [
5140 "num-traits",
2144 "displaydoc",
2145 "icu_locale_core",
2146 "serde",
2147 "stable_deref_trait",
2148 "writeable",
2149 "yoke",
2150 "zerofrom",
2151 "zerotrie",
2152 "zerovec",
51412153]
51422154
51432155[[package]]
5144name = "num-iter"
5145version = "0.1.46"
2156name = "icu_segmenter"
2157version = "2.3.0"
51462158source = "registry+https://github.com/rust-lang/crates.io-index"
5147checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
2159checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db"
51482160dependencies = [
5149 "num-integer",
5150 "num-traits",
2161 "core_maths",
2162 "icu_collections",
2163 "icu_locale_fallback",
2164 "icu_provider",
2165 "icu_segmenter_data",
2166 "potential_utf",
2167 "smallvec",
2168 "utf8_iter",
2169 "zerovec",
51512170]
51522171
51532172[[package]]
5154name = "num-modular"
5155version = "0.6.4"
2173name = "icu_segmenter_data"
2174version = "2.3.0"
51562175source = "registry+https://github.com/rust-lang/crates.io-index"
5157checksum = "fc41a1374056e9672221567958a66c16be12d0e2c1b408761e14d901c237d5e0"
2176checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad"
51582177
51592178[[package]]
5160name = "num-order"
5161version = "1.2.0"
2179name = "icu_time"
2180version = "2.3.0"
51622181source = "registry+https://github.com/rust-lang/crates.io-index"
5163checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
2182checksum = "af37e1c3b9f3ab564b619fdf4b22de169f6381eb0f7048e3ca454290c438126b"
51642183dependencies = [
5165 "num-modular",
2184 "calendrical_calculations",
2185 "displaydoc",
2186 "icu_calendar",
2187 "icu_locale_core",
2188 "icu_provider",
2189 "icu_time_data",
2190 "ixdtf",
2191 "serde",
2192 "zerotrie",
2193 "zerovec",
51662194]
51672195
51682196[[package]]
5169name = "num-traits"
5170version = "0.2.19"
2197name = "icu_time_data"
2198version = "2.3.0"
51712199source = "registry+https://github.com/rust-lang/crates.io-index"
5172checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
5173dependencies = [
5174 "autocfg",
5175 "libm",
5176]
2200checksum = "005ff86fa5851a77fc0a977b72d717f777bd9230415a247219d7898aefb1517a"
51772201
51782202[[package]]
5179name = "num_cpus"
5180version = "1.17.0"
2203name = "ident_case"
2204version = "1.0.1"
51812205source = "registry+https://github.com/rust-lang/crates.io-index"
5182checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
5183dependencies = [
5184 "hermit-abi",
5185 "libc",
5186]
2206checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
51872207
51882208[[package]]
5189name = "num_enum"
5190version = "0.7.6"
2209name = "idna"
2210version = "0.1.5"
51912211source = "registry+https://github.com/rust-lang/crates.io-index"
5192checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
2212checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
51932213dependencies = [
5194 "num_enum_derive",
5195 "rustversion",
2214 "matches",
2215 "unicode-bidi",
2216 "unicode-normalization",
51962217]
51972218
51982219[[package]]
5199name = "num_enum_derive"
5200version = "0.7.6"
2220name = "idna"
2221version = "1.1.0"
52012222source = "registry+https://github.com/rust-lang/crates.io-index"
5202checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
2223checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
52032224dependencies = [
5204 "proc-macro-crate",
5205 "proc-macro2",
5206 "quote",
5207 "syn 2.0.119",
2225 "idna_adapter",
2226 "smallvec",
2227 "utf8_iter",
52082228]
52092229
52102230[[package]]
5211name = "object_store"
5212version = "0.14.1"
2231name = "idna_adapter"
2232version = "1.2.2"
52132233source = "registry+https://github.com/rust-lang/crates.io-index"
5214checksum = "d354792e39fa5f0009e47623cf8b15b099bf9a652fa55c6f817fe28ac84fea50"
2234checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
52152235dependencies = [
5216 "async-trait",
5217 "aws-lc-rs",
5218 "base64 0.22.1",
5219 "bytes",
5220 "chrono",
5221 "crc-fast",
5222 "form_urlencoded",
5223 "futures-channel",
5224 "futures-core",
5225 "futures-util",
5226 "http",
5227 "http-body-util",
5228 "humantime",
5229 "hyper",
5230 "itertools 0.15.0",
5231 "md-5",
5232 "nix 0.31.3",
5233 "parking_lot",
5234 "percent-encoding",
5235 "quick-xml",
5236 "rand 0.10.2",
5237 "reqwest 0.13.4",
5238 "rustls-pki-types",
5239 "serde",
5240 "serde_json",
5241 "serde_urlencoded",
5242 "thiserror 2.0.19",
5243 "tokio",
5244 "tracing",
5245 "url",
5246 "walkdir",
5247 "wasm-bindgen-futures",
5248 "web-time",
5249 "windows-sys 0.61.2",
2236 "icu_normalizer",
2237 "icu_properties",
52502238]
52512239
52522240[[package]]
5253name = "obs"
5254version = "1.94.0"
2241name = "indexmap"
2242version = "2.14.0"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
2244checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
52552245dependencies = [
5256 "base64 0.23.1",
5257 "config 1.94.0",
5258 "metrics",
5259 "metrics-exporter-opentelemetry",
5260 "opentelemetry",
5261 "opentelemetry-appender-tracing",
5262 "opentelemetry-otlp",
5263 "opentelemetry_sdk",
5264 "reqwest 0.12.28",
5265 "thiserror 2.0.19",
5266 "tokio",
5267 "tracing",
5268 "tracing-opentelemetry",
5269 "tracing-subscriber",
2246 "equivalent",
2247 "hashbrown 0.17.1",
52702248]
52712249
52722250[[package]]
5273name = "once_cell"
5274version = "1.21.4"
2251name = "inout"
2252version = "0.1.4"
52752253source = "registry+https://github.com/rust-lang/crates.io-index"
5276checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
2254checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
2255dependencies = [
2256 "generic-array",
2257]
52772258
52782259[[package]]
5279name = "once_cell_polyfill"
5280version = "1.70.2"
2260name = "inout"
2261version = "0.2.2"
52812262source = "registry+https://github.com/rust-lang/crates.io-index"
5282checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
5283
5284[[package]]
5285name = "openssl"
5286version = "0.10.81"
5287source = "registry+https://github.com/rust-lang/crates.io-index"
5288checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
2263checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
52892264dependencies = [
5290 "bitflags 2.13.1",
5291 "cfg-if",
5292 "foreign-types",
5293 "libc",
5294 "openssl-macros",
5295 "openssl-sys",
2265 "hybrid-array",
52962266]
52972267
52982268[[package]]
5299name = "openssl-macros"
5300version = "0.1.1"
2269name = "itertools"
2270version = "0.14.0"
53012271source = "registry+https://github.com/rust-lang/crates.io-index"
5302checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
2272checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
53032273dependencies = [
5304 "proc-macro2",
5305 "quote",
5306 "syn 2.0.119",
2274 "either",
53072275]
53082276
53092277[[package]]
5310name = "openssl-probe"
5311version = "0.2.1"
2278name = "itoa"
2279version = "1.0.18"
53122280source = "registry+https://github.com/rust-lang/crates.io-index"
5313checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
2281checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
53142282
53152283[[package]]
5316name = "openssl-sys"
5317version = "0.9.117"
2284name = "ixdtf"
2285version = "0.6.6"
53182286source = "registry+https://github.com/rust-lang/crates.io-index"
5319checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
5320dependencies = [
5321 "cc",
5322 "libc",
5323 "pkg-config",
5324 "vcpkg",
5325]
2287checksum = "4d3667095d64c3ecffc96463a21157b04bf3e252f6e8d5750b20c02e33c194e3"
53262288
53272289[[package]]
5328name = "opentelemetry"
5329version = "0.31.0"
2290name = "js-sys"
2291version = "0.3.104"
53302292source = "registry+https://github.com/rust-lang/crates.io-index"
5331checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0"
2293checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
53322294dependencies = [
5333 "futures-core",
5334 "futures-sink",
5335 "js-sys",
5336 "pin-project-lite",
5337 "thiserror 2.0.19",
5338 "tracing",
2295 "cfg-if",
2296 "futures-util",
2297 "wasm-bindgen",
53392298]
53402299
53412300[[package]]
5342name = "opentelemetry-appender-tracing"
5343version = "0.31.0"
2301name = "language-tags"
2302version = "0.2.2"
53442303source = "registry+https://github.com/rust-lang/crates.io-index"
5345checksum = "bc11dcdfd423bb85c4724d4a4b61baec0e16093f565d3fbded30891358e95277"
5346dependencies = [
5347 "opentelemetry",
5348 "tracing",
5349 "tracing-core",
5350 "tracing-subscriber",
5351]
2304checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
53522305
53532306[[package]]
5354name = "opentelemetry-http"
5355version = "0.31.0"
2307name = "lexical-core"
2308version = "1.0.6"
53562309source = "registry+https://github.com/rust-lang/crates.io-index"
5357checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d"
2310checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594"
53582311dependencies = [
5359 "async-trait",
5360 "bytes",
5361 "http",
5362 "opentelemetry",
5363 "reqwest 0.12.28",
2312 "lexical-parse-float",
2313 "lexical-parse-integer",
2314 "lexical-util",
2315 "lexical-write-float",
2316 "lexical-write-integer",
53642317]
53652318
53662319[[package]]
5367name = "opentelemetry-otlp"
5368version = "0.31.0"
2320name = "lexical-parse-float"
2321version = "1.0.6"
53692322source = "registry+https://github.com/rust-lang/crates.io-index"
5370checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf"
2323checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56"
53712324dependencies = [
5372 "http",
5373 "opentelemetry",
5374 "opentelemetry-http",
5375 "opentelemetry-proto",
5376 "opentelemetry_sdk",
5377 "prost",
5378 "reqwest 0.12.28",
5379 "thiserror 2.0.19",
2325 "lexical-parse-integer",
2326 "lexical-util",
53802327]
53812328
53822329[[package]]
5383name = "opentelemetry-proto"
5384version = "0.31.0"
2330name = "lexical-parse-integer"
2331version = "1.0.6"
53852332source = "registry+https://github.com/rust-lang/crates.io-index"
5386checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f"
2333checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34"
53872334dependencies = [
5388 "opentelemetry",
5389 "opentelemetry_sdk",
5390 "prost",
5391 "tonic",
5392 "tonic-prost",
2335 "lexical-util",
53932336]
53942337
53952338[[package]]
5396name = "opentelemetry_sdk"
5397version = "0.31.0"
2339name = "lexical-util"
2340version = "1.0.7"
53982341source = "registry+https://github.com/rust-lang/crates.io-index"
5399checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd"
5400dependencies = [
5401 "futures-channel",
5402 "futures-executor",
5403 "futures-util",
5404 "opentelemetry",
5405 "percent-encoding",
5406 "rand 0.9.5",
5407 "thiserror 2.0.19",
5408 "tokio",
5409 "tokio-stream",
5410]
2342checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17"
54112343
54122344[[package]]
5413name = "ordered-float"
5414version = "4.6.0"
2345name = "lexical-write-float"
2346version = "1.0.6"
54152347source = "registry+https://github.com/rust-lang/crates.io-index"
5416checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
2348checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361"
54172349dependencies = [
5418 "num-traits",
2350 "lexical-util",
2351 "lexical-write-integer",
54192352]
54202353
54212354[[package]]
5422name = "os_pipe"
5423version = "1.2.3"
2355name = "lexical-write-integer"
2356version = "1.0.6"
54242357source = "registry+https://github.com/rust-lang/crates.io-index"
5425checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
2358checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df"
54262359dependencies = [
5427 "libc",
5428 "windows-sys 0.61.2",
2360 "lexical-util",
54292361]
54302362
54312363[[package]]
5432name = "ouroboros"
5433version = "0.18.5"
2364name = "libc"
2365version = "0.2.189"
54342366source = "registry+https://github.com/rust-lang/crates.io-index"
5435checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
5436dependencies = [
5437 "aliasable",
5438 "ouroboros_macro",
5439 "static_assertions",
5440]
2367checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
54412368
54422369[[package]]
5443name = "ouroboros_macro"
5444version = "0.18.5"
2370name = "libm"
2371version = "0.2.16"
54452372source = "registry+https://github.com/rust-lang/crates.io-index"
5446checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
5447dependencies = [
5448 "heck 0.4.1",
5449 "proc-macro2",
5450 "proc-macro2-diagnostics",
5451 "quote",
5452 "syn 2.0.119",
5453]
2373checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
54542374
54552375[[package]]
5456name = "p256"
5457version = "0.13.2"
2376name = "libredox"
2377version = "0.1.20"
54582378source = "registry+https://github.com/rust-lang/crates.io-index"
5459checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
2379checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
54602380dependencies = [
5461 "ecdsa 0.16.9",
5462 "elliptic-curve 0.13.8",
5463 "primeorder 0.13.6",
5464 "sha2 0.10.9",
2381 "libc",
54652382]
54662383
54672384[[package]]
5468name = "p256"
5469version = "0.14.0-rc.15"
2385name = "libsqlite3-sys"
2386version = "0.37.0"
54702387source = "registry+https://github.com/rust-lang/crates.io-index"
5471checksum = "b6bb40a5099e2c38a09dd29321a7a7f045f165a54317679c7cdfb0cbaf8f6b1e"
2388checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
54722389dependencies = [
5473 "ecdsa 0.17.0",
5474 "elliptic-curve 0.14.1",
5475 "primefield",
5476 "primeorder 0.14.0",
5477 "sha2 0.11.0",
2390 "pkg-config",
2391 "vcpkg",
54782392]
54792393
54802394[[package]]
5481name = "p384"
5482version = "0.13.1"
2395name = "linux-raw-sys"
2396version = "0.12.1"
54832397source = "registry+https://github.com/rust-lang/crates.io-index"
5484checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
5485dependencies = [
5486 "ecdsa 0.16.9",
5487 "elliptic-curve 0.13.8",
5488 "primeorder 0.13.6",
5489 "sha2 0.10.9",
5490]
2398checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
54912399
54922400[[package]]
5493name = "p384"
5494version = "0.14.0-rc.15"
2401name = "litemap"
2402version = "0.8.3"
54952403source = "registry+https://github.com/rust-lang/crates.io-index"
5496checksum = "492f329d7eb11d22dadc988626b9ea1f503b4ab043a8b1e4e2cc4ae45dabdd70"
5497dependencies = [
5498 "ecdsa 0.17.0",
5499 "elliptic-curve 0.14.1",
5500 "fiat-crypto 0.3.0",
5501 "primefield",
5502 "primeorder 0.14.0",
5503 "sha2 0.11.0",
5504]
2404checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
55052405
55062406[[package]]
5507name = "p521"
5508version = "0.14.0-rc.15"
2407name = "lock_api"
2408version = "0.4.14"
55092409source = "registry+https://github.com/rust-lang/crates.io-index"
5510checksum = "ff42e4ace5424e3b6d7cb82514be89866b85af87015f80341e37dcc21a66ce6e"
2410checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
55112411dependencies = [
5512 "base16ct 1.0.0",
5513 "ecdsa 0.17.0",
5514 "elliptic-curve 0.14.1",
5515 "primefield",
5516 "primeorder 0.14.0",
5517 "sha2 0.11.0",
2412 "scopeguard",
55182413]
55192414
55202415[[package]]
5521name = "pageant"
5522version = "0.2.1"
2416name = "log"
2417version = "0.3.9"
55232418source = "registry+https://github.com/rust-lang/crates.io-index"
5524checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5"
2419checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
55252420dependencies = [
5526 "base16ct 1.0.0",
5527 "byteorder",
5528 "bytes",
5529 "delegate",
5530 "futures",
5531 "log",
5532 "rand 0.10.2",
5533 "sha2 0.11.0",
5534 "thiserror 2.0.19",
5535 "tokio",
5536 "windows",
5537 "windows-strings",
2421 "log 0.4.33",
55382422]
55392423
55402424[[package]]
5541name = "parking"
5542version = "2.2.1"
2425name = "log"
2426version = "0.4.33"
55432427source = "registry+https://github.com/rust-lang/crates.io-index"
5544checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
2428checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
55452429
55462430[[package]]
5547name = "parking_lot"
5548version = "0.12.5"
2431name = "mac_address"
2432version = "1.1.8"
55492433source = "registry+https://github.com/rust-lang/crates.io-index"
5550checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
2434checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
55512435dependencies = [
5552 "lock_api",
5553 "parking_lot_core",
2436 "nix",
2437 "serde",
2438 "winapi",
55542439]
55552440
55562441[[package]]
5557name = "parking_lot_core"
5558version = "0.9.12"
2442name = "matches"
2443version = "0.1.10"
55592444source = "registry+https://github.com/rust-lang/crates.io-index"
5560checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
5561dependencies = [
5562 "cfg-if",
5563 "libc",
5564 "redox_syscall",
5565 "smallvec",
5566 "windows-link",
5567]
2445checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
55682446
55692447[[package]]
5570name = "password-hash"
5571version = "0.5.0"
2448name = "matchit"
2449version = "0.8.4"
55722450source = "registry+https://github.com/rust-lang/crates.io-index"
5573checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
5574dependencies = [
5575 "base64ct",
5576 "rand_core 0.6.4",
5577 "subtle",
5578]
2451checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
55792452
55802453[[package]]
5581name = "password-hash"
5582version = "0.6.1"
2454name = "md-5"
2455version = "0.11.0"
55832456source = "registry+https://github.com/rust-lang/crates.io-index"
5584checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
2457checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
55852458dependencies = [
5586 "phc",
2459 "cfg-if",
2460 "digest 0.11.3",
55872461]
55882462
55892463[[package]]
5590name = "paste"
5591version = "1.0.15"
2464name = "md5"
2465version = "0.7.0"
55922466source = "registry+https://github.com/rust-lang/crates.io-index"
5593checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
2467checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
55942468
55952469[[package]]
5596name = "pastey"
5597version = "0.2.3"
2470name = "memchr"
2471version = "2.8.3"
55982472source = "registry+https://github.com/rust-lang/crates.io-index"
5599checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
2473checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
56002474
56012475[[package]]
5602name = "pathdiff"
5603version = "0.2.3"
2476name = "memoffset"
2477version = "0.9.1"
56042478source = "registry+https://github.com/rust-lang/crates.io-index"
5605checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
5606
5607[[package]]
5608name = "payment"
5609version = "1.94.0"
2479checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
56102480dependencies = [
5611 "async-trait",
5612 "axum",
5613 "cache",
5614 "config 1.94.0",
5615 "db",
5616 "export",
5617 "jwt",
5618 "metrics",
5619 "reqwest 0.13.4",
5620 "serde",
5621 "serde_json",
5622 "thiserror 2.0.19",
5623 "time",
5624 "tracing",
5625 "uuid",
2481 "autocfg",
56262482]
56272483
56282484[[package]]
5629name = "pbkdf2"
5630version = "0.12.2"
2485name = "mime"
2486version = "0.2.6"
56312487source = "registry+https://github.com/rust-lang/crates.io-index"
5632checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
2488checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
56332489dependencies = [
5634 "digest 0.10.7",
5635 "hmac 0.12.1",
2490 "log 0.3.9",
56362491]
56372492
56382493[[package]]
5639name = "pbkdf2"
5640version = "0.13.0"
2494name = "mime"
2495version = "0.3.17"
56412496source = "registry+https://github.com/rust-lang/crates.io-index"
5642checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
5643dependencies = [
5644 "digest 0.11.3",
5645 "hmac 0.13.0",
5646]
2497checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
56472498
56482499[[package]]
5649name = "pem"
5650version = "3.0.6"
2500name = "minidom"
2501version = "0.15.2"
56512502source = "registry+https://github.com/rust-lang/crates.io-index"
5652checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
2503checksum = "f45614075738ce1b77a1768912a60c0227525971b03e09122a05b8a34a2a6278"
56532504dependencies = [
5654 "base64 0.22.1",
5655 "serde_core",
2505 "rxml",
56562506]
56572507
56582508[[package]]
5659name = "pem-rfc7468"
5660version = "0.7.0"
2509name = "mio"
2510version = "1.2.2"
56612511source = "registry+https://github.com/rust-lang/crates.io-index"
5662checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
2512checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
56632513dependencies = [
5664 "base64ct",
2514 "libc",
2515 "wasi 0.11.1+wasi-snapshot-preview1",
2516 "windows-sys 0.61.2",
56652517]
56662518
56672519[[package]]
5668name = "pem-rfc7468"
2520name = "models"
56692521version = "1.0.0"
5670source = "registry+https://github.com/rust-lang/crates.io-index"
5671checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
56722522dependencies = [
5673 "base64ct",
2523 "sea-orm",
2524 "serde",
2525 "serde_json",
2526 "time 0.3.55",
2527 "tokio",
2528 "uuid",
56742529]
56752530
56762531[[package]]
5677name = "percent-encoding"
5678version = "2.3.2"
2532name = "moka"
2533version = "0.12.16"
56792534source = "registry+https://github.com/rust-lang/crates.io-index"
5680checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
2535checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9"
2536dependencies = [
2537 "async-lock",
2538 "crossbeam-channel",
2539 "crossbeam-epoch",
2540 "crossbeam-utils",
2541 "equivalent",
2542 "event-listener",
2543 "futures-util",
2544 "parking_lot",
2545 "portable-atomic",
2546 "smallvec",
2547 "tagptr",
2548 "uuid",
2549]
56812550
56822551[[package]]
5683name = "pest"
5684version = "2.8.8"
2552name = "native-tls"
2553version = "0.2.18"
56852554source = "registry+https://github.com/rust-lang/crates.io-index"
5686checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2"
2555checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
56872556dependencies = [
5688 "memchr",
5689 "ucd-trie",
2557 "libc",
2558 "log 0.4.33",
2559 "openssl",
2560 "openssl-probe",
2561 "openssl-sys",
2562 "schannel",
2563 "security-framework",
2564 "security-framework-sys",
2565 "tempfile",
56902566]
56912567
56922568[[package]]
5693name = "pest_derive"
5694version = "2.8.8"
2569name = "nix"
2570version = "0.29.0"
56952571source = "registry+https://github.com/rust-lang/crates.io-index"
5696checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd"
2572checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
56972573dependencies = [
5698 "pest",
5699 "pest_generator",
2574 "bitflags",
2575 "cfg-if",
2576 "cfg_aliases",
2577 "libc",
2578 "memoffset",
57002579]
57012580
57022581[[package]]
5703name = "pest_generator"
5704version = "2.8.8"
2582name = "nonempty"
2583version = "0.7.0"
2584source = "registry+https://github.com/rust-lang/crates.io-index"
2585checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
2586
2587[[package]]
2588name = "num-bigint"
2589version = "0.4.8"
57052590source = "registry+https://github.com/rust-lang/crates.io-index"
5706checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6"
2591checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
57072592dependencies = [
5708 "pest",
5709 "pest_meta",
5710 "proc-macro2",
5711 "quote",
5712 "syn 2.0.119",
2593 "num-integer",
2594 "num-traits",
57132595]
57142596
57152597[[package]]
5716name = "pest_meta"
5717version = "2.8.8"
2598name = "num-bigint"
2599version = "0.5.1"
57182600source = "registry+https://github.com/rust-lang/crates.io-index"
5719checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c"
2601checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
57202602dependencies = [
5721 "pest",
2603 "num-integer",
2604 "num-traits",
57222605]
57232606
57242607[[package]]
5725name = "petgraph"
5726version = "0.8.3"
2608name = "num-complex"
2609version = "0.4.6"
57272610source = "registry+https://github.com/rust-lang/crates.io-index"
5728checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
2611checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
57292612dependencies = [
5730 "fixedbitset",
5731 "hashbrown 0.15.5",
5732 "indexmap",
2613 "num-traits",
57332614]
57342615
57352616[[package]]
5736name = "pgvector"
5737version = "0.4.2"
2617name = "num-conv"
2618version = "0.2.2"
57382619source = "registry+https://github.com/rust-lang/crates.io-index"
5739checksum = "3673cba5b9a124916096a423b806a9f29620972c6c97b08db5f2053e9428b481"
5740dependencies = [
5741 "serde",
5742]
2620checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
57432621
57442622[[package]]
5745name = "phc"
5746version = "0.6.1"
2623name = "num-integer"
2624version = "0.1.47"
57472625source = "registry+https://github.com/rust-lang/crates.io-index"
5748checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
2626checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
57492627dependencies = [
5750 "base64ct",
5751 "ctutils",
2628 "num-traits",
57522629]
57532630
57542631[[package]]
5755name = "pin-project"
5756version = "1.1.13"
2632name = "num-rational"
2633version = "0.4.2"
57572634source = "registry+https://github.com/rust-lang/crates.io-index"
5758checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
2635checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
57592636dependencies = [
5760 "pin-project-internal",
2637 "num-bigint 0.4.8",
2638 "num-integer",
2639 "num-traits",
57612640]
57622641
57632642[[package]]
5764name = "pin-project-internal"
5765version = "1.1.13"
2643name = "num-traits"
2644version = "0.2.19"
57662645source = "registry+https://github.com/rust-lang/crates.io-index"
5767checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
2646checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
57682647dependencies = [
5769 "proc-macro2",
5770 "quote",
5771 "syn 2.0.119",
2648 "autocfg",
2649 "libm",
57722650]
5773
5774[[package]]
5775name = "pin-project-lite"
5776version = "0.2.17"
5777source = "registry+https://github.com/rust-lang/crates.io-index"
5778checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
57792651
57802652[[package]]
5781name = "pkcs1"
5782version = "0.7.5"
2653name = "num_cpus"
2654version = "1.17.0"
57832655source = "registry+https://github.com/rust-lang/crates.io-index"
5784checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
2656checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
57852657dependencies = [
5786 "der 0.7.10",
5787 "pkcs8 0.10.2",
5788 "spki 0.7.3",
2658 "hermit-abi",
2659 "libc",
57892660]
57902661
57912662[[package]]
5792name = "pkcs1"
5793version = "0.8.0-rc.4"
2663name = "object"
2664version = "0.39.1"
57942665source = "registry+https://github.com/rust-lang/crates.io-index"
5795checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e"
2666checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
57962667dependencies = [
5797 "der 0.8.1",
5798 "spki 0.8.0",
2668 "memchr",
57992669]
58002670
58012671[[package]]
5802name = "pkcs5"
5803version = "0.8.1"
2672name = "once_cell"
2673version = "1.21.4"
2674source = "registry+https://github.com/rust-lang/crates.io-index"
2675checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
2676
2677[[package]]
2678name = "opaque-debug"
2679version = "0.3.1"
58042680source = "registry+https://github.com/rust-lang/crates.io-index"
5805checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca"
5806dependencies = [
5807 "aes",
5808 "aes-gcm",
5809 "cbc",
5810 "der 0.8.1",
5811 "pbkdf2 0.13.0",
5812 "rand_core 0.10.1",
5813 "scrypt",
5814 "sha2 0.11.0",
5815 "spki 0.8.0",
5816]
2681checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
58172682
58182683[[package]]
5819name = "pkcs8"
5820version = "0.10.2"
2684name = "openssl"
2685version = "0.10.81"
58212686source = "registry+https://github.com/rust-lang/crates.io-index"
5822checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
2687checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
58232688dependencies = [
5824 "der 0.7.10",
5825 "spki 0.7.3",
2689 "bitflags",
2690 "cfg-if",
2691 "foreign-types",
2692 "libc",
2693 "openssl-macros",
2694 "openssl-sys",
58262695]
58272696
58282697[[package]]
5829name = "pkcs8"
5830version = "0.11.0"
2698name = "openssl-macros"
2699version = "0.1.1"
58312700source = "registry+https://github.com/rust-lang/crates.io-index"
5832checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
2701checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
58332702dependencies = [
5834 "der 0.8.1",
5835 "pkcs5",
5836 "rand_core 0.10.1",
5837 "spki 0.8.0",
2703 "proc-macro2",
2704 "quote",
2705 "syn 2.0.119",
58382706]
58392707
58402708[[package]]
5841name = "pkg-config"
5842version = "0.3.33"
2709name = "openssl-probe"
2710version = "0.2.1"
58432711source = "registry+https://github.com/rust-lang/crates.io-index"
5844checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
2712checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
58452713
58462714[[package]]
5847name = "pluralizer"
5848version = "0.5.0"
2715name = "openssl-sys"
2716version = "0.9.117"
58492717source = "registry+https://github.com/rust-lang/crates.io-index"
5850checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe"
2718checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
58512719dependencies = [
5852 "once_cell",
5853 "regex",
2720 "cc",
2721 "libc",
2722 "pkg-config",
2723 "vcpkg",
58542724]
58552725
58562726[[package]]
5857name = "polling"
5858version = "3.11.0"
2727name = "option-ext"
2728version = "0.2.0"
58592729source = "registry+https://github.com/rust-lang/crates.io-index"
5860checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
5861dependencies = [
5862 "cfg-if",
5863 "concurrent-queue",
5864 "hermit-abi",
5865 "pin-project-lite",
5866 "rustix",
5867 "windows-sys 0.61.2",
5868]
2730checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
58692731
58702732[[package]]
5871name = "poly1305"
5872version = "0.9.1"
2733name = "ordered-float"
2734version = "4.6.0"
58732735source = "registry+https://github.com/rust-lang/crates.io-index"
5874checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
2736checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
58752737dependencies = [
5876 "cpufeatures 0.3.0",
5877 "universal-hash",
5878 "zeroize",
2738 "num-traits",
58792739]
58802740
58812741[[package]]
5882name = "polyval"
5883version = "0.7.3"
2742name = "ordered-multimap"
2743version = "0.4.3"
58842744source = "registry+https://github.com/rust-lang/crates.io-index"
5885checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd"
2745checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
58862746dependencies = [
5887 "cpubits",
5888 "cpufeatures 0.3.0",
5889 "universal-hash",
2747 "dlv-list",
2748 "hashbrown 0.12.3",
58902749]
58912750
58922751[[package]]
5893name = "portable-atomic"
5894version = "1.14.0"
2752name = "ouroboros"
2753version = "0.18.5"
58952754source = "registry+https://github.com/rust-lang/crates.io-index"
5896checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
2755checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
2756dependencies = [
2757 "aliasable",
2758 "ouroboros_macro",
2759 "static_assertions",
2760]
58972761
58982762[[package]]
5899name = "portable-atomic-util"
5900version = "0.2.7"
2763name = "ouroboros_macro"
2764version = "0.18.5"
59012765source = "registry+https://github.com/rust-lang/crates.io-index"
5902checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
2766checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
59032767dependencies = [
5904 "portable-atomic",
2768 "heck 0.4.1",
2769 "proc-macro2",
2770 "proc-macro2-diagnostics",
2771 "quote",
2772 "syn 2.0.119",
59052773]
59062774
59072775[[package]]
5908name = "post"
5909version = "1.94.0"
2776name = "oxcache"
2777version = "0.4.3"
2778source = "registry+https://github.com/rust-lang/crates.io-index"
2779checksum = "985b963bd0d5a36076847f9bb33cb1de9a1d91ceb2a03d6d8b1b50e86118a5fb"
59102780dependencies = [
5911 "async-graphql",
59122781 "async-trait",
5913 "axum",
5914 "axum-extra",
5915 "bytes",
5916 "cache",
5917 "config 1.94.0",
5918 "db",
5919 "export",
5920 "futures",
5921 "http",
5922 "jwt",
5923 "log",
5924 "metrics",
5925 "object_store",
5926 "rate_limit",
5927 "sea-orm",
2782 "chrono",
2783 "dashmap",
2784 "icu",
2785 "moka",
2786 "once_cell",
2787 "redis",
2788 "regex",
59282789 "serde",
59292790 "serde_json",
5930 "space",
5931 "storage",
5932 "thiserror 2.0.19",
5933 "time",
2791 "serde_stacker",
59342792 "tokio",
5935 "tower",
5936 "tracing",
5937 "utoipa",
59382793 "uuid",
5939 "validator",
2794 "writeable",
2795]
2796
2797[[package]]
2798name = "parking"
2799version = "2.2.1"
2800source = "registry+https://github.com/rust-lang/crates.io-index"
2801checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
2802
2803[[package]]
2804name = "parking_lot"
2805version = "0.12.5"
2806source = "registry+https://github.com/rust-lang/crates.io-index"
2807checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
2808dependencies = [
2809 "lock_api",
2810 "parking_lot_core",
59402811]
59412812
59422813[[package]]
5943name = "potential_utf"
5944version = "0.1.5"
2814name = "parking_lot_core"
2815version = "0.9.12"
59452816source = "registry+https://github.com/rust-lang/crates.io-index"
5946checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
2817checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
59472818dependencies = [
5948 "zerovec",
2819 "cfg-if",
2820 "libc",
2821 "redox_syscall",
2822 "smallvec",
2823 "windows-link",
59492824]
59502825
59512826[[package]]
5952name = "powerfmt"
5953version = "0.2.0"
2827name = "percent-encoding"
2828version = "1.0.1"
59542829source = "registry+https://github.com/rust-lang/crates.io-index"
5955checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2830checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
59562831
59572832[[package]]
5958name = "ppv-lite86"
5959version = "0.2.21"
2833name = "percent-encoding"
2834version = "2.3.2"
59602835source = "registry+https://github.com/rust-lang/crates.io-index"
5961checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
5962dependencies = [
5963 "zerocopy",
5964]
2836checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
59652837
59662838[[package]]
5967name = "prettyplease"
5968version = "0.2.37"
2839name = "pgvector"
2840version = "0.4.2"
59692841source = "registry+https://github.com/rust-lang/crates.io-index"
5970checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2842checksum = "3673cba5b9a124916096a423b806a9f29620972c6c97b08db5f2053e9428b481"
59712843dependencies = [
5972 "proc-macro2",
5973 "syn 2.0.119",
2844 "serde",
59742845]
59752846
59762847[[package]]
5977name = "primefield"
5978version = "0.14.0"
2848name = "pin-project-lite"
2849version = "0.2.17"
59792850source = "registry+https://github.com/rust-lang/crates.io-index"
5980checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
5981dependencies = [
5982 "crypto-bigint 0.7.5",
5983 "crypto-common 0.2.2",
5984 "ff 0.14.0",
5985 "rand_core 0.10.1",
5986 "subtle",
5987 "zeroize",
5988]
2851checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
59892852
5990[[package]]
5991name = "primeorder"
5992version = "0.13.6"
2853[[package]]
2854name = "pkg-config"
2855version = "0.3.34"
59932856source = "registry+https://github.com/rust-lang/crates.io-index"
5994checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
5995dependencies = [
5996 "elliptic-curve 0.13.8",
5997]
2857checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
59982858
59992859[[package]]
6000name = "primeorder"
6001version = "0.14.0"
2860name = "pluralizer"
2861version = "0.5.0"
60022862source = "registry+https://github.com/rust-lang/crates.io-index"
6003checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
2863checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe"
60042864dependencies = [
6005 "elliptic-curve 0.14.1",
60062865 "once_cell",
6007 "primefield",
6008 "serdect",
6009 "wnaf",
2866 "regex",
60102867]
60112868
60122869[[package]]
6013name = "proc-macro-crate"
6014version = "3.5.0"
2870name = "polyval"
2871version = "0.6.2"
60152872source = "registry+https://github.com/rust-lang/crates.io-index"
6016checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
2873checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
60172874dependencies = [
6018 "toml_edit",
2875 "cfg-if",
2876 "cpufeatures 0.2.17",
2877 "opaque-debug",
2878 "universal-hash 0.5.1",
60192879]
60202880
60212881[[package]]
6022name = "proc-macro-error-attr3"
6023version = "3.1.0"
2882name = "polyval"
2883version = "0.7.3"
60242884source = "registry+https://github.com/rust-lang/crates.io-index"
6025checksum = "b0084e6206a967a2dad822180626b2f6b07a3b379325e8f1ec0438e33a469ba7"
2885checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd"
60262886dependencies = [
6027 "proc-macro2",
6028 "quote",
2887 "cpubits",
2888 "cpufeatures 0.3.0",
2889 "universal-hash 0.6.1",
60292890]
60302891
60312892[[package]]
6032name = "proc-macro-error3"
6033version = "3.1.0"
2893name = "portable-atomic"
2894version = "1.15.0"
60342895source = "registry+https://github.com/rust-lang/crates.io-index"
6035checksum = "0cf066225f2373bc711684792b69bdeac0356019b007e721090c24d92d5d5a50"
6036dependencies = [
6037 "proc-macro-error-attr3",
6038 "proc-macro2",
6039 "quote",
6040 "syn 3.0.3",
6041]
2896checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
60422897
60432898[[package]]
6044name = "proc-macro2"
6045version = "1.0.107"
2899name = "potential_utf"
2900version = "0.1.6"
60462901source = "registry+https://github.com/rust-lang/crates.io-index"
6047checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
2902checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
60482903dependencies = [
6049 "unicode-ident",
2904 "serde_core",
2905 "writeable",
2906 "zerovec",
60502907]
60512908
60522909[[package]]
6053name = "proc-macro2-diagnostics"
6054version = "0.10.1"
2910name = "powerfmt"
2911version = "0.2.0"
60552912source = "registry+https://github.com/rust-lang/crates.io-index"
6056checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
6057dependencies = [
6058 "proc-macro2",
6059 "quote",
6060 "syn 2.0.119",
6061 "version_check",
6062 "yansi",
6063]
2913checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
60642914
60652915[[package]]
6066name = "prodash"
6067version = "31.0.0"
2916name = "ppv-lite86"
2917version = "0.2.21"
60682918source = "registry+https://github.com/rust-lang/crates.io-index"
6069checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c"
2919checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
60702920dependencies = [
6071 "bytesize",
6072 "human_format",
6073 "parking_lot",
2921 "zerocopy",
60742922]
60752923
60762924[[package]]
6077name = "project"
6078version = "1.94.0"
6079dependencies = [
6080 "async-graphql",
6081 "async-trait",
6082 "axum",
6083 "axum-extra",
6084 "cache",
6085 "config 1.94.0",
6086 "db",
6087 "export",
6088 "futures",
6089 "jwt",
6090 "log",
6091 "metrics",
6092 "rate_limit",
6093 "sea-orm",
6094 "serde",
6095 "serde_json",
6096 "space",
6097 "thiserror 2.0.19",
6098 "time",
6099 "tokio",
6100 "tracing",
6101 "utoipa",
6102 "uuid",
6103 "validator",
6104]
6105
6106[[package]]
6107name = "prost"
6108version = "0.14.4"
2925name = "proc-macro-crate"
2926version = "3.5.0"
61092927source = "registry+https://github.com/rust-lang/crates.io-index"
6110checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
2928checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
61112929dependencies = [
6112 "bytes",
6113 "prost-derive",
2930 "toml_edit",
61142931]
61152932
61162933[[package]]
6117name = "prost-build"
6118version = "0.14.4"
2934name = "proc-macro2"
2935version = "1.0.107"
61192936source = "registry+https://github.com/rust-lang/crates.io-index"
6120checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
2937checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
61212938dependencies = [
6122 "heck 0.5.0",
6123 "itertools 0.14.0",
6124 "log",
6125 "multimap",
6126 "petgraph",
6127 "prettyplease",
6128 "prost",
6129 "prost-types",
6130 "pulldown-cmark",
6131 "pulldown-cmark-to-cmark",
6132 "regex",
6133 "syn 2.0.119",
6134 "tempfile",
2939 "unicode-ident",
61352940]
61362941
61372942[[package]]
6138name = "prost-derive"
6139version = "0.14.4"
2943name = "proc-macro2-diagnostics"
2944version = "0.10.1"
61402945source = "registry+https://github.com/rust-lang/crates.io-index"
6141checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
2946checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
61422947dependencies = [
6143 "anyhow",
6144 "itertools 0.14.0",
61452948 "proc-macro2",
61462949 "quote",
61472950 "syn 2.0.119",
2951 "version_check 0.9.5",
2952 "yansi",
61482953]
61492954
61502955[[package]]
6151name = "prost-types"
6152version = "0.14.4"
2956name = "psm"
2957version = "0.1.32"
61532958source = "registry+https://github.com/rust-lang/crates.io-index"
6154checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
2959checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622"
61552960dependencies = [
6156 "prost",
2961 "ar_archive_writer",
2962 "cc",
61572963]
61582964
61592965[[package]]
@@ -6174,113 +2980,16 @@
61742980 "proc-macro2",
61752981 "quote",
61762982 "syn 1.0.109",
6177]
6178
6179[[package]]
6180name = "pulldown-cmark"
6181version = "0.13.4"
6182source = "registry+https://github.com/rust-lang/crates.io-index"
6183checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
6184dependencies = [
6185 "bitflags 2.13.1",
6186 "memchr",
6187 "unicase",
6188]
6189
6190[[package]]
6191name = "pulldown-cmark-to-cmark"
6192version = "22.0.1"
6193source = "registry+https://github.com/rust-lang/crates.io-index"
6194checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60"
6195dependencies = [
6196 "pulldown-cmark",
6197]
6198
6199[[package]]
6200name = "queue"
6201version = "1.94.0"
6202dependencies = [
6203 "async-trait",
6204 "bytes",
6205 "config 1.94.0",
6206 "futures",
6207 "lombok",
6208 "metrics",
6209 "rdkafka",
6210 "serde",
6211 "serde_json",
6212 "thiserror 2.0.19",
6213 "tokio",
6214 "tokio-util",
6215 "tracing",
6216 "uuid",
62172983]
62182984
62192985[[package]]
62202986name = "quick-xml"
6221version = "0.41.0"
6222source = "registry+https://github.com/rust-lang/crates.io-index"
6223checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
6224dependencies = [
6225 "memchr",
6226 "serde",
6227]
6228
6229[[package]]
6230name = "quinn"
6231version = "0.11.11"
6232source = "registry+https://github.com/rust-lang/crates.io-index"
6233checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
6234dependencies = [
6235 "bytes",
6236 "cfg_aliases",
6237 "pin-project-lite",
6238 "quinn-proto",
6239 "quinn-udp",
6240 "rustc-hash",
6241 "rustls",
6242 "socket2",
6243 "thiserror 2.0.19",
6244 "tokio",
6245 "tracing",
6246 "web-time",
6247]
6248
6249[[package]]
6250name = "quinn-proto"
6251version = "0.11.16"
6252source = "registry+https://github.com/rust-lang/crates.io-index"
6253checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
6254dependencies = [
6255 "aws-lc-rs",
6256 "bytes",
6257 "getrandom 0.4.3",
6258 "lru-slab",
6259 "rand 0.10.2",
6260 "rand_pcg",
6261 "ring",
6262 "rustc-hash",
6263 "rustls",
6264 "rustls-pki-types",
6265 "slab",
6266 "thiserror 2.0.19",
6267 "tinyvec",
6268 "tracing",
6269 "web-time",
6270]
6271
6272[[package]]
6273name = "quinn-udp"
6274version = "0.5.15"
2987version = "0.28.2"
62752988source = "registry+https://github.com/rust-lang/crates.io-index"
6276checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
2989checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
62772990dependencies = [
6278 "cfg_aliases",
6279 "libc",
6280 "once_cell",
6281 "socket2",
6282 "tracing",
6283 "windows-sys 0.61.2",
2991 "memchr",
2992 "serde",
62842993]
62852994
62862995[[package]]
@@ -6291,12 +3000,6 @@
62913000dependencies = [
62923001 "proc-macro2",
62933002]
6294
6295[[package]]
6296name = "quoted_printable"
6297version = "0.5.2"
6298source = "registry+https://github.com/rust-lang/crates.io-index"
6299checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
63003003
63013004[[package]]
63023005name = "r-efi"
@@ -6323,22 +3026,12 @@
63233026checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
63243027dependencies = [
63253028 "libc",
6326 "rand_chacha 0.3.1",
3029 "rand_chacha",
63273030 "rand_core 0.6.4",
63283031]
63293032
63303033[[package]]
63313034name = "rand"
6332version = "0.9.5"
6333source = "registry+https://github.com/rust-lang/crates.io-index"
6334checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
6335dependencies = [
6336 "rand_chacha 0.9.0",
6337 "rand_core 0.9.5",
6338]
6339
6340[[package]]
6341name = "rand"
63423035version = "0.10.2"
63433036source = "registry+https://github.com/rust-lang/crates.io-index"
63443037checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
@@ -6359,16 +3052,6 @@
63593052]
63603053
63613054[[package]]
6362name = "rand_chacha"
6363version = "0.9.0"
6364source = "registry+https://github.com/rust-lang/crates.io-index"
6365checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
6366dependencies = [
6367 "ppv-lite86",
6368 "rand_core 0.9.5",
6369]
6370
6371[[package]]
63723055name = "rand_core"
63733056version = "0.6.4"
63743057source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6379,95 +3062,20 @@
63793062
63803063[[package]]
63813064name = "rand_core"
6382version = "0.9.5"
6383source = "registry+https://github.com/rust-lang/crates.io-index"
6384checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
6385dependencies = [
6386 "getrandom 0.3.4",
6387]
6388
6389[[package]]
6390name = "rand_core"
63913065version = "0.10.1"
63923066source = "registry+https://github.com/rust-lang/crates.io-index"
63933067checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
6394
6395[[package]]
6396name = "rand_pcg"
6397version = "0.10.2"
6398source = "registry+https://github.com/rust-lang/crates.io-index"
6399checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
6400dependencies = [
6401 "rand_core 0.10.1",
6402]
6403
6404[[package]]
6405name = "rapidhash"
6406version = "4.5.1"
6407source = "registry+https://github.com/rust-lang/crates.io-index"
6408checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e"
6409dependencies = [
6410 "rustversion",
6411]
6412
6413[[package]]
6414name = "rate_limit"
6415version = "1.94.0"
6416dependencies = [
6417 "axum",
6418 "axum-extra",
6419 "cache",
6420 "config 1.94.0",
6421 "http",
6422 "metrics",
6423 "serde",
6424 "serde_json",
6425 "thiserror 2.0.19",
6426 "time",
6427 "tokio",
6428 "tracing",
6429 "uuid",
6430]
6431
6432[[package]]
6433name = "rdkafka"
6434version = "0.39.0"
6435source = "registry+https://github.com/rust-lang/crates.io-index"
6436checksum = "d7956f9ac12b5712e50372d9749a3102f4810a8d42481c5eae3748d36d585bcf"
6437dependencies = [
6438 "futures-channel",
6439 "futures-util",
6440 "libc",
6441 "log",
6442 "rdkafka-sys",
6443 "serde",
6444 "serde_derive",
6445 "serde_json",
6446 "slab",
6447 "tokio",
6448]
6449
6450[[package]]
6451name = "rdkafka-sys"
6452version = "4.10.0+2.12.1"
6453source = "registry+https://github.com/rust-lang/crates.io-index"
6454checksum = "e234cf318915c1059d4921ef7f75616b5219b10b46e9f3a511a15eb4b56a3f77"
6455dependencies = [
6456 "cmake",
6457 "libc",
6458 "libz-sys",
6459 "num_enum",
6460 "pkg-config",
6461]
64623068
64633069[[package]]
64643070name = "redis"
6465version = "1.5.0"
3071version = "1.6.0"
64663072source = "registry+https://github.com/rust-lang/crates.io-index"
6467checksum = "3257df217f7eab0044627a268c9cc6cdb60c0c421c88f83ac41c4e31520b6b84"
3073checksum = "e37a4ca5c6ca42aa3e6df2fd32b987a65d32a4c2159a6f3fe0fd1df306a2658f"
64683074dependencies = [
3075 "arc-swap",
64693076 "arcstr",
64703077 "async-lock",
3078 "backon",
64713079 "bytes",
64723080 "cfg-if",
64733081 "combine",
@@ -6475,17 +3083,17 @@
64753083 "futures-channel",
64763084 "futures-util",
64773085 "itoa",
6478 "log",
6479 "num-bigint",
6480 "percent-encoding",
3086 "log 0.4.33",
3087 "num-bigint 0.5.1",
3088 "percent-encoding 2.3.2",
64813089 "pin-project-lite",
64823090 "rand 0.10.2",
64833091 "ryu",
64843092 "sha1_smol",
6485 "socket2",
3093 "socket2 0.6.5",
64863094 "tokio",
64873095 "tokio-util",
6488 "url",
3096 "url 2.5.8",
64893097 "xxhash-rust",
64903098]
64913099
@@ -6495,205 +3103,56 @@
64953103source = "registry+https://github.com/rust-lang/crates.io-index"
64963104checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
64973105dependencies = [
6498 "bitflags 2.13.1",
3106 "bitflags",
64993107]
65003108
65013109[[package]]
6502name = "ref-cast"
6503version = "1.0.26"
3110name = "redox_users"
3111version = "0.4.6"
65043112source = "registry+https://github.com/rust-lang/crates.io-index"
6505checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d"
3113checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
65063114dependencies = [
6507 "ref-cast-impl",
6508]
6509
6510[[package]]
6511name = "ref-cast-impl"
6512version = "1.0.26"
6513source = "registry+https://github.com/rust-lang/crates.io-index"
6514checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c"
6515dependencies = [
6516 "proc-macro2",
6517 "quote",
6518 "syn 3.0.3",
6519]
6520
6521[[package]]
6522name = "regex"
6523version = "1.13.1"
6524source = "registry+https://github.com/rust-lang/crates.io-index"
6525checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
6526dependencies = [
6527 "aho-corasick",
6528 "memchr",
6529 "regex-automata",
6530 "regex-syntax",
6531]
6532
6533[[package]]
6534name = "regex-automata"
6535version = "0.4.16"
6536source = "registry+https://github.com/rust-lang/crates.io-index"
6537checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
6538dependencies = [
6539 "aho-corasick",
6540 "memchr",
6541 "regex-syntax",
6542]
6543
6544[[package]]
6545name = "regex-syntax"
6546version = "0.8.11"
6547source = "registry+https://github.com/rust-lang/crates.io-index"
6548checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
6549
6550[[package]]
6551name = "rend"
6552version = "0.4.2"
6553source = "registry+https://github.com/rust-lang/crates.io-index"
6554checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
6555dependencies = [
6556 "bytecheck",
6557]
6558
6559[[package]]
6560name = "repo"
6561version = "1.94.0"
6562dependencies = [
6563 "async-graphql",
6564 "async-trait",
6565 "axum",
6566 "axum-extra",
6567 "cache",
6568 "config 1.94.0",
6569 "db",
6570 "export",
6571 "futures",
6572 "git",
6573 "gix",
6574 "http",
6575 "jwt",
6576 "log",
6577 "metrics",
6578 "rate_limit",
6579 "sea-orm",
6580 "serde",
6581 "serde_json",
6582 "space",
6583 "storage",
6584 "thiserror 2.0.19",
6585 "time",
6586 "tokio",
6587 "tower",
6588 "tracing",
6589 "utoipa",
6590 "uuid",
6591 "validator",
6592]
6593
6594[[package]]
6595name = "reqwest"
6596version = "0.12.28"
6597source = "registry+https://github.com/rust-lang/crates.io-index"
6598checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
6599dependencies = [
6600 "base64 0.22.1",
6601 "bytes",
6602 "futures-channel",
6603 "futures-core",
6604 "futures-util",
6605 "h2",
6606 "http",
6607 "http-body",
6608 "http-body-util",
6609 "hyper",
6610 "hyper-rustls",
6611 "hyper-util",
6612 "js-sys",
6613 "log",
6614 "percent-encoding",
6615 "pin-project-lite",
6616 "quinn",
6617 "rustls",
6618 "rustls-pki-types",
6619 "serde",
6620 "serde_json",
6621 "serde_urlencoded",
6622 "sync_wrapper",
6623 "tokio",
6624 "tokio-rustls",
6625 "tokio-util",
6626 "tower",
6627 "tower-http",
6628 "tower-service",
6629 "url",
6630 "wasm-bindgen",
6631 "wasm-bindgen-futures",
6632 "wasm-streams 0.4.2",
6633 "web-sys",
6634 "webpki-roots",
3115 "getrandom 0.2.17",
3116 "libredox",
3117 "thiserror 1.0.69",
66353118]
66363119
66373120[[package]]
6638name = "reqwest"
6639version = "0.13.4"
3121name = "regex"
3122version = "1.13.1"
66403123source = "registry+https://github.com/rust-lang/crates.io-index"
6641checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
3124checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
66423125dependencies = [
6643 "base64 0.22.1",
6644 "bytes",
6645 "futures-core",
6646 "futures-util",
6647 "h2",
6648 "http",
6649 "http-body",
6650 "http-body-util",
6651 "hyper",
6652 "hyper-rustls",
6653 "hyper-util",
6654 "js-sys",
6655 "log",
6656 "percent-encoding",
6657 "pin-project-lite",
6658 "quinn",
6659 "rustls",
6660 "rustls-pki-types",
6661 "rustls-platform-verifier",
6662 "serde",
6663 "serde_json",
6664 "serde_urlencoded",
6665 "sync_wrapper",
6666 "tokio",
6667 "tokio-rustls",
6668 "tokio-util",
6669 "tower",
6670 "tower-http",
6671 "tower-service",
6672 "url",
6673 "wasm-bindgen",
6674 "wasm-bindgen-futures",
6675 "wasm-streams 0.5.0",
6676 "web-sys",
3126 "aho-corasick",
3127 "memchr",
3128 "regex-automata",
3129 "regex-syntax",
66773130]
66783131
66793132[[package]]
6680name = "rfc6979"
6681version = "0.4.0"
3133name = "regex-automata"
3134version = "0.4.18"
66823135source = "registry+https://github.com/rust-lang/crates.io-index"
6683checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
3136checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
66843137dependencies = [
6685 "hmac 0.12.1",
6686 "subtle",
3138 "aho-corasick",
3139 "memchr",
3140 "regex-syntax",
66873141]
66883142
66893143[[package]]
6690name = "rfc6979"
6691version = "0.6.0"
3144name = "regex-syntax"
3145version = "0.8.11"
3146source = "registry+https://github.com/rust-lang/crates.io-index"
3147checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
3148
3149[[package]]
3150name = "rend"
3151version = "0.4.2"
66923152source = "registry+https://github.com/rust-lang/crates.io-index"
6693checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
3153checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
66943154dependencies = [
6695 "crypto-bigint 0.7.5",
6696 "hmac 0.13.0",
3155 "bytecheck",
66973156]
66983157
66993158[[package]]
@@ -6706,7 +3165,7 @@
67063165 "cfg-if",
67073166 "getrandom 0.2.17",
67083167 "libc",
6709 "untrusted 0.9.0",
3168 "untrusted",
67103169 "windows-sys 0.52.0",
67113170]
67123171
@@ -6740,182 +3199,49 @@
67403199]
67413200
67423201[[package]]
6743name = "rmcp"
6744version = "3.1.2"
3202name = "rust-ini"
3203version = "0.18.0"
67453204source = "registry+https://github.com/rust-lang/crates.io-index"
6746checksum = "c8dddc5b1924b9a59fba420166160ca2c4663a4e01803e52eda33070f56d63c8"
3205checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
67473206dependencies = [
6748 "async-trait",
6749 "base64 0.23.1",
6750 "bytes",
6751 "chrono",
6752 "futures",
6753 "http",
6754 "http-body",
6755 "http-body-util",
6756 "pastey",
6757 "pin-project-lite",
6758 "rand 0.10.2",
6759 "rmcp-macros",
6760 "schemars",
6761 "serde",
6762 "serde_json",
6763 "sse-stream",
6764 "thiserror 2.0.19",
6765 "tokio",
6766 "tokio-stream",
6767 "tokio-util",
6768 "tower-service",
6769 "tracing",
6770 "uuid",
6771]
6772
6773[[package]]
6774name = "rmcp-macros"
6775version = "3.1.2"
6776source = "registry+https://github.com/rust-lang/crates.io-index"
6777checksum = "6898e24cd16342b59bfa8a53c2c04b9cf62fc8a2cfea57b9c038b09984bfc521"
6778dependencies = [
6779 "darling 0.24.0",
6780 "proc-macro2",
6781 "quote",
6782 "serde_json",
6783 "syn 3.0.3",
6784]
6785
6786[[package]]
6787name = "rsa"
6788version = "0.9.10"
6789source = "registry+https://github.com/rust-lang/crates.io-index"
6790checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
6791dependencies = [
6792 "const-oid 0.9.6",
6793 "digest 0.10.7",
6794 "num-bigint-dig",
6795 "num-integer",
6796 "num-traits",
6797 "pkcs1 0.7.5",
6798 "pkcs8 0.10.2",
6799 "rand_core 0.6.4",
6800 "signature 2.2.0",
6801 "spki 0.7.3",
6802 "subtle",
6803 "zeroize",
6804]
6805
6806[[package]]
6807name = "rsa"
6808version = "0.10.0-rc.18"
6809source = "registry+https://github.com/rust-lang/crates.io-index"
6810checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
6811dependencies = [
6812 "const-oid 0.10.2",
6813 "crypto-bigint 0.7.5",
6814 "crypto-primes",
6815 "digest 0.11.3",
6816 "pkcs1 0.8.0-rc.4",
6817 "pkcs8 0.11.0",
6818 "rand_core 0.10.1",
6819 "sha2 0.11.0",
6820 "signature 3.0.0",
6821 "spki 0.8.0",
6822 "zeroize",
6823]
6824
6825[[package]]
6826name = "russh"
6827version = "0.62.2"
6828source = "registry+https://github.com/rust-lang/crates.io-index"
6829checksum = "ca6c3c1dd0a9ad3a9915a2f6e907d158f9a7e9ac32ddc772b003faafc027d9a8"
6830dependencies = [
6831 "aes",
6832 "aws-lc-rs",
6833 "bitflags 2.13.1",
6834 "block-padding",
6835 "byteorder",
6836 "bytes",
6837 "cbc",
6838 "cipher",
6839 "crypto-bigint 0.7.5",
6840 "ctr",
6841 "curve25519-dalek 5.0.0-rc.1",
6842 "data-encoding",
6843 "delegate",
6844 "der 0.8.1",
6845 "digest 0.11.3",
6846 "ecdsa 0.17.0",
6847 "ed25519-dalek 3.0.0-rc.1",
6848 "elliptic-curve 0.14.1",
6849 "enum_dispatch",
6850 "flate2",
6851 "futures",
6852 "generic-array 1.4.4",
6853 "getrandom 0.4.3",
6854 "ghash",
6855 "hex-literal",
6856 "hmac 0.13.0",
6857 "inout",
6858 "internal-russh-num-bigint",
6859 "keccak",
6860 "log",
6861 "md5",
6862 "ml-kem",
6863 "module-lattice",
6864 "num-bigint",
6865 "p256 0.14.0-rc.15",
6866 "p384 0.14.0-rc.15",
6867 "p521",
6868 "pageant",
6869 "pbkdf2 0.13.0",
6870 "pkcs1 0.8.0-rc.4",
6871 "pkcs5",
6872 "pkcs8 0.11.0",
6873 "polyval",
6874 "rand 0.10.2",
6875 "rand_core 0.10.1",
6876 "rsa 0.10.0-rc.18",
6877 "russh-cryptovec",
6878 "russh-util",
6879 "salsa20",
6880 "scrypt",
6881 "sec1 0.8.1",
6882 "sha1 0.11.0",
6883 "sha2 0.11.0",
6884 "sha3",
6885 "signature 3.0.0",
6886 "spki 0.8.0",
6887 "ssh-encoding",
6888 "ssh-key",
6889 "subtle",
6890 "thiserror 2.0.19",
6891 "tokio",
6892 "typenum",
6893 "universal-hash",
6894 "zeroize",
6895]
6896
6897[[package]]
6898name = "russh-cryptovec"
6899version = "0.62.0"
6900source = "registry+https://github.com/rust-lang/crates.io-index"
6901checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438"
6902dependencies = [
6903 "log",
6904 "nix 0.31.3",
6905 "ssh-encoding",
6906 "windows-sys 0.61.2",
3207 "cfg-if",
3208 "ordered-multimap",
69073209]
69083210
69093211[[package]]
6910name = "russh-util"
6911version = "0.52.0"
3212name = "rust-s3-async"
3213version = "0.35.0"
69123214source = "registry+https://github.com/rust-lang/crates.io-index"
6913checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6"
3215checksum = "b264f6d2ef36cd3a5720ff31119db1a66cc95c909a14593f6459ecd19000ade7"
69143216dependencies = [
3217 "async-trait",
3218 "aws-creds",
3219 "aws-region",
3220 "base64 0.21.7",
3221 "bytes",
3222 "cfg-if",
69153223 "chrono",
3224 "futures",
3225 "hex",
3226 "hmac 0.12.1",
3227 "http 0.2.12",
3228 "hyper 0.14.32",
3229 "hyper-native-tls",
3230 "hyper-tls",
3231 "md5",
3232 "minidom",
3233 "percent-encoding 2.3.2",
3234 "quick-xml",
3235 "serde",
3236 "sha2 0.10.9",
3237 "strum_macros",
3238 "thiserror 1.0.69",
3239 "time 0.3.55",
69163240 "tokio",
6917 "wasm-bindgen",
6918 "wasm-bindgen-futures",
3241 "tokio-native-tls",
3242 "tokio-stream",
3243 "tracing",
3244 "url 2.5.8",
69193245]
69203246
69213247[[package]]
@@ -6934,12 +3260,6 @@
69343260 "serde_json",
69353261 "wasm-bindgen",
69363262]
6937
6938[[package]]
6939name = "rustc-hash"
6940version = "2.1.3"
6941source = "registry+https://github.com/rust-lang/crates.io-index"
6942checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
69433263
69443264[[package]]
69453265name = "rustc_version"
@@ -6956,7 +3276,7 @@
69563276source = "registry+https://github.com/rust-lang/crates.io-index"
69573277checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
69583278dependencies = [
6959 "bitflags 2.13.1",
3279 "bitflags",
69603280 "errno",
69613281 "libc",
69623282 "linux-raw-sys",
@@ -6969,8 +3289,6 @@
69693289source = "registry+https://github.com/rust-lang/crates.io-index"
69703290checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
69713291dependencies = [
6972 "aws-lc-rs",
6973 "log",
69743292 "once_cell",
69753293 "ring",
69763294 "rustls-pki-types",
@@ -6980,71 +3298,47 @@
69803298]
69813299
69823300[[package]]
6983name = "rustls-native-certs"
6984version = "0.8.4"
6985source = "registry+https://github.com/rust-lang/crates.io-index"
6986checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
6987dependencies = [
6988 "openssl-probe",
6989 "rustls-pki-types",
6990 "schannel",
6991 "security-framework",
6992]
6993
6994[[package]]
69953301name = "rustls-pki-types"
69963302version = "1.15.1"
69973303source = "registry+https://github.com/rust-lang/crates.io-index"
69983304checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
69993305dependencies = [
7000 "web-time",
70013306 "zeroize",
70023307]
70033308
70043309[[package]]
7005name = "rustls-platform-verifier"
7006version = "0.7.0"
3310name = "rustls-webpki"
3311version = "0.103.14"
70073312source = "registry+https://github.com/rust-lang/crates.io-index"
7008checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
3313checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
70093314dependencies = [
7010 "core-foundation",
7011 "core-foundation-sys",
7012 "jni",
7013 "log",
7014 "once_cell",
7015 "rustls",
7016 "rustls-native-certs",
7017 "rustls-platform-verifier-android",
7018 "rustls-webpki",
7019 "security-framework",
7020 "security-framework-sys",
7021 "webpki-root-certs",
7022 "windows-sys 0.61.2",
3315 "ring",
3316 "rustls-pki-types",
3317 "untrusted",
70233318]
70243319
70253320[[package]]
7026name = "rustls-platform-verifier-android"
7027version = "0.1.1"
3321name = "rustversion"
3322version = "1.0.23"
70283323source = "registry+https://github.com/rust-lang/crates.io-index"
7029checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
3324checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
70303325
70313326[[package]]
7032name = "rustls-webpki"
7033version = "0.103.13"
3327name = "rxml"
3328version = "0.9.1"
70343329source = "registry+https://github.com/rust-lang/crates.io-index"
7035checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
3330checksum = "a98f186c7a2f3abbffb802984b7f1dfd65dac8be1aafdaabbca4137f53f0dff7"
70363331dependencies = [
7037 "aws-lc-rs",
7038 "ring",
7039 "rustls-pki-types",
7040 "untrusted 0.9.0",
3332 "bytes",
3333 "rxml_validation",
3334 "smartstring",
70413335]
70423336
70433337[[package]]
7044name = "rustversion"
7045version = "1.0.23"
3338name = "rxml_validation"
3339version = "0.9.1"
70463340source = "registry+https://github.com/rust-lang/crates.io-index"
7047checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
3341checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530"
70483342
70493343[[package]]
70503344name = "ryu"
@@ -7053,23 +3347,10 @@
70533347checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
70543348
70553349[[package]]
7056name = "salsa20"
7057version = "0.11.0"
3350name = "safemem"
3351version = "0.3.3"
70583352source = "registry+https://github.com/rust-lang/crates.io-index"
7059checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c"
7060dependencies = [
7061 "cfg-if",
7062 "cipher",
7063]
7064
7065[[package]]
7066name = "same-file"
7067version = "1.0.6"
7068source = "registry+https://github.com/rust-lang/crates.io-index"
7069checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
7070dependencies = [
7071 "winapi-util",
7072]
3353checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
70733354
70743355[[package]]
70753356name = "schannel"
@@ -7081,48 +3362,10 @@
70813362]
70823363
70833364[[package]]
7084name = "schemars"
7085version = "1.2.2"
7086source = "registry+https://github.com/rust-lang/crates.io-index"
7087checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
7088dependencies = [
7089 "chrono",
7090 "dyn-clone",
7091 "ref-cast",
7092 "schemars_derive",
7093 "serde",
7094 "serde_json",
7095]
7096
7097[[package]]
7098name = "schemars_derive"
7099version = "1.2.2"
7100source = "registry+https://github.com/rust-lang/crates.io-index"
7101checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba"
7102dependencies = [
7103 "proc-macro2",
7104 "quote",
7105 "serde_derive_internals",
7106 "syn 3.0.3",
7107]
7108
7109[[package]]
71103365name = "scopeguard"
71113366version = "1.2.0"
71123367source = "registry+https://github.com/rust-lang/crates.io-index"
71133368checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
7114
7115[[package]]
7116name = "scrypt"
7117version = "0.12.0"
7118source = "registry+https://github.com/rust-lang/crates.io-index"
7119checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0"
7120dependencies = [
7121 "cfg-if",
7122 "pbkdf2 0.13.0",
7123 "salsa20",
7124 "sha2 0.11.0",
7125]
71263369
71273370[[package]]
71283371name = "sea-bae"
@@ -7138,9 +3381,9 @@
71383381
71393382[[package]]
71403383name = "sea-orm"
7141version = "2.0.1"
3384version = "2.0.2"
71423385source = "registry+https://github.com/rust-lang/crates.io-index"
7143checksum = "e29e1a2e2e6da3d2486086f9d96b4bf5b7d8bf344e16c48bd87ce7977b8d0154"
3386checksum = "a334e83ced3ae3ee44db0f84d1fcf8d2087a1ad9bb9036f00f9f6067156ea197"
71443387dependencies = [
71453388 "async-stream",
71463389 "async-trait",
@@ -7149,9 +3392,8 @@
71493392 "derive-where",
71503393 "derive_more",
71513394 "futures-util",
7152 "inventory",
7153 "itertools 0.14.0",
7154 "log",
3395 "itertools",
3396 "log 0.4.33",
71553397 "mac_address",
71563398 "ouroboros",
71573399 "pgvector",
@@ -7165,11 +3407,11 @@
71653407 "serde_json",
71663408 "sqlx",
71673409 "sqlx-core",
7168 "strum 0.28.0",
7169 "thiserror 2.0.19",
7170 "time",
3410 "strum",
3411 "thiserror 2.0.20",
3412 "time 0.3.55",
71713413 "tracing",
7172 "url",
3414 "url 2.5.8",
71733415 "uuid",
71743416 "web-time",
71753417]
@@ -7182,17 +3424,17 @@
71823424dependencies = [
71833425 "arrow",
71843426 "sea-query",
7185 "thiserror 2.0.19",
3427 "thiserror 2.0.20",
71863428]
71873429
71883430[[package]]
71893431name = "sea-orm-macros"
7190version = "2.0.1"
3432version = "2.0.2"
71913433source = "registry+https://github.com/rust-lang/crates.io-index"
7192checksum = "88a6fd7ae2932a838d3bc7ca1e5483ffa10d6b25e4c43c8aebb4cd14773c2d08"
3434checksum = "4039a86f9acc4d3b52747508b347dddc6fd725bbc429902ebeb6d26225fc2528"
71933435dependencies = [
71943436 "heck 0.5.0",
7195 "itertools 0.14.0",
3437 "itertools",
71963438 "pluralizer",
71973439 "proc-macro2",
71983440 "quote",
@@ -7203,16 +3445,16 @@
72033445
72043446[[package]]
72053447name = "sea-query"
7206version = "1.0.1"
3448version = "1.0.2"
72073449source = "registry+https://github.com/rust-lang/crates.io-index"
7208checksum = "8d190cfb3bcceb8a8d7d04dee5a0c77f60c7627979cdcb47fdcb8934f009badf"
3450checksum = "546040c653a705e60ec65ecd3191a809603734bebbc225775916dea9ae409b31"
72093451dependencies = [
72103452 "chrono",
72113453 "ordered-float",
72123454 "rust_decimal",
72133455 "sea-query-derive",
72143456 "serde_json",
7215 "time",
3457 "time 0.3.55",
72163458 "uuid",
72173459]
72183460
@@ -7222,12 +3464,12 @@
72223464source = "registry+https://github.com/rust-lang/crates.io-index"
72233465checksum = "a0b0f466921cdd3cf4b89d5c3ac2173dba89a873ab395b123a645de181ec7537"
72243466dependencies = [
7225 "darling 0.20.11",
3467 "darling",
72263468 "heck 0.4.1",
72273469 "proc-macro2",
72283470 "quote",
72293471 "syn 2.0.119",
7230 "thiserror 2.0.19",
3472 "thiserror 2.0.20",
72313473]
72323474
72333475[[package]]
@@ -7238,105 +3480,46 @@
72383480dependencies = [
72393481 "sea-query",
72403482 "sqlx",
7241]
7242
7243[[package]]
7244name = "sea-schema"
7245version = "0.18.1"
7246source = "registry+https://github.com/rust-lang/crates.io-index"
7247checksum = "3553c77dceed56e95bece9ea876c4dd67ca879ef51055a0b97a7bb89a8ae4fed"
7248dependencies = [
7249 "async-trait",
7250 "sea-query",
7251 "sea-query-sqlx",
7252 "sea-schema-derive",
7253 "sqlx",
7254]
7255
7256[[package]]
7257name = "sea-schema-derive"
7258version = "0.3.0"
7259source = "registry+https://github.com/rust-lang/crates.io-index"
7260checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0"
7261dependencies = [
7262 "heck 0.4.1",
7263 "proc-macro2",
7264 "quote",
7265 "syn 2.0.119",
7266]
7267
7268[[package]]
7269name = "seahash"
7270version = "4.1.0"
7271source = "registry+https://github.com/rust-lang/crates.io-index"
7272checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
7273
7274[[package]]
7275name = "search"
7276version = "1.94.0"
7277dependencies = [
7278 "config 1.94.0",
7279 "lombok",
7280 "meilisearch-sdk",
7281 "metrics",
7282 "queue",
7283 "serde",
7284 "serde_json",
7285 "thiserror 2.0.19",
7286 "tokio",
7287 "tracing",
7288]
7289
7290[[package]]
7291name = "sec1"
7292version = "0.7.3"
7293source = "registry+https://github.com/rust-lang/crates.io-index"
7294checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
7295dependencies = [
7296 "base16ct 0.2.0",
7297 "der 0.7.10",
7298 "generic-array 0.14.9",
7299 "pkcs8 0.10.2",
7300 "subtle",
7301 "zeroize",
73023483]
73033484
73043485[[package]]
7305name = "sec1"
7306version = "0.8.1"
3486name = "sea-schema"
3487version = "0.18.1"
73073488source = "registry+https://github.com/rust-lang/crates.io-index"
7308checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
3489checksum = "3553c77dceed56e95bece9ea876c4dd67ca879ef51055a0b97a7bb89a8ae4fed"
73093490dependencies = [
7310 "base16ct 1.0.0",
7311 "ctutils",
7312 "der 0.8.1",
7313 "hybrid-array",
7314 "subtle",
7315 "zeroize",
3491 "async-trait",
3492 "sea-query",
3493 "sea-query-sqlx",
3494 "sea-schema-derive",
3495 "sqlx",
73163496]
73173497
73183498[[package]]
7319name = "secret"
7320version = "1.94.0"
3499name = "sea-schema-derive"
3500version = "0.3.0"
3501source = "registry+https://github.com/rust-lang/crates.io-index"
3502checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0"
73213503dependencies = [
7322 "aes-gcm",
7323 "base64 0.23.1",
7324 "hex",
7325 "rand 0.10.2",
7326 "serde",
7327 "serde_json",
7328 "sha2 0.10.9",
7329 "thiserror 2.0.19",
7330 "tracing",
3504 "heck 0.4.1",
3505 "proc-macro2",
3506 "quote",
3507 "syn 2.0.119",
73313508]
73323509
73333510[[package]]
3511name = "seahash"
3512version = "4.1.0"
3513source = "registry+https://github.com/rust-lang/crates.io-index"
3514checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
3515
3516[[package]]
73343517name = "security-framework"
73353518version = "3.7.0"
73363519source = "registry+https://github.com/rust-lang/crates.io-index"
73373520checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
73383521dependencies = [
7339 "bitflags 2.13.1",
3522 "bitflags",
73403523 "core-foundation",
73413524 "core-foundation-sys",
73423525 "libc",
@@ -7390,41 +3573,16 @@
73903573]
73913574
73923575[[package]]
7393name = "serde_derive_internals"
7394version = "0.30.0"
7395source = "registry+https://github.com/rust-lang/crates.io-index"
7396checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
7397dependencies = [
7398 "proc-macro2",
7399 "quote",
7400 "syn 3.0.3",
7401]
7402
7403[[package]]
74043576name = "serde_json"
74053577version = "1.0.151"
74063578source = "registry+https://github.com/rust-lang/crates.io-index"
74073579checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
74083580dependencies = [
7409 "indexmap",
74103581 "itoa",
74113582 "memchr",
74123583 "serde",
74133584 "serde_core",
74143585 "zmij",
7415]
7416
7417[[package]]
7418name = "serde_norway"
7419version = "0.9.42"
7420source = "registry+https://github.com/rust-lang/crates.io-index"
7421checksum = "e408f29489b5fd500fab51ff1484fc859bb655f32c671f307dcd733b72e8168c"
7422dependencies = [
7423 "indexmap",
7424 "itoa",
7425 "ryu",
7426 "serde",
7427 "unsafe-libyaml-norway",
74283586]
74293587
74303588[[package]]
@@ -7439,12 +3597,14 @@
74393597]
74403598
74413599[[package]]
7442name = "serde_spanned"
7443version = "1.1.1"
3600name = "serde_stacker"
3601version = "0.1.14"
74443602source = "registry+https://github.com/rust-lang/crates.io-index"
7445checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
3603checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a"
74463604dependencies = [
3605 "serde",
74473606 "serde_core",
3607 "stacker",
74483608]
74493609
74503610[[package]]
@@ -7460,40 +3620,19 @@
74603620]
74613621
74623622[[package]]
7463name = "serde_yaml"
7464version = "0.9.34+deprecated"
7465source = "registry+https://github.com/rust-lang/crates.io-index"
7466checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
3623name = "session"
3624version = "1.0.0"
74673625dependencies = [
7468 "indexmap",
7469 "itoa",
7470 "ryu",
7471 "serde",
7472 "unsafe-libyaml",
3626 "async-trait",
3627 "axum_session",
3628 "base64 0.23.1",
3629 "chrono",
3630 "config",
3631 "export",
3632 "redis",
74733633]
74743634
74753635[[package]]
7476name = "serdect"
7477version = "0.4.3"
7478source = "registry+https://github.com/rust-lang/crates.io-index"
7479checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
7480dependencies = [
7481 "base16ct 1.0.0",
7482 "serde",
7483]
7484
7485[[package]]
7486name = "sha1"
7487version = "0.10.7"
7488source = "registry+https://github.com/rust-lang/crates.io-index"
7489checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
7490dependencies = [
7491 "cfg-if",
7492 "cpufeatures 0.2.17",
7493 "digest 0.10.7",
7494]
7495
7496[[package]]
74973636name = "sha1"
74983637version = "0.11.0"
74993638source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7505,16 +3644,6 @@
75053644]
75063645
75073646[[package]]
7508name = "sha1-checked"
7509version = "0.10.0"
7510source = "registry+https://github.com/rust-lang/crates.io-index"
7511checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"
7512dependencies = [
7513 "digest 0.10.7",
7514 "sha1 0.10.7",
7515]
7516
7517[[package]]
75183647name = "sha1_smol"
75193648version = "1.0.1"
75203649source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7543,83 +3672,10 @@
75433672]
75443673
75453674[[package]]
7546name = "sha3"
7547version = "0.11.0"
7548source = "registry+https://github.com/rust-lang/crates.io-index"
7549checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
7550dependencies = [
7551 "digest 0.11.3",
7552 "keccak",
7553]
7554
7555[[package]]
7556name = "sharded-slab"
7557version = "0.1.7"
7558source = "registry+https://github.com/rust-lang/crates.io-index"
7559checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
7560dependencies = [
7561 "lazy_static",
7562]
7563
7564[[package]]
7565name = "shared_child"
7566version = "1.1.1"
7567source = "registry+https://github.com/rust-lang/crates.io-index"
7568checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
7569dependencies = [
7570 "libc",
7571 "sigchld",
7572 "windows-sys 0.60.2",
7573]
7574
7575[[package]]
7576name = "shared_thread"
7577version = "0.2.0"
7578source = "registry+https://github.com/rust-lang/crates.io-index"
7579checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0"
7580
7581[[package]]
7582name = "shell-words"
7583version = "1.1.1"
7584source = "registry+https://github.com/rust-lang/crates.io-index"
7585checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
7586
7587[[package]]
75883675name = "shlex"
75893676version = "2.0.1"
75903677source = "registry+https://github.com/rust-lang/crates.io-index"
75913678checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
7592
7593[[package]]
7594name = "sigchld"
7595version = "0.2.4"
7596source = "registry+https://github.com/rust-lang/crates.io-index"
7597checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
7598dependencies = [
7599 "libc",
7600 "os_pipe",
7601 "signal-hook 0.3.18",
7602]
7603
7604[[package]]
7605name = "signal-hook"
7606version = "0.3.18"
7607source = "registry+https://github.com/rust-lang/crates.io-index"
7608checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
7609dependencies = [
7610 "libc",
7611 "signal-hook-registry",
7612]
7613
7614[[package]]
7615name = "signal-hook"
7616version = "0.4.4"
7617source = "registry+https://github.com/rust-lang/crates.io-index"
7618checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d"
7619dependencies = [
7620 "libc",
7621 "signal-hook-registry",
7622]
76233679
76243680[[package]]
76253681name = "signal-hook-registry"
@@ -7632,72 +3688,45 @@
76323688]
76333689
76343690[[package]]
7635name = "signature"
7636version = "2.2.0"
3691name = "simdutf8"
3692version = "0.1.5"
76373693source = "registry+https://github.com/rust-lang/crates.io-index"
7638checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
7639dependencies = [
7640 "digest 0.10.7",
7641 "rand_core 0.6.4",
7642]
3694checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
76433695
76443696[[package]]
7645name = "signature"
7646version = "3.0.0"
3697name = "slab"
3698version = "0.4.12"
76473699source = "registry+https://github.com/rust-lang/crates.io-index"
7648checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
7649dependencies = [
7650 "digest 0.11.3",
7651 "rand_core 0.10.1",
7652]
3700checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
76533701
76543702[[package]]
7655name = "simd-adler32"
7656version = "0.3.10"
3703name = "smallvec"
3704version = "1.15.2"
76573705source = "registry+https://github.com/rust-lang/crates.io-index"
7658checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
7659
7660[[package]]
7661name = "simd_cesu8"
7662version = "1.2.0"
7663source = "registry+https://github.com/rust-lang/crates.io-index"
7664checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
3706checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
76653707dependencies = [
7666 "rustc_version",
7667 "simdutf8",
3708 "serde",
76683709]
76693710
76703711[[package]]
7671name = "simdutf8"
7672version = "0.1.5"
3712name = "smartstring"
3713version = "1.0.1"
76733714source = "registry+https://github.com/rust-lang/crates.io-index"
7674checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
7675
7676[[package]]
7677name = "simple_asn1"
7678version = "0.6.4"
7679source = "registry+https://github.com/rust-lang/crates.io-index"
7680checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
3715checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
76813716dependencies = [
7682 "num-bigint",
7683 "num-traits",
7684 "thiserror 2.0.19",
7685 "time",
3717 "autocfg",
3718 "static_assertions",
3719 "version_check 0.9.5",
76863720]
7687
7688[[package]]
7689name = "slab"
7690version = "0.4.12"
7691source = "registry+https://github.com/rust-lang/crates.io-index"
7692checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
76933721
76943722[[package]]
7695name = "smallvec"
7696version = "1.15.2"
3723name = "socket2"
3724version = "0.5.10"
76973725source = "registry+https://github.com/rust-lang/crates.io-index"
7698checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
3726checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
76993727dependencies = [
7700 "serde",
3728 "libc",
3729 "windows-sys 0.52.0",
77013730]
77023731
77033732[[package]]
@@ -7711,64 +3740,12 @@
77113740]
77123741
77133742[[package]]
7714name = "space"
7715version = "1.94.0"
7716dependencies = [
7717 "axum",
7718 "axum-extra",
7719 "cache",
7720 "config 1.94.0",
7721 "db",
7722 "export",
7723 "futures",
7724 "jwt",
7725 "log",
7726 "metrics",
7727 "rate_limit",
7728 "serde",
7729 "serde_json",
7730 "thiserror 2.0.19",
7731 "time",
7732 "tokio",
7733 "tracing",
7734 "utoipa",
7735 "uuid",
7736 "validator",
7737]
7738
7739[[package]]
77403743name = "spin"
77413744version = "0.9.9"
77423745source = "registry+https://github.com/rust-lang/crates.io-index"
77433746checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
77443747dependencies = [
77453748 "lock_api",
7746]
7747
7748[[package]]
7749name = "spin"
7750version = "0.10.1"
7751source = "registry+https://github.com/rust-lang/crates.io-index"
7752checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
7753
7754[[package]]
7755name = "spki"
7756version = "0.7.3"
7757source = "registry+https://github.com/rust-lang/crates.io-index"
7758checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
7759dependencies = [
7760 "base64ct",
7761 "der 0.7.10",
7762]
7763
7764[[package]]
7765name = "spki"
7766version = "0.8.0"
7767source = "registry+https://github.com/rust-lang/crates.io-index"
7768checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
7769dependencies = [
7770 "base64ct",
7771 "der 0.8.1",
77723749]
77733750
77743751[[package]]
@@ -7805,21 +3782,23 @@
78053782 "hashbrown 0.16.1",
78063783 "hashlink",
78073784 "indexmap",
7808 "log",
3785 "log 0.4.33",
78093786 "memchr",
7810 "percent-encoding",
3787 "percent-encoding 2.3.2",
78113788 "rust_decimal",
3789 "rustls",
78123790 "serde",
78133791 "serde_json",
78143792 "sha2 0.10.9",
78153793 "smallvec",
7816 "thiserror 2.0.19",
7817 "time",
3794 "thiserror 2.0.20",
3795 "time 0.3.55",
78183796 "tokio",
78193797 "tokio-stream",
78203798 "tracing",
7821 "url",
3799 "url 2.5.8",
78223800 "uuid",
3801 "webpki-roots",
78233802]
78243803
78253804[[package]]
@@ -7857,7 +3836,7 @@
78573836 "sqlx-sqlite",
78583837 "syn 2.0.119",
78593838 "tokio",
7860 "url",
3839 "url 2.5.8",
78613840]
78623841
78633842[[package]]
@@ -7866,7 +3845,7 @@
78663845source = "registry+https://github.com/rust-lang/crates.io-index"
78673846checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27"
78683847dependencies = [
7869 "bitflags 2.13.1",
3848 "bitflags",
78703849 "byteorder",
78713850 "bytes",
78723851 "chrono",
@@ -7876,16 +3855,16 @@
78763855 "either",
78773856 "futures-core",
78783857 "futures-util",
7879 "generic-array 0.14.9",
7880 "log",
7881 "percent-encoding",
3858 "generic-array",
3859 "log 0.4.33",
3860 "percent-encoding 2.3.2",
78823861 "rust_decimal",
78833862 "serde",
7884 "sha1 0.11.0",
3863 "sha1",
78853864 "sha2 0.11.0",
78863865 "sqlx-core",
7887 "thiserror 2.0.19",
7888 "time",
3866 "thiserror 2.0.20",
3867 "time 0.3.55",
78893868 "tracing",
78903869 "uuid",
78913870]
@@ -7898,7 +3877,7 @@
78983877dependencies = [
78993878 "atoi",
79003879 "base64 0.22.1",
7901 "bitflags 2.13.1",
3880 "bitflags",
79023881 "byteorder",
79033882 "chrono",
79043883 "crc",
@@ -7908,10 +3887,10 @@
79083887 "futures-core",
79093888 "futures-util",
79103889 "hex",
7911 "hkdf 0.13.0",
3890 "hkdf",
79123891 "hmac 0.13.0",
79133892 "itoa",
7914 "log",
3893 "log 0.4.33",
79153894 "md-5",
79163895 "memchr",
79173896 "rand 0.10.2",
@@ -7922,221 +3901,84 @@
79223901 "smallvec",
79233902 "sqlx-core",
79243903 "stringprep",
7925 "thiserror 2.0.19",
7926 "time",
7927 "tracing",
7928 "uuid",
7929 "whoami",
7930]
7931
7932[[package]]
7933name = "sqlx-sqlite"
7934version = "0.9.0"
7935source = "registry+https://github.com/rust-lang/crates.io-index"
7936checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
7937dependencies = [
7938 "atoi",
7939 "chrono",
7940 "flume",
7941 "form_urlencoded",
7942 "futures-channel",
7943 "futures-core",
7944 "futures-executor",
7945 "futures-intrusive",
7946 "futures-util",
7947 "libsqlite3-sys",
7948 "log",
7949 "percent-encoding",
7950 "serde",
7951 "sqlx-core",
7952 "thiserror 2.0.19",
7953 "time",
7954 "tracing",
7955 "url",
7956 "uuid",
7957]
7958
7959[[package]]
7960name = "sse-stream"
7961version = "0.2.5"
7962source = "registry+https://github.com/rust-lang/crates.io-index"
7963checksum = "c123f296ade4ec4b8b0f6162116e6629f5146922ca5ab40ca9d3c2e73ab4761e"
7964dependencies = [
7965 "bytes",
7966 "futures-util",
7967 "http-body",
7968 "http-body-util",
7969 "pin-project-lite",
7970]
7971
7972[[package]]
7973name = "ssh-cipher"
7974version = "0.3.0"
7975source = "registry+https://github.com/rust-lang/crates.io-index"
7976checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597"
7977dependencies = [
7978 "aead",
7979 "aes",
7980 "aes-gcm",
7981 "chacha20",
7982 "cipher",
7983 "ctutils",
7984 "des",
7985 "poly1305",
7986 "ssh-encoding",
7987 "zeroize",
7988]
7989
7990[[package]]
7991name = "ssh-encoding"
7992version = "0.3.0"
7993source = "registry+https://github.com/rust-lang/crates.io-index"
7994checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e"
7995dependencies = [
7996 "base64ct",
7997 "bytes",
7998 "crypto-bigint 0.7.5",
7999 "ctutils",
8000 "digest 0.11.3",
8001 "pem-rfc7468 1.0.0",
8002 "zeroize",
8003]
8004
8005[[package]]
8006name = "ssh-key"
8007version = "0.7.0-rc.11"
8008source = "registry+https://github.com/rust-lang/crates.io-index"
8009checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3"
8010dependencies = [
8011 "argon2 0.6.0-rc.8",
8012 "bcrypt-pbkdf",
8013 "ctutils",
8014 "ed25519-dalek 3.0.0-rc.1",
8015 "hex",
8016 "hmac 0.13.0",
8017 "p256 0.14.0-rc.15",
8018 "p384 0.14.0-rc.15",
8019 "p521",
8020 "rand_core 0.10.1",
8021 "rsa 0.10.0-rc.18",
8022 "sec1 0.8.1",
8023 "sha1 0.11.0",
8024 "sha2 0.11.0",
8025 "signature 3.0.0",
8026 "ssh-cipher",
8027 "ssh-encoding",
8028 "zeroize",
8029]
8030
8031[[package]]
8032name = "stable_deref_trait"
8033version = "1.2.1"
8034source = "registry+https://github.com/rust-lang/crates.io-index"
8035checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
8036
8037[[package]]
8038name = "static_assertions"
8039version = "1.1.0"
8040source = "registry+https://github.com/rust-lang/crates.io-index"
8041checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
8042
8043[[package]]
8044name = "static_assertions_next"
8045version = "1.1.2"
8046source = "registry+https://github.com/rust-lang/crates.io-index"
8047checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766"
8048
8049[[package]]
8050name = "storage"
8051version = "1.94.0"
8052dependencies = [
8053 "async-trait",
8054 "bytes",
8055 "config 1.94.0",
8056 "futures",
8057 "http",
8058 "lombok",
8059 "metrics",
8060 "object_store",
8061 "serde",
8062 "thiserror 2.0.19",
8063 "tokio",
8064 "tracing",
8065 "url",
8066]
8067
8068[[package]]
8069name = "storagelayer"
8070version = "1.94.0"
8071dependencies = [
8072 "async-trait",
8073 "bytes",
8074 "cache",
8075 "db",
8076 "futures",
8077 "jwt",
8078 "object_store",
8079 "queue",
8080 "sea-orm",
8081 "serde",
8082 "space",
8083 "storage",
8084 "thiserror 2.0.19",
8085 "time",
8086 "tokio",
3904 "thiserror 2.0.20",
3905 "time 0.3.55",
80873906 "tracing",
8088 "url",
8089 "utoipa",
80903907 "uuid",
3908 "whoami",
80913909]
80923910
80933911[[package]]
8094name = "stringprep"
8095version = "0.1.5"
3912name = "sqlx-sqlite"
3913version = "0.9.0"
80963914source = "registry+https://github.com/rust-lang/crates.io-index"
8097checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
3915checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
80983916dependencies = [
8099 "unicode-bidi",
8100 "unicode-normalization",
8101 "unicode-properties",
3917 "atoi",
3918 "chrono",
3919 "flume",
3920 "form_urlencoded",
3921 "futures-channel",
3922 "futures-core",
3923 "futures-executor",
3924 "futures-intrusive",
3925 "futures-util",
3926 "libsqlite3-sys",
3927 "log 0.4.33",
3928 "percent-encoding 2.3.2",
3929 "serde",
3930 "sqlx-core",
3931 "thiserror 2.0.20",
3932 "time 0.3.55",
3933 "tracing",
3934 "url 2.5.8",
3935 "uuid",
81023936]
81033937
81043938[[package]]
8105name = "strsim"
8106version = "0.11.1"
3939name = "stable_deref_trait"
3940version = "1.2.1"
81073941source = "registry+https://github.com/rust-lang/crates.io-index"
8108checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
3942checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
81093943
81103944[[package]]
8111name = "structmeta"
8112version = "0.3.0"
3945name = "stacker"
3946version = "0.1.25"
81133947source = "registry+https://github.com/rust-lang/crates.io-index"
8114checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329"
3948checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967"
81153949dependencies = [
8116 "proc-macro2",
8117 "quote",
8118 "structmeta-derive",
8119 "syn 2.0.119",
3950 "cc",
3951 "cfg-if",
3952 "libc",
3953 "psm",
3954 "windows-sys 0.61.2",
81203955]
81213956
81223957[[package]]
8123name = "structmeta-derive"
8124version = "0.3.0"
3958name = "static_assertions"
3959version = "1.1.0"
81253960source = "registry+https://github.com/rust-lang/crates.io-index"
8126checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
3961checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
3962
3963[[package]]
3964name = "storage"
3965version = "1.0.0"
81273966dependencies = [
8128 "proc-macro2",
8129 "quote",
8130 "syn 2.0.119",
3967 "config",
3968 "http 0.2.12",
3969 "rust-s3-async",
3970 "tokio",
81313971]
81323972
81333973[[package]]
8134name = "strum"
8135version = "0.27.2"
3974name = "stringprep"
3975version = "0.1.5"
81363976source = "registry+https://github.com/rust-lang/crates.io-index"
8137checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
3977checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
81383978dependencies = [
8139 "strum_macros",
3979 "unicode-bidi",
3980 "unicode-normalization",
3981 "unicode-properties",
81403982]
81413983
81423984[[package]]
@@ -8147,14 +3989,15 @@
81473989
81483990[[package]]
81493991name = "strum_macros"
8150version = "0.27.2"
3992version = "0.24.3"
81513993source = "registry+https://github.com/rust-lang/crates.io-index"
8152checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
3994checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
81533995dependencies = [
8154 "heck 0.5.0",
3996 "heck 0.4.1",
81553997 "proc-macro2",
81563998 "quote",
8157 "syn 2.0.119",
3999 "rustversion",
4000 "syn 1.0.109",
81584001]
81594002
81604003[[package]]
@@ -8201,9 +4044,6 @@
82014044version = "1.0.2"
82024045source = "registry+https://github.com/rust-lang/crates.io-index"
82034046checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
8204dependencies = [
8205 "futures-core",
8206]
82074047
82084048[[package]]
82094049name = "synstructure"
@@ -8217,6 +4057,12 @@
82174057]
82184058
82194059[[package]]
4060name = "tagptr"
4061version = "0.2.0"
4062source = "registry+https://github.com/rust-lang/crates.io-index"
4063checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
4064
4065[[package]]
82204066name = "tap"
82214067version = "1.0.1"
82224068source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8246,11 +4092,11 @@
82464092
82474093[[package]]
82484094name = "thiserror"
8249version = "2.0.19"
4095version = "2.0.20"
82504096source = "registry+https://github.com/rust-lang/crates.io-index"
8251checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
4097checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
82524098dependencies = [
8253 "thiserror-impl 2.0.19",
4099 "thiserror-impl 2.0.20",
82544100]
82554101
82564102[[package]]
@@ -8266,9 +4112,9 @@
82664112
82674113[[package]]
82684114name = "thiserror-impl"
8269version = "2.0.19"
4115version = "2.0.20"
82704116source = "registry+https://github.com/rust-lang/crates.io-index"
8271checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
4117checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
82724118dependencies = [
82734119 "proc-macro2",
82744120 "quote",
@@ -8276,12 +4122,14 @@
82764122]
82774123
82784124[[package]]
8279name = "thread_local"
8280version = "1.1.10"
4125name = "time"
4126version = "0.1.45"
82814127source = "registry+https://github.com/rust-lang/crates.io-index"
8282checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
4128checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
82834129dependencies = [
8284 "cfg-if",
4130 "libc",
4131 "wasi 0.10.0+wasi-snapshot-preview1",
4132 "winapi",
82854133]
82864134
82874135[[package]]
@@ -8325,11 +4173,12 @@
83254173
83264174[[package]]
83274175name = "tinystr"
8328version = "0.8.3"
4176version = "0.8.4"
83294177source = "registry+https://github.com/rust-lang/crates.io-index"
8330checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
4178checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
83314179dependencies = [
83324180 "displaydoc",
4181 "serde_core",
83334182 "zerovec",
83344183]
83354184
@@ -8360,7 +4209,7 @@
83604209 "parking_lot",
83614210 "pin-project-lite",
83624211 "signal-hook-registry",
8363 "socket2",
4212 "socket2 0.6.5",
83644213 "tokio-macros",
83654214 "windows-sys 0.61.2",
83664215]
@@ -8387,16 +4236,6 @@
83874236]
83884237
83894238[[package]]
8390name = "tokio-rustls"
8391version = "0.26.4"
8392source = "registry+https://github.com/rust-lang/crates.io-index"
8393checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
8394dependencies = [
8395 "rustls",
8396 "tokio",
8397]
8398
8399[[package]]
84004239name = "tokio-stream"
84014240version = "0.1.19"
84024241source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8405,19 +4244,6 @@
84054244 "futures-core",
84064245 "pin-project-lite",
84074246 "tokio",
8408 "tokio-util",
8409]
8410
8411[[package]]
8412name = "tokio-tungstenite"
8413version = "0.29.0"
8414source = "registry+https://github.com/rust-lang/crates.io-index"
8415checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
8416dependencies = [
8417 "futures-util",
8418 "log",
8419 "tokio",
8420 "tungstenite",
84214247]
84224248
84234249[[package]]
@@ -8429,151 +4255,39 @@
84294255 "bytes",
84304256 "futures-core",
84314257 "futures-sink",
8432 "futures-util",
84334258 "libc",
84344259 "pin-project-lite",
84354260 "tokio",
84364261]
84374262
84384263[[package]]
8439name = "toml"
8440version = "1.1.4+spec-1.1.0"
8441source = "registry+https://github.com/rust-lang/crates.io-index"
8442checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
8443dependencies = [
8444 "serde_core",
8445 "serde_spanned",
8446 "toml_datetime",
8447 "toml_parser",
8448 "winnow",
8449]
8450
8451[[package]]
84524264name = "toml_datetime"
84534265version = "1.1.1+spec-1.1.0"
84544266source = "registry+https://github.com/rust-lang/crates.io-index"
84554267checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
84564268dependencies = [
84574269 "serde_core",
8458]
8459
8460[[package]]
8461name = "toml_edit"
8462version = "0.25.13+spec-1.1.0"
8463source = "registry+https://github.com/rust-lang/crates.io-index"
8464checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
8465dependencies = [
8466 "indexmap",
8467 "toml_datetime",
8468 "toml_parser",
8469 "winnow",
8470]
8471
8472[[package]]
8473name = "toml_parser"
8474version = "1.1.3+spec-1.1.0"
8475source = "registry+https://github.com/rust-lang/crates.io-index"
8476checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
8477dependencies = [
8478 "winnow",
8479]
8480
8481[[package]]
8482name = "tonic"
8483version = "0.14.6"
8484source = "registry+https://github.com/rust-lang/crates.io-index"
8485checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
8486dependencies = [
8487 "async-trait",
8488 "axum",
8489 "base64 0.22.1",
8490 "bytes",
8491 "flate2",
8492 "h2",
8493 "http",
8494 "http-body",
8495 "http-body-util",
8496 "hyper",
8497 "hyper-timeout",
8498 "hyper-util",
8499 "percent-encoding",
8500 "pin-project",
8501 "rustls-native-certs",
8502 "socket2",
8503 "sync_wrapper",
8504 "tokio",
8505 "tokio-rustls",
8506 "tokio-stream",
8507 "tower",
8508 "tower-layer",
8509 "tower-service",
8510 "tracing",
8511]
8512
8513[[package]]
8514name = "tonic-build"
8515version = "0.14.6"
8516source = "registry+https://github.com/rust-lang/crates.io-index"
8517checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322"
8518dependencies = [
8519 "prettyplease",
8520 "proc-macro2",
8521 "quote",
8522 "syn 2.0.119",
8523]
8524
8525[[package]]
8526name = "tonic-health"
8527version = "0.14.6"
8528source = "registry+https://github.com/rust-lang/crates.io-index"
8529checksum = "fcfab99db777fba2802f0dfa861d1628d1ae916fb199d29819941f139ae85082"
8530dependencies = [
8531 "prost",
8532 "tokio",
8533 "tokio-stream",
8534 "tonic",
8535 "tonic-prost",
8536]
8537
8538[[package]]
8539name = "tonic-prost"
8540version = "0.14.6"
8541source = "registry+https://github.com/rust-lang/crates.io-index"
8542checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
8543dependencies = [
8544 "bytes",
8545 "prost",
8546 "tonic",
85474270]
85484271
85494272[[package]]
8550name = "tonic-prost-build"
8551version = "0.14.6"
4273name = "toml_edit"
4274version = "0.25.13+spec-1.1.0"
85524275source = "registry+https://github.com/rust-lang/crates.io-index"
8553checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27"
4276checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
85544277dependencies = [
8555 "prettyplease",
8556 "proc-macro2",
8557 "prost-build",
8558 "prost-types",
8559 "quote",
8560 "syn 2.0.119",
8561 "tempfile",
8562 "tonic-build",
4278 "indexmap",
4279 "toml_datetime",
4280 "toml_parser",
4281 "winnow",
85634282]
85644283
85654284[[package]]
8566name = "totp"
8567version = "1.94.0"
4285name = "toml_parser"
4286version = "1.1.3+spec-1.1.0"
4287source = "registry+https://github.com/rust-lang/crates.io-index"
4288checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
85684289dependencies = [
8569 "aes-gcm",
8570 "base64 0.23.1",
8571 "hmac 0.12.1",
8572 "rand 0.10.2",
8573 "sha1 0.10.7",
8574 "sha2 0.10.9",
8575 "thiserror 2.0.19",
8576 "time",
4290 "winnow",
85774291]
85784292
85794293[[package]]
@@ -8584,33 +4298,12 @@
85844298dependencies = [
85854299 "futures-core",
85864300 "futures-util",
8587 "indexmap",
85884301 "pin-project-lite",
8589 "slab",
85904302 "sync_wrapper",
85914303 "tokio",
8592 "tokio-util",
85934304 "tower-layer",
85944305 "tower-service",
85954306 "tracing",
8596]
8597
8598[[package]]
8599name = "tower-http"
8600version = "0.6.11"
8601source = "registry+https://github.com/rust-lang/crates.io-index"
8602checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
8603dependencies = [
8604 "bitflags 2.13.1",
8605 "bytes",
8606 "futures-util",
8607 "http",
8608 "http-body",
8609 "pin-project-lite",
8610 "tower",
8611 "tower-layer",
8612 "tower-service",
8613 "url",
86144307]
86154308
86164309[[package]]
@@ -8631,7 +4324,7 @@
86314324source = "registry+https://github.com/rust-lang/crates.io-index"
86324325checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
86334326dependencies = [
8634 "log",
4327 "log 0.4.33",
86354328 "pin-project-lite",
86364329 "tracing-attributes",
86374330 "tracing-core",
@@ -8655,56 +4348,13 @@
86554348checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
86564349dependencies = [
86574350 "once_cell",
8658 "valuable",
86594351]
86604352
86614353[[package]]
8662name = "tracing-log"
8663version = "0.2.0"
4354name = "traitobject"
4355version = "0.1.1"
86644356source = "registry+https://github.com/rust-lang/crates.io-index"
8665checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
8666dependencies = [
8667 "log",
8668 "once_cell",
8669 "tracing-core",
8670]
8671
8672[[package]]
8673name = "tracing-opentelemetry"
8674version = "0.32.0"
8675source = "registry+https://github.com/rust-lang/crates.io-index"
8676checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e"
8677dependencies = [
8678 "js-sys",
8679 "opentelemetry",
8680 "opentelemetry_sdk",
8681 "rustversion",
8682 "smallvec",
8683 "thiserror 2.0.19",
8684 "tracing",
8685 "tracing-core",
8686 "tracing-log",
8687 "tracing-subscriber",
8688 "web-time",
8689]
8690
8691[[package]]
8692name = "tracing-subscriber"
8693version = "0.3.23"
8694source = "registry+https://github.com/rust-lang/crates.io-index"
8695checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
8696dependencies = [
8697 "matchers",
8698 "nu-ansi-term",
8699 "once_cell",
8700 "regex-automata",
8701 "sharded-slab",
8702 "smallvec",
8703 "thread_local",
8704 "tracing",
8705 "tracing-core",
8706 "tracing-log",
8707]
4357checksum = "04a79e25382e2e852e8da874249358d382ebaf259d0d34e75d8db16a7efabbc7"
87084358
87094359[[package]]
87104360name = "try-lock"
@@ -8713,20 +4363,10 @@
87134363checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
87144364
87154365[[package]]
8716name = "tungstenite"
8717version = "0.29.0"
4366name = "typeable"
4367version = "0.1.2"
87184368source = "registry+https://github.com/rust-lang/crates.io-index"
8719checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8"
8720dependencies = [
8721 "bytes",
8722 "data-encoding",
8723 "http",
8724 "httparse",
8725 "log",
8726 "rand 0.9.5",
8727 "sha1 0.10.7",
8728 "thiserror 2.0.19",
8729]
4369checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
87304370
87314371[[package]]
87324372name = "typenum"
@@ -8735,39 +4375,21 @@
87354375checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
87364376
87374377[[package]]
8738name = "ucd-trie"
8739version = "0.1.7"
4378name = "unicase"
4379version = "1.4.2"
87404380source = "registry+https://github.com/rust-lang/crates.io-index"
8741checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
8742
8743[[package]]
8744name = "uluru"
8745version = "3.1.0"
8746source = "registry+https://github.com/rust-lang/crates.io-index"
8747checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da"
4381checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
87484382dependencies = [
8749 "arrayvec",
4383 "version_check 0.1.5",
87504384]
87514385
87524386[[package]]
8753name = "unicase"
8754version = "2.9.0"
8755source = "registry+https://github.com/rust-lang/crates.io-index"
8756checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
8757
8758[[package]]
87594387name = "unicode-bidi"
87604388version = "0.3.18"
87614389source = "registry+https://github.com/rust-lang/crates.io-index"
87624390checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
87634391
87644392[[package]]
8765name = "unicode-bom"
8766version = "2.0.3"
8767source = "registry+https://github.com/rust-lang/crates.io-index"
8768checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
8769
8770[[package]]
87714393name = "unicode-ident"
87724394version = "1.0.24"
87734395source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8787,12 +4409,6 @@
87874409version = "0.1.4"
87884410source = "registry+https://github.com/rust-lang/crates.io-index"
87894411checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
8790
8791[[package]]
8792name = "unicode-segmentation"
8793version = "1.13.3"
8794source = "registry+https://github.com/rust-lang/crates.io-index"
8795checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
87964412
87974413[[package]]
87984414name = "unicode-xid"
@@ -8802,184 +4418,76 @@
88024418
88034419[[package]]
88044420name = "universal-hash"
8805version = "0.6.1"
4421version = "0.5.1"
88064422source = "registry+https://github.com/rust-lang/crates.io-index"
8807checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
8808dependencies = [
8809 "crypto-common 0.2.2",
8810 "ctutils",
8811]
8812
8813[[package]]
8814name = "unsafe-libyaml"
8815version = "0.2.11"
8816source = "registry+https://github.com/rust-lang/crates.io-index"
8817checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
8818
8819[[package]]
8820name = "unsafe-libyaml-norway"
8821version = "0.2.15"
8822source = "registry+https://github.com/rust-lang/crates.io-index"
8823checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104"
8824
8825[[package]]
8826name = "untrusted"
8827version = "0.7.1"
8828source = "registry+https://github.com/rust-lang/crates.io-index"
8829checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
8830
8831[[package]]
8832name = "untrusted"
8833version = "0.9.0"
8834source = "registry+https://github.com/rust-lang/crates.io-index"
8835checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
8836
8837[[package]]
8838name = "url"
8839version = "2.5.8"
8840source = "registry+https://github.com/rust-lang/crates.io-index"
8841checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
8842dependencies = [
8843 "form_urlencoded",
8844 "idna",
8845 "percent-encoding",
8846 "serde",
8847]
8848
8849[[package]]
8850name = "user"
8851version = "1.94.0"
8852dependencies = [
8853 "aes-gcm",
8854 "auth",
8855 "axum",
8856 "axum-extra",
8857 "base64 0.23.1",
8858 "bytes",
8859 "cache",
8860 "config 1.94.0",
8861 "db",
8862 "export",
8863 "hex",
8864 "hmac 0.12.1",
8865 "http",
8866 "jwt",
8867 "log",
8868 "metrics",
8869 "object_store",
8870 "rand 0.10.2",
8871 "rand_core 0.10.1",
8872 "rate_limit",
8873 "serde",
8874 "serde_json",
8875 "sha1 0.10.7",
8876 "sha2 0.10.9",
8877 "storage",
8878 "thiserror 2.0.19",
8879 "time",
8880 "tokio",
8881 "totp",
8882 "tracing",
8883 "url",
8884 "utoipa",
8885 "uuid",
8886 "validator",
8887]
8888
8889[[package]]
8890name = "utf8_iter"
8891version = "1.0.4"
8892source = "registry+https://github.com/rust-lang/crates.io-index"
8893checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
8894
8895[[package]]
8896name = "utf8parse"
8897version = "0.2.2"
8898source = "registry+https://github.com/rust-lang/crates.io-index"
8899checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
8900
8901[[package]]
8902name = "utoipa"
8903version = "5.5.0"
8904source = "registry+https://github.com/rust-lang/crates.io-index"
8905checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160"
4423checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
89064424dependencies = [
8907 "indexmap",
8908 "serde",
8909 "serde_json",
8910 "serde_norway",
8911 "utoipa-gen",
4425 "crypto-common 0.1.6",
4426 "subtle",
89124427]
89134428
89144429[[package]]
8915name = "utoipa-gen"
8916version = "5.5.0"
4430name = "universal-hash"
4431version = "0.6.1"
89174432source = "registry+https://github.com/rust-lang/crates.io-index"
8918checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8"
4433checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
89194434dependencies = [
8920 "proc-macro2",
8921 "quote",
8922 "syn 2.0.119",
8923 "uuid",
4435 "crypto-common 0.2.2",
4436 "ctutils",
89244437]
89254438
89264439[[package]]
8927name = "utoipa-scalar"
8928version = "0.3.0"
4440name = "untrusted"
4441version = "0.9.0"
89294442source = "registry+https://github.com/rust-lang/crates.io-index"
8930checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
8931dependencies = [
8932 "axum",
8933 "serde",
8934 "serde_json",
8935 "utoipa",
8936]
4443checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
89374444
89384445[[package]]
8939name = "uuid"
8940version = "1.24.0"
4446name = "url"
4447version = "1.7.2"
89414448source = "registry+https://github.com/rust-lang/crates.io-index"
8942checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
4449checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
89434450dependencies = [
8944 "getrandom 0.4.3",
8945 "js-sys",
8946 "serde_core",
8947 "wasm-bindgen",
4451 "idna 0.1.5",
4452 "matches",
4453 "percent-encoding 1.0.1",
89484454]
89494455
89504456[[package]]
8951name = "validator"
8952version = "0.21.0"
4457name = "url"
4458version = "2.5.8"
89534459source = "registry+https://github.com/rust-lang/crates.io-index"
8954checksum = "c3d68c6633c483df6780cc5277a417c7c2d1bceee2649d06c8ab6b0fd2dd3c81"
4460checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
89554461dependencies = [
8956 "idna",
8957 "regex",
4462 "form_urlencoded",
4463 "idna 1.1.0",
4464 "percent-encoding 2.3.2",
89584465 "serde",
8959 "serde_derive",
8960 "serde_json",
8961 "url",
8962 "validator_derive",
89634466]
89644467
89654468[[package]]
8966name = "validator_derive"
8967version = "0.20.1"
4469name = "utf16_iter"
4470version = "1.0.5"
4471source = "registry+https://github.com/rust-lang/crates.io-index"
4472checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
4473
4474[[package]]
4475name = "utf8_iter"
4476version = "1.0.4"
89684477source = "registry+https://github.com/rust-lang/crates.io-index"
8969checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335"
8970dependencies = [
8971 "darling 0.23.0",
8972 "proc-macro-error3",
8973 "proc-macro2",
8974 "quote",
8975 "syn 2.0.119",
8976]
4478checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
89774479
89784480[[package]]
8979name = "valuable"
8980version = "0.1.1"
4481name = "uuid"
4482version = "1.24.1"
89814483source = "registry+https://github.com/rust-lang/crates.io-index"
8982checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
4484checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
4485dependencies = [
4486 "getrandom 0.4.3",
4487 "js-sys",
4488 "serde_core",
4489 "wasm-bindgen",
4490]
89834491
89844492[[package]]
89854493name = "vcpkg"
@@ -8989,19 +4497,15 @@
89894497
89904498[[package]]
89914499name = "version_check"
8992version = "0.9.5"
4500version = "0.1.5"
89934501source = "registry+https://github.com/rust-lang/crates.io-index"
8994checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
4502checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
89954503
89964504[[package]]
8997name = "walkdir"
8998version = "2.5.0"
4505name = "version_check"
4506version = "0.9.5"
89994507source = "registry+https://github.com/rust-lang/crates.io-index"
9000checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
9001dependencies = [
9002 "same-file",
9003 "winapi-util",
9004]
4508checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
90054509
90064510[[package]]
90074511name = "want"
@@ -9014,6 +4518,12 @@
90144518
90154519[[package]]
90164520name = "wasi"
4521version = "0.10.0+wasi-snapshot-preview1"
4522source = "registry+https://github.com/rust-lang/crates.io-index"
4523checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
4524
4525[[package]]
4526name = "wasi"
90174527version = "0.11.1+wasi-snapshot-preview1"
90184528source = "registry+https://github.com/rust-lang/crates.io-index"
90194529checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
@@ -9029,9 +4539,9 @@
90294539
90304540[[package]]
90314541name = "wasm-bindgen"
9032version = "0.2.126"
4542version = "0.2.127"
90334543source = "registry+https://github.com/rust-lang/crates.io-index"
9034checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
4544checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
90354545dependencies = [
90364546 "cfg-if",
90374547 "once_cell",
@@ -9039,23 +4549,13 @@
90394549 "serde",
90404550 "wasm-bindgen-macro",
90414551 "wasm-bindgen-shared",
9042]
9043
9044[[package]]
9045name = "wasm-bindgen-futures"
9046version = "0.4.76"
9047source = "registry+https://github.com/rust-lang/crates.io-index"
9048checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
9049dependencies = [
9050 "js-sys",
9051 "wasm-bindgen",
90524552]
90534553
90544554[[package]]
90554555name = "wasm-bindgen-macro"
9056version = "0.2.126"
4556version = "0.2.127"
90574557source = "registry+https://github.com/rust-lang/crates.io-index"
9058checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
4558checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
90594559dependencies = [
90604560 "quote",
90614561 "wasm-bindgen-macro-support",
@@ -9063,9 +4563,9 @@
90634563
90644564[[package]]
90654565name = "wasm-bindgen-macro-support"
9066version = "0.2.126"
4566version = "0.2.127"
90674567source = "registry+https://github.com/rust-lang/crates.io-index"
9068checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
4568checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
90694569dependencies = [
90704570 "bumpalo",
90714571 "proc-macro2",
@@ -9076,50 +4576,14 @@
90764576
90774577[[package]]
90784578name = "wasm-bindgen-shared"
9079version = "0.2.126"
4579version = "0.2.127"
90804580source = "registry+https://github.com/rust-lang/crates.io-index"
9081checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
4581checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
90824582dependencies = [
90834583 "unicode-ident",
90844584]
90854585
90864586[[package]]
9087name = "wasm-streams"
9088version = "0.4.2"
9089source = "registry+https://github.com/rust-lang/crates.io-index"
9090checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
9091dependencies = [
9092 "futures-util",
9093 "js-sys",
9094 "wasm-bindgen",
9095 "wasm-bindgen-futures",
9096 "web-sys",
9097]
9098
9099[[package]]
9100name = "wasm-streams"
9101version = "0.5.0"
9102source = "registry+https://github.com/rust-lang/crates.io-index"
9103checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
9104dependencies = [
9105 "futures-util",
9106 "js-sys",
9107 "wasm-bindgen",
9108 "wasm-bindgen-futures",
9109 "web-sys",
9110]
9111
9112[[package]]
9113name = "web-sys"
9114version = "0.3.103"
9115source = "registry+https://github.com/rust-lang/crates.io-index"
9116checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
9117dependencies = [
9118 "js-sys",
9119 "wasm-bindgen",
9120]
9121
9122[[package]]
91234587name = "web-time"
91244588version = "1.1.0"
91254589source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -9127,71 +4591,6 @@
91274591dependencies = [
91284592 "js-sys",
91294593 "wasm-bindgen",
9130]
9131
9132[[package]]
9133name = "webapi"
9134version = "1.94.0"
9135dependencies = [
9136 "auth",
9137 "axum",
9138 "cache",
9139 "config 1.94.0",
9140 "db",
9141 "email",
9142 "export",
9143 "issues",
9144 "jwt",
9145 "mcp",
9146 "once_cell",
9147 "payment",
9148 "post",
9149 "project",
9150 "queue",
9151 "rate_limit",
9152 "repo",
9153 "serde",
9154 "serde_json",
9155 "space",
9156 "storage",
9157 "thiserror 2.0.19",
9158 "tokio",
9159 "tracing",
9160 "user",
9161 "utoipa",
9162 "utoipa-scalar",
9163]
9164
9165[[package]]
9166name = "webhook"
9167version = "1.94.0"
9168dependencies = [
9169 "async-trait",
9170 "config 1.94.0",
9171 "db",
9172 "gitsync",
9173 "hex",
9174 "hmac 0.12.1",
9175 "metrics",
9176 "rand 0.10.2",
9177 "reqwest 0.13.4",
9178 "sea-orm",
9179 "serde",
9180 "serde_json",
9181 "sha2 0.10.9",
9182 "time",
9183 "tokio",
9184 "tracing",
9185 "uuid",
9186]
9187
9188[[package]]
9189name = "webpki-root-certs"
9190version = "1.0.9"
9191source = "registry+https://github.com/rust-lang/crates.io-index"
9192checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
9193dependencies = [
9194 "rustls-pki-types",
91954594]
91964595
91974596[[package]]
@@ -9205,9 +4604,9 @@
92054604
92064605[[package]]
92074606name = "whoami"
9208version = "2.1.2"
4607version = "2.1.3"
92094608source = "registry+https://github.com/rust-lang/crates.io-index"
9210checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d"
4609checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c"
92114610
92124611[[package]]
92134612name = "winapi"
@@ -9217,51 +4616,21 @@
92174616dependencies = [
92184617 "winapi-i686-pc-windows-gnu",
92194618 "winapi-x86_64-pc-windows-gnu",
9220]
9221
9222[[package]]
9223name = "winapi-i686-pc-windows-gnu"
9224version = "0.4.0"
9225source = "registry+https://github.com/rust-lang/crates.io-index"
9226checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
9227
9228[[package]]
9229name = "winapi-util"
9230version = "0.1.11"
9231source = "registry+https://github.com/rust-lang/crates.io-index"
9232checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
9233dependencies = [
9234 "windows-sys 0.61.2",
92354619]
92364620
92374621[[package]]
9238name = "winapi-x86_64-pc-windows-gnu"
4622name = "winapi-i686-pc-windows-gnu"
92394623version = "0.4.0"
92404624source = "registry+https://github.com/rust-lang/crates.io-index"
9241checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
4625checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
92424626
92434627[[package]]
9244name = "windows"
9245version = "0.62.2"
4628name = "winapi-x86_64-pc-windows-gnu"
4629version = "0.4.0"
92464630source = "registry+https://github.com/rust-lang/crates.io-index"
9247checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
9248dependencies = [
9249 "windows-collections",
9250 "windows-core",
9251 "windows-future",
9252 "windows-numerics",
9253]
4631checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
92544632
92554633[[package]]
9256name = "windows-collections"
9257version = "0.3.2"
9258source = "registry+https://github.com/rust-lang/crates.io-index"
9259checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
9260dependencies = [
9261 "windows-core",
9262]
9263
9264[[package]]
92654634name = "windows-core"
92664635version = "0.62.2"
92674636source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -9275,17 +4644,6 @@
92754644]
92764645
92774646[[package]]
9278name = "windows-future"
9279version = "0.3.2"
9280source = "registry+https://github.com/rust-lang/crates.io-index"
9281checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
9282dependencies = [
9283 "windows-core",
9284 "windows-link",
9285 "windows-threading",
9286]
9287
9288[[package]]
92894647name = "windows-implement"
92904648version = "0.60.2"
92914649source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -9312,16 +4670,6 @@
93124670version = "0.2.1"
93134671source = "registry+https://github.com/rust-lang/crates.io-index"
93144672checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
9315
9316[[package]]
9317name = "windows-numerics"
9318version = "0.3.1"
9319source = "registry+https://github.com/rust-lang/crates.io-index"
9320checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
9321dependencies = [
9322 "windows-core",
9323 "windows-link",
9324]
93254673
93264674[[package]]
93274675name = "windows-result"
@@ -9343,20 +4691,20 @@
93434691
93444692[[package]]
93454693name = "windows-sys"
9346version = "0.52.0"
4694version = "0.48.0"
93474695source = "registry+https://github.com/rust-lang/crates.io-index"
9348checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
4696checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
93494697dependencies = [
9350 "windows-targets 0.52.6",
4698 "windows-targets 0.48.5",
93514699]
93524700
93534701[[package]]
93544702name = "windows-sys"
9355version = "0.60.2"
4703version = "0.52.0"
93564704source = "registry+https://github.com/rust-lang/crates.io-index"
9357checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
4705checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
93584706dependencies = [
9359 "windows-targets 0.53.5",
4707 "windows-targets 0.52.6",
93604708]
93614709
93624710[[package]]
@@ -9370,6 +4718,21 @@
93704718
93714719[[package]]
93724720name = "windows-targets"
4721version = "0.48.5"
4722source = "registry+https://github.com/rust-lang/crates.io-index"
4723checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
4724dependencies = [
4725 "windows_aarch64_gnullvm 0.48.5",
4726 "windows_aarch64_msvc 0.48.5",
4727 "windows_i686_gnu 0.48.5",
4728 "windows_i686_msvc 0.48.5",
4729 "windows_x86_64_gnu 0.48.5",
4730 "windows_x86_64_gnullvm 0.48.5",
4731 "windows_x86_64_msvc 0.48.5",
4732]
4733
4734[[package]]
4735name = "windows-targets"
93734736version = "0.52.6"
93744737source = "registry+https://github.com/rust-lang/crates.io-index"
93754738checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -9377,7 +4740,7 @@
93774740 "windows_aarch64_gnullvm 0.52.6",
93784741 "windows_aarch64_msvc 0.52.6",
93794742 "windows_i686_gnu 0.52.6",
9380 "windows_i686_gnullvm 0.52.6",
4743 "windows_i686_gnullvm",
93814744 "windows_i686_msvc 0.52.6",
93824745 "windows_x86_64_gnu 0.52.6",
93834746 "windows_x86_64_gnullvm 0.52.6",
@@ -9385,30 +4748,10 @@
93854748]
93864749
93874750[[package]]
9388name = "windows-targets"
9389version = "0.53.5"
4751name = "windows_aarch64_gnullvm"
4752version = "0.48.5"
93904753source = "registry+https://github.com/rust-lang/crates.io-index"
9391checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
9392dependencies = [
9393 "windows-link",
9394 "windows_aarch64_gnullvm 0.53.1",
9395 "windows_aarch64_msvc 0.53.1",
9396 "windows_i686_gnu 0.53.1",
9397 "windows_i686_gnullvm 0.53.1",
9398 "windows_i686_msvc 0.53.1",
9399 "windows_x86_64_gnu 0.53.1",
9400 "windows_x86_64_gnullvm 0.53.1",
9401 "windows_x86_64_msvc 0.53.1",
9402]
9403
9404[[package]]
9405name = "windows-threading"
9406version = "0.2.1"
9407source = "registry+https://github.com/rust-lang/crates.io-index"
9408checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
9409dependencies = [
9410 "windows-link",
9411]
4754checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
94124755
94134756[[package]]
94144757name = "windows_aarch64_gnullvm"
@@ -9417,10 +4760,10 @@
94174760checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
94184761
94194762[[package]]
9420name = "windows_aarch64_gnullvm"
9421version = "0.53.1"
4763name = "windows_aarch64_msvc"
4764version = "0.48.5"
94224765source = "registry+https://github.com/rust-lang/crates.io-index"
9423checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
4766checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
94244767
94254768[[package]]
94264769name = "windows_aarch64_msvc"
@@ -9429,22 +4772,16 @@
94294772checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
94304773
94314774[[package]]
9432name = "windows_aarch64_msvc"
9433version = "0.53.1"
4775name = "windows_i686_gnu"
4776version = "0.48.5"
94344777source = "registry+https://github.com/rust-lang/crates.io-index"
9435checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
4778checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
94364779
94374780[[package]]
94384781name = "windows_i686_gnu"
94394782version = "0.52.6"
94404783source = "registry+https://github.com/rust-lang/crates.io-index"
94414784checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
9442
9443[[package]]
9444name = "windows_i686_gnu"
9445version = "0.53.1"
9446source = "registry+https://github.com/rust-lang/crates.io-index"
9447checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
94484785
94494786[[package]]
94504787name = "windows_i686_gnullvm"
@@ -9453,10 +4790,10 @@
94534790checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
94544791
94554792[[package]]
9456name = "windows_i686_gnullvm"
9457version = "0.53.1"
4793name = "windows_i686_msvc"
4794version = "0.48.5"
94584795source = "registry+https://github.com/rust-lang/crates.io-index"
9459checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
4796checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
94604797
94614798[[package]]
94624799name = "windows_i686_msvc"
@@ -9465,10 +4802,10 @@
94654802checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
94664803
94674804[[package]]
9468name = "windows_i686_msvc"
9469version = "0.53.1"
4805name = "windows_x86_64_gnu"
4806version = "0.48.5"
94704807source = "registry+https://github.com/rust-lang/crates.io-index"
9471checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
4808checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
94724809
94734810[[package]]
94744811name = "windows_x86_64_gnu"
@@ -9477,10 +4814,10 @@
94774814checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
94784815
94794816[[package]]
9480name = "windows_x86_64_gnu"
9481version = "0.53.1"
4817name = "windows_x86_64_gnullvm"
4818version = "0.48.5"
94824819source = "registry+https://github.com/rust-lang/crates.io-index"
9483checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
4820checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
94844821
94854822[[package]]
94864823name = "windows_x86_64_gnullvm"
@@ -9489,10 +4826,10 @@
94894826checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
94904827
94914828[[package]]
9492name = "windows_x86_64_gnullvm"
9493version = "0.53.1"
4829name = "windows_x86_64_msvc"
4830version = "0.48.5"
94944831source = "registry+https://github.com/rust-lang/crates.io-index"
9495checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
4832checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
94964833
94974834[[package]]
94984835name = "windows_x86_64_msvc"
@@ -9501,41 +4838,12 @@
95014838checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
95024839
95034840[[package]]
9504name = "windows_x86_64_msvc"
9505version = "0.53.1"
9506source = "registry+https://github.com/rust-lang/crates.io-index"
9507checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
9508
9509[[package]]
95104841name = "winnow"
95114842version = "1.0.4"
95124843source = "registry+https://github.com/rust-lang/crates.io-index"
95134844checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
95144845dependencies = [
95154846 "memchr",
9516]
9517
9518[[package]]
9519name = "wiremock"
9520version = "0.6.5"
9521source = "registry+https://github.com/rust-lang/crates.io-index"
9522checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
9523dependencies = [
9524 "assert-json-diff",
9525 "base64 0.22.1",
9526 "deadpool",
9527 "futures",
9528 "http",
9529 "http-body-util",
9530 "hyper",
9531 "hyper-util",
9532 "log",
9533 "once_cell",
9534 "regex",
9535 "serde",
9536 "serde_json",
9537 "tokio",
9538 "url",
95394847]
95404848
95414849[[package]]
@@ -9545,21 +4853,19 @@
95454853checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
95464854
95474855[[package]]
9548name = "wnaf"
9549version = "0.14.0"
4856name = "write16"
4857version = "1.0.0"
95504858source = "registry+https://github.com/rust-lang/crates.io-index"
9551checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1"
9552dependencies = [
9553 "ff 0.14.0",
9554 "group 0.14.0",
9555 "hybrid-array",
9556]
4859checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
95574860
95584861[[package]]
95594862name = "writeable"
9560version = "0.6.3"
4863version = "0.6.4"
95614864source = "registry+https://github.com/rust-lang/crates.io-index"
9562checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
4865checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
4866dependencies = [
4867 "either",
4868]
95634869
95644870[[package]]
95654871name = "wyz"
@@ -9577,32 +4883,10 @@
95774883checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
95784884
95794885[[package]]
9580name = "yaml-rust2"
9581version = "0.11.0"
9582source = "registry+https://github.com/rust-lang/crates.io-index"
9583checksum = "631a50d867fafb7093e709d75aaee9e0e0d5deb934021fcea25ac2fe09edc51e"
9584dependencies = [
9585 "arraydeque",
9586 "encoding_rs",
9587 "hashlink",
9588]
9589
9590[[package]]
95914886name = "yansi"
95924887version = "1.0.1"
95934888source = "registry+https://github.com/rust-lang/crates.io-index"
95944889checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
9595
9596[[package]]
9597name = "yaup"
9598version = "0.3.1"
9599source = "registry+https://github.com/rust-lang/crates.io-index"
9600checksum = "b0144f1a16a199846cb21024da74edd930b43443463292f536b7110b4855b5c6"
9601dependencies = [
9602 "form_urlencoded",
9603 "serde",
9604 "thiserror 1.0.69",
9605]
96064890
96074891[[package]]
96084892name = "yoke"
@@ -9629,18 +4913,18 @@
96294913
96304914[[package]]
96314915name = "zerocopy"
9632version = "0.8.55"
4916version = "0.8.56"
96334917source = "registry+https://github.com/rust-lang/crates.io-index"
9634checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
4918checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
96354919dependencies = [
96364920 "zerocopy-derive",
96374921]
96384922
96394923[[package]]
96404924name = "zerocopy-derive"
9641version = "0.8.55"
4925version = "0.8.56"
96424926source = "registry+https://github.com/rust-lang/crates.io-index"
9643checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
4927checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
96444928dependencies = [
96454929 "proc-macro2",
96464930 "quote",
@@ -9673,38 +4957,26 @@
96734957version = "1.9.0"
96744958source = "registry+https://github.com/rust-lang/crates.io-index"
96754959checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
9676dependencies = [
9677 "zeroize_derive",
9678]
9679
9680[[package]]
9681name = "zeroize_derive"
9682version = "1.5.0"
9683source = "registry+https://github.com/rust-lang/crates.io-index"
9684checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
9685dependencies = [
9686 "proc-macro2",
9687 "quote",
9688 "syn 2.0.119",
9689]
96904960
96914961[[package]]
96924962name = "zerotrie"
9693version = "0.2.4"
4963version = "0.2.5"
96944964source = "registry+https://github.com/rust-lang/crates.io-index"
9695checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
4965checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
96964966dependencies = [
96974967 "displaydoc",
96984968 "yoke",
96994969 "zerofrom",
4970 "zerovec",
97004971]
97014972
97024973[[package]]
97034974name = "zerovec"
9704version = "0.11.6"
4975version = "0.11.7"
97054976source = "registry+https://github.com/rust-lang/crates.io-index"
9706checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
4977checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8"
97074978dependencies = [
4979 "serde",
97084980 "yoke",
97094981 "zerofrom",
97104982 "zerovec-derive",
@@ -9712,20 +4984,14 @@
97124984
97134985[[package]]
97144986name = "zerovec-derive"
9715version = "0.11.3"
4987version = "0.11.4"
97164988source = "registry+https://github.com/rust-lang/crates.io-index"
9717checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
4989checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62"
97184990dependencies = [
97194991 "proc-macro2",
97204992 "quote",
9721 "syn 2.0.119",
4993 "syn 3.0.3",
97224994]
9723
9724[[package]]
9725name = "zlib-rs"
9726version = "0.6.7"
9727source = "registry+https://github.com/rust-lang/crates.io-index"
9728checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
97294995
97304996[[package]]
97314997name = "zmij"
Modified Cargo.toml View Cargo.toml
@@ -1,156 +1,34 @@
1[workspace.package]
2version = "1.94.0"
3edition = "2024"
4authors = []
5categories = []
6keywords = []
7repository = ""
8homepage = ""
9
101[workspace]
11resolver = "3"
12members = [
13 "chat/storagelayer",
14 "lib/auth",
15 "lib/cache",
16 "lib/config",
17 "lib/db",
18 "lib/email",
19 "lib/export",
20 "dvcs/git",
21 "dvcs/gitauth",
22 "dvcs/gitlfs",
23 "dvcs/gitsmart",
24 "dvcs/gitssh",
25 "dvcs/gitsync",
26 "dvcs/codesearch",
27 "lib/issues",
28 "lib/jwt",
29 "lib/obs",
30 "lib/mcp",
31 "lib/payment",
32 "lib/post",
33 "lib/project",
34 "lib/queue",
35 "lib/rate_limit",
36 "lib/repo",
37 "lib/search",
38 "lib/secret",
39 "lib/space",
40 "lib/storage",
41 "lib/totp",
42 "lib/user",
43 "lib/webapi",
44 "lib/webhook",
45 "chat/chatlayer",
46 "chat/glayer",
47 "chat/live",
48 "app/livechat",
49 "app/apihost",
50 "app/mails",
51 "app/gitpro",
52 "app/githook",
53 "app/migrate",
54 "agent/llm",
55 "agent/lprovider"
2members = ["lib/cache",
3 "lib/config", "lib/export",
4 "lib/models",
5 "lib/session",
6 "lib/storage"
567]
578
9resolver = "3"
5810
11[workspace.package]
12version = "1.0.0"
13edition = "2024"
14
15
5916[workspace.dependencies]
60git = { path = "dvcs/git" }
61config = { path = "lib/config" }
62config-rs = { package = "config", version = "0.15", default-features = false, features = ["toml", "yaml"] }
63gitauth = { path = "dvcs/gitauth" }
64gitsmart = { path = "dvcs/gitsmart" }
65gitssh = { path = "dvcs/gitssh" }
66gitsync = { path = "dvcs/gitsync" }
67db = { path = "lib/db" }
68storage = { path = "lib/storage" }
69gitlfs = { path = "dvcs/gitlfs" }
70cache = { path = "lib/cache" }
71queue = { path = "lib/queue" }
72auth = { path = "lib/auth" }
73email = { path = "lib/email" }
74export = { path = "lib/export" }
75issues = { path = "lib/issues" }
76post = { path = "lib/post" }
77jwt = { path = "lib/jwt" }
78user = { path = "lib/user" }
79space = { path = "lib/space" }
80totp = { path = "lib/totp" }
81rate_limit = { path = "lib/rate_limit" }
82project = { path = "lib/project" }
83payment = { path = "lib/payment" }
84search = { path = "lib/search" }
85secret = { path = "lib/secret" }
86webapi = { path = "lib/webapi" }
87webhook = { path = "lib/webhook" }
88obs = { path = "lib/obs" }
89mcp = { path = "lib/mcp" }
90storagelayer = { path = "chat/storagelayer" }
91chatlayer = { path = "chat/chatlayer" }
92glayer = { path = "chat/glayer" }
93live = { path = "chat/live" }
17config = { workspace = true }
18models = { workspace = true }
19storage = { workspace = true }
20
9421
95tokio = { version = "1.53.1", features = ["full"] }
96sea-orm = { version = "2.0.1", features = ["sqlx-all","runtime-tokio","with-time","with-uuid"]}
97serde = { version = "1.0.229", features = ["derive"] }
98time = { version = "0.3", features = ["serde", "serde-human-readable", "macros", "formatting"] }
99uuid = { version = "1", features = ["v7", "serde"] }
100lombok = "0.4.0"
101log = "0.4"
102tracing = "0.1.44"
103tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] }
104thiserror = "2.0.19"
105async-trait = "0.1"
106bytes = "1"
107futures = "0.3"
108http = "1.2"
109object_store = { version = "0.14", default-features = false, features = ["aws", "fs"] }
110url = "2"
111serde_json = "1"
112serde_yaml = "0.9"
22tokio = "1.53.1"
23serde = "1.0.229"
24serde_json = "1.0.151"
11325axum = "0.8.9"
114axum-extra = { version = "0.12.6", features = ["cookie"] }
115reqwest = { version = "0.13.4", default-features = false, features = ["default-tls", "json", "form", "stream"] }
116altcha = { version = "0.2.0", features = ["argon2"] }
117argon2 = "0.5.3"
118password-hash = "0.5.0"
119rand = { version = "0.10.2", features = ["thread_rng"] }
120redis = { version = "1.5.0", features = ["tokio-comp", "cluster-async", "sentinel"] }
121bincache = { version = "0.5.1", features = ["rt_tokio_1"] }
122gix = { version = "0.86.0", features = ["serde", "sha256", "blocking-network-client"] }
123duct = "1.1.0"
124russh = "=0.62.2"
125ssh-key = "=0.7.0-rc.11"
126async-compression = { version = "0.4", features = ["tokio", "gzip"] }
127tokio-util = { version = "0.7", features = ["io"] }
128utoipa = { version = "5.5.0", features = ["uuid", "time", "yaml"] }
129clap = { version = "4.5", features = ["derive"] }
130jsonwebtoken = { version = "11.0.0", features = ["rust_crypto"] }
26axum_session = "0.21.0"
27rust-s3-async = "0.35.0"
28redis = { version = "1.5.0", features = ["aio", "tokio-comp", "connection-manager", "cluster-async"] }
29sea-orm = { version = "2.0.1", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
30oxcache = { version = "0.4.3", features = ["core"] }
31async-trait = "0.1.92"
13132base64 = "0.23.1"
132flate2 = "1"
133sha2 = "0.10"
134sha1 = "0.10"
135tokio-stream = "0.1.19"
136hmac = "0.12"
137hex = "0.4"
138aes-gcm = "0.11"
139features = "0.10.0"
140validator = { version = "0.21.0" }
141async-graphql = "7.2.1"
142utoipa-scalar = { version = "0.3.0", features = ["axum"] }
143metrics = { version = "0.24.6", features = [] }
144opentelemetry = { version = "=0.31.0", features = ["trace", "metrics", "logs"] }
145opentelemetry_sdk = { version = "=0.31.0", features = ["trace", "metrics", "logs", "rt-tokio"] }
146opentelemetry-otlp = { version = "=0.31.0", default-features = false, features = ["http-proto", "reqwest-blocking-client", "trace", "metrics", "logs"] }
147opentelemetry-appender-tracing = { version = "=0.31.0" }
148tracing-opentelemetry = { version = "=0.32.0" }
149metrics-exporter-opentelemetry = { version = "=0.2.1" }
150tonic = { version = "0.14.2", features = ["tls-ring", "tls-native-roots", "gzip"] }
151tonic-build = "0.14.2"
152tonic-health = "0.14.0"
153tonic-prost = "0.14.6"
154tonic-prost-build = "0.14.6"
155prost = "0.14.1"
156prost-types = "0.14.1"
33chrono = "0.4.45"
34
Deleted Makefile
@@ -1,285 +0,0 @@
1SHELL := /usr/bin/env bash
2MAKEFLAGS += --no-builtin-rules
3
4EXTRA_ARGS := $(filter-out -- $(firstword $(MAKECMDGOALS)),$(MAKECMDGOALS))
5
6.DEFAULT_GOAL := help
7
8.PHONY: help
9help:
10 @printf ' \033[36m%-22s\033[0m %s\n' 'target' 'description'
11 @awk 'BEGIN {FS = ":.*?## "} \
12 /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' \
13 $(MAKEFILE_LIST)
14
15.PHONY: build
16build:
17 cargo build --workspace
18
19.PHONY: check
20check:
21 cargo check --workspace --all-targets
22
23.PHONY: test
24test:
25 cargo test --workspace
26
27.PHONY: clippy
28clippy:
29 cargo clippy --workspace --all-targets -- -D warnings
30
31.PHONY: fmt
32fmt:
33 cargo fmt --all
34
35.PHONY: fmt-check
36fmt-check:
37 cargo fmt --all --check
38
39.PHONY: clean
40clean:
41 cargo clean
42 @find . -type d -name target -prune -exec rm -rf {} +
43 @echo "已清理 target/ 目录"
44
45# ---------- Docker ----------
46
47DOCKER_APPS := apihost githook gitpro livechat mails
48
49.PHONY: docker-build
50docker-build: ## 宿主机编译并构建全部应用的 Docker 镜像
51 ./docker/build.sh
52
53.PHONY: docker-build-apihost docker-build-githook docker-build-gitpro docker-build-livechat docker-build-mails
54docker-build-apihost: ## 构建 apihost 镜像
55 ./docker/build.sh apihost
56docker-build-githook: ## 构建 githook 镜像
57 ./docker/build.sh githook
58docker-build-gitpro: ## 构建 gitpro 镜像
59 ./docker/build.sh gitpro
60docker-build-livechat: ## 构建 livechat 镜像
61 ./docker/build.sh livechat
62docker-build-mails: ## 构建 mails 镜像
63 ./docker/build.sh mails
64
65.PHONY: list-apps
66list-apps:
67 @echo apihost githook gitpro livechat mails
68
69.PHONY: ports
70ports:
71 @printf ' %-12s %s\n' app port
72 @printf ' %-12s %s\n' apihost 8080
73 @printf ' %-12s %s\n' githook '(non-network)'
74 @printf ' %-12s %s\n' gitpro '(non-network)'
75 @printf ' %-12s %s\n' livechat 8081
76 @printf ' %-12s %s\n' mails '(non-network)'
77
78.PHONY: port-check
79port-check:
80 @status=0; \
81 check() { \
82 port="$$1"; \
83 name="$$2"; \
84 if [ "$$port" = "0" ]; then return; fi; \
85 if ss -ltn 2>/dev/null | grep -q ":$$port "; then \
86 echo " ✗ $$name: port $$port 已占用"; \
87 status=1; \
88 else \
89 echo " ✓ $$name: port $$port 空闲"; \
90 fi; \
91 }; \
92 check 8080 apihost; \
93 check 8081 livechat; \
94 exit $$status
95
96.PHONY: run-apihost
97run-apihost:
98 @if ss -ltn 2>/dev/null | grep -q ":8080 "; then \
99 echo "✗ apihost 默认端口 8080 已被占用"; \
100 echo " kill 占用进程,或 make run-apihost -- --port <其他>"; \
101 exit 1; \
102 fi
103 @if [ -n "$(EXTRA_ARGS)" ]; then \
104 cargo run -p apihost -- $(EXTRA_ARGS); \
105 else \
106 cargo run -p apihost; \
107 fi
108
109.PHONY: build-apihost
110build-apihost:
111 cargo build -p apihost
112
113.PHONY: check-apihost
114check-apihost:
115 cargo check -p apihost
116
117.PHONY: test-apihost
118test-apihost:
119 cargo test -p apihost
120
121.PHONY: dev-apihost
122dev-apihost:
123 @command -v cargo-watch >/dev/null 2>&1 || { \
124 echo "缺少 cargo-watch,请先: cargo install cargo-watch --locked"; \
125 exit 1; \
126 }
127 cargo watch -x "run -p apihost"
128
129.PHONY: run-githook
130run-githook:
131 @if [ -n "$(EXTRA_ARGS)" ]; then \
132 cargo run -p githook -- $(EXTRA_ARGS); \
133 else \
134 cargo run -p githook; \
135 fi
136
137.PHONY: build-githook
138build-githook:
139 cargo build -p githook
140
141.PHONY: check-githook
142check-githook:
143 cargo check -p githook
144
145.PHONY: test-githook
146test-githook:
147 cargo test -p githook
148
149.PHONY: dev-githook
150dev-githook:
151 @command -v cargo-watch >/dev/null 2>&1 || { \
152 echo "缺少 cargo-watch,请先: cargo install cargo-watch --locked"; \
153 exit 1; \
154 }
155 cargo watch -x "run -p githook"
156
157.PHONY: run-gitpro
158run-gitpro:
159 @if [ -n "$(EXTRA_ARGS)" ]; then \
160 cargo run -p gitpro -- $(EXTRA_ARGS); \
161 else \
162 cargo run -p gitpro; \
163 fi
164
165.PHONY: build-gitpro
166build-gitpro:
167 cargo build -p gitpro
168
169.PHONY: check-gitpro
170check-gitpro:
171 cargo check -p gitpro
172
173.PHONY: test-gitpro
174test-gitpro:
175 cargo test -p gitpro
176
177.PHONY: dev-gitpro
178dev-gitpro:
179 @command -v cargo-watch >/dev/null 2>&1 || { \
180 echo "缺少 cargo-watch,请先: cargo install cargo-watch --locked"; \
181 exit 1; \
182 }
183 cargo watch -x "run -p gitpro"
184
185.PHONY: run-livechat
186run-livechat:
187 @if ss -ltn 2>/dev/null | grep -q ":8081 "; then \
188 echo "✗ livechat 默认端口 8081 已被占用"; \
189 echo " kill 占用进程,或 make run-livechat -- --port <其他>"; \
190 exit 1; \
191 fi
192 @if [ -n "$(EXTRA_ARGS)" ]; then \
193 cargo run -p livechat -- $(EXTRA_ARGS); \
194 else \
195 cargo run -p livechat; \
196 fi
197
198.PHONY: build-livechat
199build-livechat:
200 cargo build -p livechat
201
202.PHONY: check-livechat
203check-livechat:
204 cargo check -p livechat
205
206.PHONY: test-livechat
207test-livechat:
208 cargo test -p livechat
209
210.PHONY: dev-livechat
211dev-livechat:
212 @command -v cargo-watch >/dev/null 2>&1 || { \
213 echo "缺少 cargo-watch,请先: cargo install cargo-watch --locked"; \
214 exit 1; \
215 }
216 cargo watch -x "run -p livechat"
217
218.PHONY: run-mails
219run-mails:
220 @if [ -n "$(EXTRA_ARGS)" ]; then \
221 cargo run -p mails -- $(EXTRA_ARGS); \
222 else \
223 cargo run -p mails; \
224 fi
225
226.PHONY: build-mails
227build-mails:
228 cargo build -p mails
229
230.PHONY: check-migrate
231check-migrate:
232 cargo check -p migrate
233
234.PHONY: migrate
235migrate:
236 cargo run -p migrate
237
238.PHONY: check-mails
239check-mails:
240 cargo check -p mails
241
242.PHONY: test-mails
243test-mails:
244 cargo test -p mails
245
246.PHONY: dev-mails
247dev-mails:
248 @command -v cargo-watch >/dev/null 2>&1 || { \
249 echo "缺少 cargo-watch,请先: cargo install cargo-watch --locked"; \
250 exit 1; \
251 }
252 cargo watch -x "run -p mails"
253
254.PHONY: run
255run: run-apihost
256
257.PHONY: build-all
258build-all:
259 @trap 'kill $$(jobs -p) 2>/dev/null' INT TERM
260 cargo build -p apihost & \
261 cargo build -p githook & \
262 cargo build -p gitpro & \
263 cargo build -p livechat & \
264 cargo build -p mails & \
265 wait
266
267
268.PHONY: run-all
269run-all: port-check
270 @trap 'kill $$(jobs -p) 2>/dev/null' INT TERM; \
271 cargo run -p apihost -- --port 8080 & \
272 cargo run -p githook & \
273 cargo run -p gitpro & \
274 cargo run -p livechat -- --port 8081 & \
275 cargo run -p mails & \
276 wait
277
278.PHONY: check-deps
279check-deps:
280 @command -v cargo-outdated >/dev/null 2>&1 && \
281 cargo outdated --workspace --exit-code 0 || \
282 echo "未安装 cargo-outdated(cargo install cargo-outdated --locked)"
283 @command -v cargo-deny >/dev/null 2>/dev/null && \
284 cargo deny check || \
285 echo "未安装 cargo-deny(cargo install cargo-deny --locked)"
No newline at end of file